diff --git a/.changeset/fast-feet-double.md b/.changeset/fast-feet-double.md new file mode 100644 index 0000000000..6f315b3186 --- /dev/null +++ b/.changeset/fast-feet-double.md @@ -0,0 +1,7 @@ +--- +"supi": patch +--- + +Subdependencies are not needlessly updated. + +Fixes a regression introduced by [cc8a3bd312ea1405a6c79b1d157f0f9ae1be07aa](https://github.com/pnpm/pnpm/commit/cc8a3bd312ea1405a6c79b1d157f0f9ae1be07aa). diff --git a/.changeset/tender-boats-refuse.md b/.changeset/tender-boats-refuse.md new file mode 100644 index 0000000000..6820f62212 --- /dev/null +++ b/.changeset/tender-boats-refuse.md @@ -0,0 +1,5 @@ +--- +"@pnpm/resolve-dependencies": minor +--- + +A new option added: `forceFullResolution`. When `true`, the whole dependency graph will be walked through during resolution. diff --git a/packages/resolve-dependencies/src/index.ts b/packages/resolve-dependencies/src/index.ts index 6b2962d6d0..1302e83a4d 100644 --- a/packages/resolve-dependencies/src/index.ts +++ b/packages/resolve-dependencies/src/index.ts @@ -51,6 +51,7 @@ export default async function ( dryRun: boolean, engineStrict: boolean, force: boolean, + forceFullResolution: boolean, hooks: { readPackage?: ReadPackageHook, }, @@ -77,6 +78,7 @@ export default async function ( dryRun: opts.dryRun, engineStrict: opts.engineStrict, force: opts.force, + forceFullResolution: opts.forceFullResolution, linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? -1, lockfileDir: opts.lockfileDir, nodeVersion: opts.nodeVersion, diff --git a/packages/resolve-dependencies/src/resolveDependencies.ts b/packages/resolve-dependencies/src/resolveDependencies.ts index 70d3beb91a..303ea6dd0c 100644 --- a/packages/resolve-dependencies/src/resolveDependencies.ts +++ b/packages/resolve-dependencies/src/resolveDependencies.ts @@ -121,6 +121,7 @@ export interface ChildrenByParentId { export interface ResolutionContext { defaultTag: string, dryRun: boolean, + forceFullResolution: boolean, resolvedPackagesByPackageId: ResolvedPackagesByPackageId, outdatedDependencies: {[pkgId: string]: string}, childrenByParentId: ChildrenByParentId, @@ -224,7 +225,7 @@ export default async function resolveDependencies ( parentDependsOnPeers: options.parentDependsOnPeers, preferredDependencies: options.preferredDependencies, prefix: ctx.prefix, - proceed: options.proceed, + proceed: options.proceed || ctx.forceFullResolution, registries: ctx.registries, resolvedDependencies: options.resolvedDependencies, }) @@ -583,7 +584,7 @@ async function resolveDependency ( if ( !options.parentDependsOnPeer && !pkgResponse.body.updated && options.currentDepth === Math.max(0, options.updateDepth) && - depIsLinked && !ctx.force + depIsLinked && !ctx.force && !options.proceed ) { return null } diff --git a/packages/supi/src/install/index.ts b/packages/supi/src/install/index.ts index 49e06e3ba9..8a8e65ffc2 100644 --- a/packages/supi/src/install/index.ts +++ b/packages/supi/src/install/index.ts @@ -583,20 +583,14 @@ async function installInContext ( !R.isEmpty(ctx.wantedLockfile.packages) && getPreferredVersionsFromLockfile(ctx.wantedLockfile.packages!) || undefined ) - const updateLockfile = ctx.wantedLockfile.lockfileVersion !== LOCKFILE_VERSION || !opts.currentLockfileIsUpToDate - const defaultUpdateDepth = (() => { - if (opts.force || updateLockfile) return Infinity - if (opts.update) { - return opts.depth - } - return -1 - })() + const forceFullResolution = ctx.wantedLockfile.lockfileVersion !== LOCKFILE_VERSION + || !opts.currentLockfileIsUpToDate + || opts.force const _toResolveImporter = toResolveImporter.bind(null, { - defaultUpdateDepth, + defaultUpdateDepth: opts.update ? opts.depth : -1, lockfileOnly: opts.lockfileOnly, preferredVersions, storeDir: ctx.storeDir, - updateLockfile, virtualStoreDir: ctx.virtualStoreDir, workspacePackages: opts.workspacePackages, }) @@ -614,6 +608,7 @@ async function installInContext ( dryRun: opts.lockfileOnly, engineStrict: opts.engineStrict, force: opts.force, + forceFullResolution, hooks: opts.hooks, linkWorkspacePackagesDepth: opts.linkWorkspacePackagesDepth ?? (opts.saveWorkspaceProtocol ? 0 : -1), lockfileDir: opts.lockfileDir, @@ -829,7 +824,6 @@ async function toResolveImporter ( lockfileOnly: boolean, preferredVersions?: PreferredVersions, storeDir: string, - updateLockfile: boolean, virtualStoreDir: string, workspacePackages: WorkspacePackages, }, @@ -847,7 +841,7 @@ async function toResolveImporter ( const existingDeps = nonLinkedDependencies .filter(({ alias }) => !project.wantedDependencies.some((wantedDep) => wantedDep.alias === alias)) let wantedDependencies!: Array - if (!project.manifest || opts.updateLockfile) { + if (!project.manifest) { wantedDependencies = [ ...project.wantedDependencies, ...existingDeps, diff --git a/packages/supi/test/install/misc.ts b/packages/supi/test/install/misc.ts index 39fd847bf2..bd6caca0f8 100644 --- a/packages/supi/test/install/misc.ts +++ b/packages/supi/test/install/misc.ts @@ -990,7 +990,7 @@ test('all the subdeps of dependencies are linked when a node_modules is partiall 'bar', 'foo', 'foobarqar', - 'qar', + 'is-positive', ] ) }) diff --git a/packages/supi/test/install/update.ts b/packages/supi/test/install/update.ts index 2530e47ae1..72be69c0c8 100644 --- a/packages/supi/test/install/update.ts +++ b/packages/supi/test/install/update.ts @@ -49,6 +49,42 @@ test('preserve subdeps on update', async (t: tape.Test) => { }) }) +test('preserve subdeps on update when no node_modules is present', async (t: tape.Test) => { + const project = prepareEmpty(t) + + await Promise.all([ + addDistTag('abc-grand-parent-with-c', '1.0.0', 'latest'), + addDistTag('abc-parent-with-ab', '1.0.0', 'latest'), + addDistTag('bar', '100.0.0', 'latest'), + addDistTag('foo', '100.0.0', 'latest'), + addDistTag('foobarqar', '1.0.0', 'latest'), + addDistTag('peer-c', '1.0.0', 'latest'), + ]) + + const manifest = await addDependenciesToPackage({}, ['foobarqar', 'abc-grand-parent-with-c'], await testDefaults({ lockfileOnly: true })) + + await Promise.all([ + addDistTag('abc-grand-parent-with-c', '1.0.1', 'latest'), + addDistTag('abc-parent-with-ab', '1.0.1', 'latest'), + addDistTag('bar', '100.1.0', 'latest'), + addDistTag('foo', '100.1.0', 'latest'), + addDistTag('foobarqar', '1.0.1', 'latest'), + ]) + + await install(manifest, await testDefaults({ update: true, depth: 0 })) + + const lockfile = await project.readLockfile() + + t.ok(lockfile.packages) + t.ok(lockfile.packages['/abc-parent-with-ab/1.0.0_peer-c@1.0.0'], 'preserve version of package that has resolved peer deps') + t.ok(lockfile.packages['/foobarqar/1.0.1']) + t.deepEqual(lockfile.packages['/foobarqar/1.0.1'].dependencies, { + bar: '100.0.0', + foo: '100.0.0', + qar: '100.0.0', + }) +}) + test('update does not fail when package has only peer dependencies', async (t: tape.Test) => { prepareEmpty(t)