diff --git a/e2e/pnpm_lockfiles/.bazelignore b/e2e/pnpm_lockfiles/.bazelignore index 12f5335689..7c55579bcb 100644 --- a/e2e/pnpm_lockfiles/.bazelignore +++ b/e2e/pnpm_lockfiles/.bazelignore @@ -5,3 +5,4 @@ projects/c/node_modules v54/node_modules/ v60/node_modules/ v61/node_modules/ +v90/node_modules/ diff --git a/e2e/pnpm_lockfiles/MODULE.bazel b/e2e/pnpm_lockfiles/MODULE.bazel index 2dce334fc4..45d6c35184 100644 --- a/e2e/pnpm_lockfiles/MODULE.bazel +++ b/e2e/pnpm_lockfiles/MODULE.bazel @@ -40,6 +40,7 @@ npm = use_extension( "v54", "v60", "v61", + "v90", ] ] @@ -52,5 +53,6 @@ npm = use_extension( "v54", "v60", "v61", + "v90", ] ] diff --git a/e2e/pnpm_lockfiles/WORKSPACE b/e2e/pnpm_lockfiles/WORKSPACE index 3668769277..5830c2bd87 100644 --- a/e2e/pnpm_lockfiles/WORKSPACE +++ b/e2e/pnpm_lockfiles/WORKSPACE @@ -27,15 +27,19 @@ load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock") "v54", "v60", "v61", + "v90", ] ] load("@lock-v54//:repositories.bzl", npm_repositories_v54 = "npm_repositories") load("@lock-v60//:repositories.bzl", npm_repositories_v60 = "npm_repositories") load("@lock-v61//:repositories.bzl", npm_repositories_v61 = "npm_repositories") +load("@lock-v90//:repositories.bzl", npm_repositories_v90 = "npm_repositories") npm_repositories_v54() npm_repositories_v60() npm_repositories_v61() + +npm_repositories_v90() diff --git a/e2e/pnpm_lockfiles/base/package.json b/e2e/pnpm_lockfiles/base/package.json index 755ac2a776..97257c0c5b 100644 --- a/e2e/pnpm_lockfiles/base/package.json +++ b/e2e/pnpm_lockfiles/base/package.json @@ -20,6 +20,9 @@ "pnpm": { "patchedDependencies": { "meaning-of-life@1.0.0": "patches/meaning-of-life@1.0.0-pnpm.patch" - } + }, + "onlyBuildDependencies": [ + "@aspect-test/c" + ] } } diff --git a/e2e/pnpm_lockfiles/setup.sh b/e2e/pnpm_lockfiles/setup.sh index 3e25fa9471..e3260c6aeb 100755 --- a/e2e/pnpm_lockfiles/setup.sh +++ b/e2e/pnpm_lockfiles/setup.sh @@ -3,6 +3,7 @@ # 5.4 - pnpm v7.0.0 bumped the lockfile version to 5.4 # 6.0 - pnpm v8.0.0 bumped the lockfile version to 6.0; this included breaking changes # 6.1 - pnpm v8.6.0 bumped the lockfile version to 6.1 +# 9.0 - pnpm v9.0.0 bumped the lockfile version to 9.0; this includes breaking changes regarding lifecycle hooks and patches mv v54/pnpm-lock.yaml base && pushd base && npx pnpm@^7.0 install --lockfile-only && mv pnpm-lock.yaml ../v54/ && popd @@ -10,3 +11,6 @@ mv v54/pnpm-lock.yaml base && pushd base && npx pnpm@^7.0 install --lockfile-onl # while still presenting minor differences from <8.6.0. mv v60/pnpm-lock.yaml base && pushd base && npx pnpm@8.5.1 install --lockfile-only && mv pnpm-lock.yaml ../v60/ && popd mv v61/pnpm-lock.yaml base && pushd base && npx pnpm@8.6.0 install --lockfile-only && mv pnpm-lock.yaml ../v61/ && popd + +# pnpm v9.0.0 bumped the lockfile version to 9.0 +mv v90/pnpm-lock.yaml base && pushd base && npx pnpm@^9.0 install --lockfile-only && mv pnpm-lock.yaml ../v90 && popd diff --git a/e2e/pnpm_lockfiles/v90/BUILD.bazel b/e2e/pnpm_lockfiles/v90/BUILD.bazel new file mode 100644 index 0000000000..e6d1703ef4 --- /dev/null +++ b/e2e/pnpm_lockfiles/v90/BUILD.bazel @@ -0,0 +1,6 @@ +load("@lock-v90//:defs.bzl", "npm_link_all_packages") +load("//:lockfile-test.bzl", "lockfile_test") + +npm_link_all_packages() + +lockfile_test() diff --git a/e2e/pnpm_lockfiles/v90/package.json b/e2e/pnpm_lockfiles/v90/package.json new file mode 120000 index 0000000000..9846ac29f1 --- /dev/null +++ b/e2e/pnpm_lockfiles/v90/package.json @@ -0,0 +1 @@ +../base/package.json \ No newline at end of file diff --git a/e2e/pnpm_lockfiles/v90/patches b/e2e/pnpm_lockfiles/v90/patches new file mode 120000 index 0000000000..143935d1fd --- /dev/null +++ b/e2e/pnpm_lockfiles/v90/patches @@ -0,0 +1 @@ +../base/patches \ No newline at end of file diff --git a/e2e/pnpm_lockfiles/v90/pnpm-lock.yaml b/e2e/pnpm_lockfiles/v90/pnpm-lock.yaml new file mode 100644 index 0000000000..c6d4448b3a --- /dev/null +++ b/e2e/pnpm_lockfiles/v90/pnpm-lock.yaml @@ -0,0 +1,172 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +patchedDependencies: + meaning-of-life@1.0.0: + hash: o3deharooos255qt5xdujc3cuq + path: patches/meaning-of-life@1.0.0-pnpm.patch + +importers: + + .: + dependencies: + '@aspect-test/a': + specifier: ^5.0.2 + version: 5.0.2 + '@aspect-test/e': + specifier: 1.0.0 + version: 1.0.0 + meaning-of-life: + specifier: 1.0.0 + version: 1.0.0(patch_hash=o3deharooos255qt5xdujc3cuq) + rollup: + specifier: 3.2.5 + version: 3.2.5 + uvu: + specifier: 0.5.6 + version: 0.5.6 + optionalDependencies: + '@aspect-test/c': + specifier: '2.0' + version: 2.0.2 + devDependencies: + '@aspect-test/b': + specifier: 5.0.2 + version: 5.0.2 + '@types/node': + specifier: ~16.18.11 + version: 16.18.97 + + ../projects/a: {} + + ../projects/b: + dependencies: + '@scoped/a': + specifier: workspace:* + version: link:../a + + ../projects/c: + dependencies: + '@scoped/a': + specifier: link:../a + version: link:../a + +packages: + + '@aspect-test/a@5.0.2': + resolution: {integrity: sha512-bURS+F0+tS2XPxUPbrqsTZxIre1U5ZglwzDqcOCrU7MbxuRrkO24hesgTMGJldCglwL/tiEGRlvdMndlPgRdNw==} + hasBin: true + + '@aspect-test/b@5.0.2': + resolution: {integrity: sha512-I8wnJV5J0h8ui1O3K6XPq1qGHKopTl/OnvkSfor7uJ9yRCm2Qv6Tf2LsTgR2xzkgiwhA4iBwdYFwecwinF244w==} + hasBin: true + + '@aspect-test/c@2.0.2': + resolution: {integrity: sha512-rMJmd3YBvY7y0jh+2m72TiAhe6dVKjMMNFFVOXFCbM233m7lsG4cq970H1C8rUsc3AcA5E/cEHlxSVffHlHD2Q==} + hasBin: true + + '@aspect-test/d@2.0.0': + resolution: {integrity: sha512-jndwr8pLUfn795uApTcXG/yZ5hV2At1aS/wo5BVLxqlVVgLoOETF/Dp4QOjMHE/SXkXFowz6Hao+WpmzVvAO0A==} + hasBin: true + peerDependencies: + '@aspect-test/c': x.x.x + + '@aspect-test/e@1.0.0': + resolution: {integrity: sha512-GyAxHYKN650db+xnimHnL2LPz65ilmQsVhCasWA7drDNQn/rfmPiEVMzjRiS7m46scXIERaBmiJMzYDf0bIUbA==} + hasBin: true + + '@types/node@16.18.97': + resolution: {integrity: sha512-4muilE1Lbfn57unR+/nT9AFjWk0MtWi5muwCEJqnOvfRQDbSfLCUdN7vCIg8TYuaANfhLOV85ve+FNpiUsbSRg==, tarball: https://registry.npmjs.org/@types/node/-/node-16.18.97.tgz} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + meaning-of-life@1.0.0: + resolution: {integrity: sha512-fVA4xSydqtK9owabGcYw1r4EKEsMOVVeYQLeCXPu77Z+8Y2j2B2I16UqZlKIOHnYkJ4RSvpJ00ywy9IWjmuxYw==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + rollup@3.2.5: + resolution: {integrity: sha512-/Ha7HhVVofduy+RKWOQJrxe4Qb3xyZo+chcpYiD8SoQa4AG7llhupUtyfKSSrdBM2mWJjhM8wZwmbY23NmlIYw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@aspect-test/a@5.0.2': + dependencies: + '@aspect-test/b': 5.0.2 + '@aspect-test/c': 2.0.2 + '@aspect-test/d': 2.0.0(@aspect-test/c@2.0.2) + + '@aspect-test/b@5.0.2': + dependencies: + '@aspect-test/a': 5.0.2 + '@aspect-test/c': 2.0.2 + '@aspect-test/d': 2.0.0(@aspect-test/c@2.0.2) + + '@aspect-test/c@2.0.2': {} + + '@aspect-test/d@2.0.0(@aspect-test/c@2.0.2)': + dependencies: + '@aspect-test/c': 2.0.2 + + '@aspect-test/e@1.0.0': {} + + '@types/node@16.18.97': {} + + dequal@2.0.3: {} + + diff@5.2.0: {} + + fsevents@2.3.3: + optional: true + + kleur@4.1.5: {} + + meaning-of-life@1.0.0(patch_hash=o3deharooos255qt5xdujc3cuq): {} + + mri@1.2.0: {} + + rollup@3.2.5: + optionalDependencies: + fsevents: 2.3.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.0 + kleur: 4.1.5 + sade: 1.8.1 diff --git a/npm/private/npm_translate_lock_helpers.bzl b/npm/private/npm_translate_lock_helpers.bzl index 3d0ebaee1d..2e83d4bdab 100644 --- a/npm/private/npm_translate_lock_helpers.bzl +++ b/npm/private/npm_translate_lock_helpers.bzl @@ -278,7 +278,6 @@ def _get_npm_imports(importers, packages, patched_dependencies, only_built_depen optional_deps = package_info.get("optional_dependencies") dev = package_info.get("dev") optional = package_info.get("optional") - pnpm_patched = package_info.get("patched") requires_build = package_info.get("requires_build") transitive_closure = package_info.get("transitive_closure") resolution = package_info.get("resolution") @@ -331,6 +330,9 @@ def _get_npm_imports(importers, packages, patched_dependencies, only_built_depen translate_patches, patches_keys = _gather_values_from_matching_names(True, attr.patches, name, friendly_name, unfriendly_name) + pnpm_patch = patched_dependencies.get(friendly_name, {}).get("path", None) + pnpm_patched = pnpm_patch != None + if len(translate_patches) > 0 and pnpm_patched: msg = """\ ERROR: can not apply both `pnpm.patchedDependencies` and `npm_translate_lock(patches)` to the same package {pkg}. @@ -341,7 +343,7 @@ ERROR: can not apply both `pnpm.patchedDependencies` and `npm_translate_lock(pat # Apply patch from `pnpm.patchedDependencies` first if pnpm_patched: - patch_path = "//%s:%s" % (attr.pnpm_lock.package, patched_dependencies.get(friendly_name).get("path")) + patch_path = "//%s:%s" % (attr.pnpm_lock.package, pnpm_patch) patches.append(patch_path) # pnpm patches are always applied with -p1 diff --git a/npm/private/test/utils_tests.bzl b/npm/private/test/utils_tests.bzl index 0a63a1f857..039d9a7075 100644 --- a/npm/private/test/utils_tests.bzl +++ b/npm/private/test/utils_tests.bzl @@ -68,12 +68,13 @@ def test_version_supported(ctx): msg = utils.assert_lockfile_version(1.2, testonly = True) asserts.equals(env, "npm_translate_lock requires lock_version at least 5.4, but found 1.2. Please upgrade to pnpm v7 or greater.", msg) msg = utils.assert_lockfile_version(99.99, testonly = True) - asserts.equals(env, "npm_translate_lock currently supports a maximum lock_version of 6.1, but found 99.99. Please file an issue on rules_js", msg) + asserts.equals(env, "npm_translate_lock currently supports a maximum lock_version of 9.0, but found 99.99. Please file an issue on rules_js", msg) # supported versions utils.assert_lockfile_version(5.4) utils.assert_lockfile_version(6.0) utils.assert_lockfile_version(6.1) + utils.assert_lockfile_version(9.0) return unittest.end(env) diff --git a/npm/private/transitive_closure.bzl b/npm/private/transitive_closure.bzl index 14b967fd45..edb54c1481 100644 --- a/npm/private/transitive_closure.bzl +++ b/npm/private/transitive_closure.bzl @@ -130,7 +130,6 @@ def _gather_package_info(package_path, package_snapshot): "optional_dependencies": package_snapshot.get("optionalDependencies", {}), "dev": package_snapshot.get("dev", False), "optional": package_snapshot.get("optional", False), - "patched": package_snapshot.get("patched", False), "has_bin": package_snapshot.get("hasBin", False), "requires_build": package_snapshot.get("requiresBuild", False), } diff --git a/npm/private/utils.bzl b/npm/private/utils.bzl index 614e85e1e0..1f34f3b75d 100644 --- a/npm/private/utils.bzl +++ b/npm/private/utils.bzl @@ -162,6 +162,74 @@ def _convert_v6_packages(packages): package_info[key] = dependencies result[_convert_pnpm_v6_package_name(package)] = package_info + + return result + +def _convert_pnpm_v9_package_name(package_name): + # Covert a pnpm lock file v9 name/version@version string of the format + # @scope/name@version(@scope/name@version)(@scope/name@version)@version + # to a pnpm lock file v5 @scope/name/version_peer_version format that is compatible with rules_js. + package_name = _convert_pnpm_v6_version_peer_dep(package_name) + segments = package_name.rsplit("@", 1) + if len(segments) != 2: + msg = "unexpected pnpm versioned name {}".format(package_name) + fail(msg) + return "/%s/%s" % (segments[0], segments[1]) + +# v9 importers are the same as v6 importers +_convert_v9_importers = _convert_v6_importers + +def _convert_v9_packages(packages, snapshots): + # Convert pnpm lockfile v9 importers to a rules_js compatible format. + + # v9 split package metadata (v6 "packages" field) into 2: + # + # packages: + # '@scoped/name@5.0.2' + # hasBin + # resolution (integrity etc) + # peerDependencies which *might* be resolved + # + # snapshots: + # '@scoped/name@2.0.0(peer@2.0.2)' + # dependencies: + # a-dep@1.2.3 + # peer@2.0.2 + # b-dep@3.2.1(peer-b@4.5.6) + # + # Where the 'snapshots' keys contain the peer information while 'packages' contain the static information + # such as hasBin, resolution and peerDependencies that require resolution. + + result = {} + + # Snapshots contains the packages with the keys (which include peers) to return + for package, snapshot_info in snapshots.items(): + # convert v6 package dependencies + optionalDependencies + for key in ["dependencies", "optionalDependencies"]: + deps = snapshot_info.get(key, None) + if deps != None: + dependencies = {} + for dep_name, dep_version in deps.items(): + dependencies[dep_name] = _convert_pnpm_v6_version_peer_dep(dep_version) + snapshot_info[key] = dependencies + + # Strip peer-dep info off to get the raw package + package_version = package + if package_version[-1] == ")": + package_version = package_version[:package_version.find("(")] + + # Metadata for this snapshot persisted in the 'packages' + package_info = packages[package_version] + if package_info == None: + msg = "Failed to find pnpm-lock snapshot %s (%s) in packages" % (package, package_version) + fail(msg) + + # Also include the static data from the 'packages' + for info_name, info_value in package_info.items(): + snapshot_info[info_name] = info_value + + result[_convert_pnpm_v9_package_name(package)] = snapshot_info + return result def _parse_pnpm_lock_json(content): @@ -212,7 +280,11 @@ def _parse_pnpm_lock_common(parsed, err): packages = parsed.get("packages", {}) - if lockfile_version >= 6.0: + if lockfile_version >= 9.0: + snapshots = parsed.get("snapshots", {}) + importers = _convert_v9_importers(importers) + packages = _convert_v9_packages(packages, snapshots) + elif lockfile_version >= 6.0: # special handling for lockfile v6 which had breaking changes importers = _convert_v6_importers(importers) packages = _convert_v6_packages(packages) @@ -229,8 +301,9 @@ def _assert_lockfile_version(version, testonly = False): # 5.4 - pnpm v7.0.0 bumped the lockfile version to 5.4 # 6.0 - pnpm v8.0.0 bumped the lockfile version to 6.0; this included breaking changes # 6.1 - pnpm v8.6.0 bumped the lockfile version to 6.1 + # 9.0 - pnpm v9.0.0 bumped the lockfile version to 9.0 min_lock_version = 5.4 - max_lock_version = 6.1 + max_lock_version = 9.0 msg = None if version < min_lock_version: