diff --git a/.changeset/hungry-geckos-roll.md b/.changeset/hungry-geckos-roll.md new file mode 100644 index 0000000000..bc21418b56 --- /dev/null +++ b/.changeset/hungry-geckos-roll.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-installation": patch +"pnpm": patch +--- + +Add support for npm lockfile v3 in `pnpm import` [#6233](https://github.com/pnpm/pnpm/issues/6233). diff --git a/__fixtures__/has-package-lock-v3-json/.gitignore b/__fixtures__/has-package-lock-v3-json/.gitignore new file mode 100644 index 0000000000..3189f95442 --- /dev/null +++ b/__fixtures__/has-package-lock-v3-json/.gitignore @@ -0,0 +1 @@ +!package-lock.json diff --git a/__fixtures__/has-package-lock-v3-json/package-lock.json b/__fixtures__/has-package-lock-v3-json/package-lock.json new file mode 100644 index 0000000000..8bc7a1604b --- /dev/null +++ b/__fixtures__/has-package-lock-v3-json/package-lock.json @@ -0,0 +1,22 @@ +{ + "name": "has-package-lock-json", + "version": "0.0.0", + "lockfileVersion": 3, + "packages": { + "": { + "version": "0.0.0", + "dependencies": { + "@pnpm.e2e/pkg-with-1-dep": "100.0.0" + } + }, + "node_modules/@pnpm.e2e/dep-of-pkg-with-1-dep": { + "version": "101.0.0" + }, + "node_modules/@pnpm.e2e/pkg-with-1-dep": { + "version": "100.0.0", + "dependencies": { + "@pnpm.e2e/dep-of-pkg-with-1-dep": "100.0.0" + } + } + } +} diff --git a/__fixtures__/has-package-lock-v3-json/package.json b/__fixtures__/has-package-lock-v3-json/package.json new file mode 100644 index 0000000000..bd82d166a5 --- /dev/null +++ b/__fixtures__/has-package-lock-v3-json/package.json @@ -0,0 +1,11 @@ +{ + "name": "has-package-lock-v3-json", + "version": "0.0.0", + "dependencies": { + "@pnpm.e2e/dep-of-pkg-with-1-dep": "^101.0.0", + "@pnpm.e2e/pkg-with-1-dep": "*" + }, + "scripts": { + "prepare": "exit 1" + } +} diff --git a/__fixtures__/pnpm-workspace.yaml b/__fixtures__/pnpm-workspace.yaml index 8587c3deea..9d01a4e5ae 100644 --- a/__fixtures__/pnpm-workspace.yaml +++ b/__fixtures__/pnpm-workspace.yaml @@ -7,6 +7,7 @@ packages: - '!has-npm-shrinkwrap-json' - '!has-outdated-deps' - '!has-package-lock-json' + - '!has-package-lock-v3-json' - '!hello-world-js-bin' - '!has-yarn-lock' - '!has-yarn2-lock' diff --git a/pkg-manager/plugin-commands-installation/src/import/index.ts b/pkg-manager/plugin-commands-installation/src/import/index.ts index 18be442bf7..30b868636e 100644 --- a/pkg-manager/plugin-commands-installation/src/import/index.ts +++ b/pkg-manager/plugin-commands-installation/src/import/index.ts @@ -28,11 +28,20 @@ import { yarnLockFileKeyNormalizer } from './yarnUtil' interface NpmPackageLock { dependencies: LockedPackagesMap + packages: LockedPackagesMap + name?: string } interface LockedPackage { version: string - dependencies?: LockedPackagesMap + lockfileVersion: number + name?: string + dependencies?: LockedPackagesMap | SimpleDependenciesMap + packages?: LockedPackagesMap +} + +interface SimpleDependenciesMap { + [name: string]: string } interface LockedPackagesMap { @@ -109,7 +118,11 @@ export async function handler ( await exists(path.join(opts.dir, 'npm-shrinkwrap.json')) ) { const npmPackageLock = await readNpmLockfile(opts.dir) - getAllVersionsByPackageNames(npmPackageLock, versionsByPackageNames) + if (npmPackageLock.lockfileVersion < 3) { + getAllVersionsByPackageNamesPreV3(npmPackageLock, versionsByPackageNames) + } else { + getAllVersionsByPackageNames(npmPackageLock, versionsByPackageNames) + } } else { throw new PnpmError('LOCKFILE_NOT_FOUND', 'No lockfile found') } @@ -224,7 +237,7 @@ async function readNpmLockfile (dir: string) { throw new PnpmError('NPM_LOCKFILE_NOT_FOUND', 'No package-lock.json or npm-shrinkwrap.json found') } -function getPreferredVersions (versionsByPackageNames: Record>) { +function getPreferredVersions (versionsByPackageNames: VersionsByPackageNames) { const preferredVersions = mapValues( (versions) => Object.fromEntries(Array.from(versions).map((version) => [version, 'version'])), versionsByPackageNames @@ -232,11 +245,11 @@ function getPreferredVersions (versionsByPackageNames: Record> + +function getAllVersionsByPackageNamesPreV3 ( npmPackageLock: NpmPackageLock | LockedPackage, - versionsByPackageNames: { - [packageName: string]: Set - } + versionsByPackageNames: VersionsByPackageNames ) { if (npmPackageLock.dependencies == null) return for (const [packageName, { version }] of Object.entries(npmPackageLock.dependencies)) { @@ -246,7 +259,48 @@ function getAllVersionsByPackageNames ( versionsByPackageNames[packageName].add(version) } for (const dep of Object.values(npmPackageLock.dependencies)) { - getAllVersionsByPackageNames(dep, versionsByPackageNames) + getAllVersionsByPackageNamesPreV3(dep, versionsByPackageNames) + } +} + +function getAllVersionsByPackageNames ( + pkg: NpmPackageLock | LockedPackage, + versionsByPackageNames: VersionsByPackageNames +): void { + if (pkg.dependencies) { + extractDependencies(versionsByPackageNames, pkg.dependencies as LockedPackagesMap) + } + if ('packages' in pkg && pkg.packages) { + extractDependencies(versionsByPackageNames, pkg.packages) + } +} + +function extractDependencies ( + versionsByPackageNames: VersionsByPackageNames, + dependencies: LockedPackagesMap +): void { + for (let [pkgName, pkgDetails] of Object.entries(dependencies)) { + if (pkgName.includes('node_modules')) { + pkgName = pkgName.substring(pkgName.lastIndexOf('node_modules/') + 13) + } + if (!versionsByPackageNames[pkgName]) { + versionsByPackageNames[pkgName] = new Set() + } + if (pkgDetails.version) { + versionsByPackageNames[pkgName].add(pkgDetails.version) + } + + if (pkgDetails.packages) { + extractDependencies(versionsByPackageNames, pkgDetails.packages) + } + if (pkgDetails.dependencies) { + for (const [pkgName1, version] of Object.entries(pkgDetails.dependencies)) { + if (!versionsByPackageNames[pkgName1]) { + versionsByPackageNames[pkgName1] = new Set() + } + versionsByPackageNames[pkgName1].add(version) + } + } } } diff --git a/pkg-manager/plugin-commands-installation/test/import.ts b/pkg-manager/plugin-commands-installation/test/import.ts index 0ef528139e..18401eb95a 100644 --- a/pkg-manager/plugin-commands-installation/test/import.ts +++ b/pkg-manager/plugin-commands-installation/test/import.ts @@ -131,3 +131,22 @@ test('import fails when no lockfiles are found', async () => { new PnpmError('LOCKFILE_NOT_FOUND', 'No lockfile found') ) }) + +test('import from package-lock.json v3', async () => { + await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' }) + f.prepare('has-package-lock-v3-json') + + await importCommand.handler({ + ...DEFAULT_OPTS, + dir: process.cwd(), + }, []) + + const project = assertProject(process.cwd()) + const lockfile = await project.readLockfile() + expect(lockfile.packages).toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0']) + expect(lockfile.packages).not.toHaveProperty(['/@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0']) + + // node_modules is not created + await project.hasNot('@pnpm.e2e/dep-of-pkg-with-1-dep') + await project.hasNot('@pnpm.e2e/pkg-with-1-dep') +})