From 1dc39a12d3dfd98a9c338dc4363c0ed7e6280b1e Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Fri, 12 May 2023 23:22:00 +0300 Subject: [PATCH 1/4] fix: print more info when lockfile is outdated ref #6526 --- .../src/satisfiesPackageManifest.ts | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts b/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts index ae289345b34..b4c3db40491 100644 --- a/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts +++ b/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts @@ -14,8 +14,8 @@ export function satisfiesPackageManifest ( }, importer: ProjectSnapshot | undefined, pkg: ProjectManifest -) { - if (!importer) return false +): { satisfies: boolean, detailedReason?: string } { + if (!importer) return { satisfies: false, detailedReason: 'no importer' } let existingDeps: Record = { ...pkg.devDependencies, ...pkg.dependencies, ...pkg.optionalDependencies } if (opts?.autoInstallPeers) { pkg = { @@ -36,13 +36,24 @@ export function satisfiesPackageManifest ( existingDeps = pickNonLinkedDeps(existingDeps) specs = pickNonLinkedDeps(specs) } - if ( - !equals(existingDeps, specs) || - importer.publishDirectory !== pkg.publishConfig?.directory - ) { - return false + if (!equals(existingDeps, specs)) { + return { + satisfies: false, + detailedReason: `importer specifiers don't match package manifest`, + } + } + if (importer.publishDirectory !== pkg.publishConfig?.directory) { + return { + satisfies: false, + detailedReason: `importer publish directory doesn't match package manifest`, + } + } + if (!equals(pkg.dependenciesMeta ?? {}, importer.dependenciesMeta ?? {})) { + return { + satisfies: false, + detailedReason: `importer dependencies meta doesn't match package manifest`, + } } - if (!equals(pkg.dependenciesMeta ?? {}, importer.dependenciesMeta ?? {})) return false for (const depField of DEPENDENCIES_FIELDS) { const importerDeps = importer[depField] ?? {} let pkgDeps: Record = pkg[depField] ?? {} @@ -69,15 +80,25 @@ export function satisfiesPackageManifest ( default: throw new Error(`Unknown dependency type "${depField as string}"`) } - if (pkgDepNames.length !== Object.keys(importerDeps).length && - pkgDepNames.length !== countOfNonLinkedDeps(importerDeps)) { - return false + if ( + pkgDepNames.length !== Object.keys(importerDeps).length && + pkgDepNames.length !== countOfNonLinkedDeps(importerDeps) + ) { + return { + satisfies: false, + detailedReason: `importer ${depField} don't match package manifest`, + } } for (const depName of pkgDepNames) { - if (!importerDeps[depName] || importer.specifiers?.[depName] !== pkgDeps[depName]) return false + if (!importerDeps[depName] || importer.specifiers?.[depName] !== pkgDeps[depName]) { + return { + satisfies: false, + detailedReason: `importer ${depField} don't match package manifest`, + } + } } } - return true + return { satisfies: true } } function countOfNonLinkedDeps (lockfileDeps: { [depName: string]: string }): number { From 2e3e0ab973ca65659b877c3fa2bb27313a1b44b7 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 14 May 2023 00:30:22 +0300 Subject: [PATCH 2/4] fix: print more info when lockfile is outdated --- .../src/satisfiesPackageManifest.ts | 10 +-- .../test/satisfiesPackageManifest.ts | 61 +++++++++++++------ 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts b/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts index b4c3db40491..0096bbe56d7 100644 --- a/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts +++ b/lockfile/lockfile-utils/src/satisfiesPackageManifest.ts @@ -39,19 +39,19 @@ export function satisfiesPackageManifest ( if (!equals(existingDeps, specs)) { return { satisfies: false, - detailedReason: `importer specifiers don't match package manifest`, + detailedReason: `specifiers in the lockfile (${JSON.stringify(specs)}) don't match specs in package.json (${JSON.stringify(existingDeps)})`, } } if (importer.publishDirectory !== pkg.publishConfig?.directory) { return { satisfies: false, - detailedReason: `importer publish directory doesn't match package manifest`, + detailedReason: `"publishDirectory" in the lockfile (${importer.publishDirectory ?? 'undefined'}) doesn't match "publishConfig.directory" in package.json (${pkg.publishConfig?.directory ?? 'undefined'})`, } } if (!equals(pkg.dependenciesMeta ?? {}, importer.dependenciesMeta ?? {})) { return { satisfies: false, - detailedReason: `importer dependencies meta doesn't match package manifest`, + detailedReason: `importer dependencies meta (${JSON.stringify(importer.dependenciesMeta)}) doesn't match package manifest dependencies meta (${JSON.stringify(pkg.dependenciesMeta)})`, } } for (const depField of DEPENDENCIES_FIELDS) { @@ -86,14 +86,14 @@ export function satisfiesPackageManifest ( ) { return { satisfies: false, - detailedReason: `importer ${depField} don't match package manifest`, + detailedReason: `"${depField}" in the lockfile (${JSON.stringify(importerDeps)}) doesn't match the same field in package.json (${JSON.stringify(pkgDeps)})`, } } for (const depName of pkgDepNames) { if (!importerDeps[depName] || importer.specifiers?.[depName] !== pkgDeps[depName]) { return { satisfies: false, - detailedReason: `importer ${depField} don't match package manifest`, + detailedReason: `importer ${depField}.${depName} specifier ${importer.specifiers[depName]} don't match package manifest specifier (${pkgDeps[depName]})`, } } } diff --git a/lockfile/lockfile-utils/test/satisfiesPackageManifest.ts b/lockfile/lockfile-utils/test/satisfiesPackageManifest.ts index 18b841d3e9f..eab985deaec 100644 --- a/lockfile/lockfile-utils/test/satisfiesPackageManifest.ts +++ b/lockfile/lockfile-utils/test/satisfiesPackageManifest.ts @@ -16,7 +16,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0' }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, { @@ -28,7 +28,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0' }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, { @@ -39,7 +39,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, devDependencies: { foo: '^1.0.0' }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, { @@ -50,7 +50,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, optionalDependencies: { foo: '^1.0.0' }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, { @@ -61,7 +61,10 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, optionalDependencies: { foo: '^1.0.0' }, } - )).toBe(false) + )).toStrictEqual({ + satisfies: false, + detailedReason: '"optionalDependencies" in the lockfile ({}) doesn\'t match the same field in package.json ({"foo":"^1.0.0"})', + }) expect(satisfiesPackageManifest( {}, { @@ -72,7 +75,10 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.1.0' }, } - )).toBe(false) + )).toStrictEqual({ + satisfies: false, + detailedReason: 'specifiers in the lockfile ({"foo":"^1.0.0"}) don\'t match specs in package.json ({"foo":"^1.1.0"})', + }) expect(satisfiesPackageManifest( {}, { @@ -83,7 +89,10 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0', bar: '2.0.0' }, } - )).toBe(false) + )).toStrictEqual({ + satisfies: false, + detailedReason: 'specifiers in the lockfile ({"foo":"^1.0.0"}) don\'t match specs in package.json ({"foo":"^1.0.0","bar":"2.0.0"})', + }) expect(satisfiesPackageManifest( {}, @@ -95,7 +104,10 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0', bar: '2.0.0' }, } - )).toBe(false) + )).toStrictEqual({ + satisfies: false, + detailedReason: '"dependencies" in the lockfile ({"foo":"1.0.0"}) doesn\'t match the same field in package.json ({"foo":"^1.0.0","bar":"2.0.0"})', + }) { const importer = { @@ -120,7 +132,7 @@ test('satisfiesPackageManifest()', () => { bar: '2.0.0', }, } - expect(satisfiesPackageManifest({}, importer, pkg)).toBe(true) + expect(satisfiesPackageManifest({}, importer, pkg)).toStrictEqual({ satisfies: true }) } { @@ -140,7 +152,10 @@ test('satisfiesPackageManifest()', () => { bar: '2.0.0', }, } - expect(satisfiesPackageManifest({}, importer, pkg)).toBe(false) + expect(satisfiesPackageManifest({}, importer, pkg)).toStrictEqual({ + satisfies: false, + detailedReason: 'specifiers in the lockfile ({"bar":"2.0.0","qar":"^1.0.0"}) don\'t match specs in package.json ({"bar":"2.0.0"})', + }) } { @@ -159,7 +174,10 @@ test('satisfiesPackageManifest()', () => { bar: '2.0.0', }, } - expect(satisfiesPackageManifest({}, importer, pkg)).toBe(false) + expect(satisfiesPackageManifest({}, importer, pkg)).toStrictEqual({ + satisfies: false, + detailedReason: '"dependencies" in the lockfile ({"bar":"2.0.0","qar":"1.0.0"}) doesn\'t match the same field in package.json ({"bar":"2.0.0"})', + }) } expect(satisfiesPackageManifest( @@ -172,7 +190,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0' }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, @@ -181,7 +199,7 @@ test('satisfiesPackageManifest()', () => { ...DEFAULT_PKG_FIELDS, dependencies: { foo: '^1.0.0' }, } - )).toBe(false) + )).toStrictEqual({ satisfies: false, detailedReason: 'no importer' }) expect(satisfiesPackageManifest( {}, @@ -202,7 +220,7 @@ test('satisfiesPackageManifest()', () => { foo: '1.0.0', }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, @@ -224,7 +242,7 @@ test('satisfiesPackageManifest()', () => { }, dependenciesMeta: {}, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( { autoInstallPeers: true }, @@ -247,7 +265,7 @@ test('satisfiesPackageManifest()', () => { bar: '^1.0.0', }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( { autoInstallPeers: true }, @@ -284,7 +302,7 @@ test('satisfiesPackageManifest()', () => { qar: '^1.0.0', }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, @@ -306,7 +324,7 @@ test('satisfiesPackageManifest()', () => { directory: 'dist', }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) expect(satisfiesPackageManifest( {}, @@ -328,7 +346,10 @@ test('satisfiesPackageManifest()', () => { directory: 'lib', }, } - )).toBe(false) + )).toStrictEqual({ + satisfies: false, + detailedReason: '"publishDirectory" in the lockfile (dist) doesn\'t match "publishConfig.directory" in package.json (lib)', + }) expect(satisfiesPackageManifest( { @@ -349,5 +370,5 @@ test('satisfiesPackageManifest()', () => { bar: 'link:../bar', }, } - )).toBe(true) + )).toStrictEqual({ satisfies: true }) }) From db544d87bd5ec88ed13341dc8adf39dd4a9c5cf6 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 14 May 2023 02:51:34 +0300 Subject: [PATCH 3/4] fix: print more info when lockfile is outdated --- pkg-manager/core/src/install/allProjectsAreUpToDate.ts | 2 +- pkg-manager/headless/src/index.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg-manager/core/src/install/allProjectsAreUpToDate.ts b/pkg-manager/core/src/install/allProjectsAreUpToDate.ts index fa22407009b..ae8fc01200e 100644 --- a/pkg-manager/core/src/install/allProjectsAreUpToDate.ts +++ b/pkg-manager/core/src/install/allProjectsAreUpToDate.ts @@ -39,7 +39,7 @@ export async function allProjectsAreUpToDate ( return pEvery(projects, (project) => { const importer = opts.wantedLockfile.importers[project.id] return !hasLocalTarballDepsInRoot(importer) && - _satisfiesPackageManifest(importer, project.manifest) && + _satisfiesPackageManifest(importer, project.manifest).satisfies && _linkedPackagesAreUpToDate({ dir: project.rootDir, manifest: project.manifest, diff --git a/pkg-manager/headless/src/index.ts b/pkg-manager/headless/src/index.ts index 7a2fe832a6b..67117392497 100644 --- a/pkg-manager/headless/src/index.ts +++ b/pkg-manager/headless/src/index.ts @@ -200,11 +200,15 @@ export async function headlessInstall (opts: HeadlessOptions): Promise Date: Sun, 14 May 2023 03:01:11 +0300 Subject: [PATCH 4/4] docs: add changesets --- .changeset/ninety-eels-begin.md | 5 +++++ .changeset/rotten-rivers-ring.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/ninety-eels-begin.md create mode 100644 .changeset/rotten-rivers-ring.md diff --git a/.changeset/ninety-eels-begin.md b/.changeset/ninety-eels-begin.md new file mode 100644 index 00000000000..67ff403b41e --- /dev/null +++ b/.changeset/ninety-eels-begin.md @@ -0,0 +1,5 @@ +--- +"@pnpm/lockfile-utils": major +--- + +Return details about the reason why the lockfile doesn't satisfy the manifest. diff --git a/.changeset/rotten-rivers-ring.md b/.changeset/rotten-rivers-ring.md new file mode 100644 index 00000000000..5b4afb47c8a --- /dev/null +++ b/.changeset/rotten-rivers-ring.md @@ -0,0 +1,6 @@ +--- +"@pnpm/headless": patch +"pnpm": patch +--- + +When installation fails because the lockfile is not up-to-date with the `package.json` file(s), print out what are the differences [#6536](https://github.com/pnpm/pnpm/pull/6536).