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 86d29aa8d3..a594c7f0b8 100644 --- a/e2e/pnpm_lockfiles/MODULE.bazel +++ b/e2e/pnpm_lockfiles/MODULE.bazel @@ -34,6 +34,10 @@ npm = use_extension( "//%s:patches/meaning-of-life@1.0.0-pnpm.patch" % version, "//:pnpm-workspace.yaml", ], + # Must manually opt-in to lifecycle hooks for lockfile v9. + # Do not opt-in for earlier versions to ensure lifecycle hooks are determined from + # the lockfile when supported by lockfile =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.11': {} + + 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/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/utils.bzl b/npm/private/utils.bzl index 882c0905d6..a8288d5b92 100644 --- a/npm/private/utils.bzl +++ b/npm/private/utils.bzl @@ -159,6 +159,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): @@ -209,7 +277,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) @@ -226,8 +298,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: