diff --git a/.changeset/dry-bobcats-yawn.md b/.changeset/dry-bobcats-yawn.md new file mode 100644 index 0000000000..ac902bae38 --- /dev/null +++ b/.changeset/dry-bobcats-yawn.md @@ -0,0 +1,5 @@ +--- +"@pnpm/plugin-commands-installation": minor +--- + +Allow to update specific packages up until a specified depth. For instance, `pnpm update @types/* --depth Infinity`. diff --git a/.changeset/few-flies-hunt.md b/.changeset/few-flies-hunt.md new file mode 100644 index 0000000000..0af5d5c984 --- /dev/null +++ b/.changeset/few-flies-hunt.md @@ -0,0 +1,6 @@ +--- +"@pnpm/resolve-dependencies": minor +"supi": minor +--- + +New option added: updateMatching. updateMatching is a function that accepts a package name. It returns `true` if the specified package should be updated. diff --git a/packages/plugin-commands-installation/src/install.ts b/packages/plugin-commands-installation/src/install.ts index 3c8500c32f..337e2fba37 100644 --- a/packages/plugin-commands-installation/src/install.ts +++ b/packages/plugin-commands-installation/src/install.ts @@ -243,6 +243,7 @@ export type InstallCommandOptions = Pick boolean, updatePackageManifest?: boolean, useBetaCli?: boolean, recursive?: boolean, diff --git a/packages/plugin-commands-installation/src/recursive.ts b/packages/plugin-commands-installation/src/recursive.ts index 9f0179e417..965bc941c8 100755 --- a/packages/plugin-commands-installation/src/recursive.ts +++ b/packages/plugin-commands-installation/src/recursive.ts @@ -42,6 +42,7 @@ import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './u type RecursiveOptions = CreateStoreControllerOptions & Pick !dep.substring(1).includes('@')) && opts.depth && opts.depth > 0 && !opts.latest + ? matcher(dependencies) : undefined, updatePackageManifest: opts.save !== false, }, dependencies) } diff --git a/packages/resolve-dependencies/src/index.ts b/packages/resolve-dependencies/src/index.ts index 5f522975a0..49489a48a0 100644 --- a/packages/resolve-dependencies/src/index.ts +++ b/packages/resolve-dependencies/src/index.ts @@ -58,6 +58,7 @@ export default async function ( nodeVersion: string, registries: Registries, pnpmVersion: string, + updateMatching?: (pkgName: string) => boolean, linkWorkspacePackagesDepth?: number, lockfileDir: string, storeController: StoreController, @@ -91,6 +92,7 @@ export default async function ( resolvedPackagesByPackageId: {} as ResolvedPackagesByPackageId, skipped: wantedToBeSkippedPackageIds, storeController: opts.storeController, + updateMatching: opts.updateMatching, virtualStoreDir: opts.virtualStoreDir, wantedLockfile: opts.wantedLockfile, } diff --git a/packages/resolve-dependencies/src/resolveDependencies.ts b/packages/resolve-dependencies/src/resolveDependencies.ts index dabea1ce55..e7ec034da7 100644 --- a/packages/resolve-dependencies/src/resolveDependencies.ts +++ b/packages/resolve-dependencies/src/resolveDependencies.ts @@ -144,6 +144,7 @@ export interface ResolutionContext { pnpmVersion: string, registries: Registries, virtualStoreDir: string, + updateMatching?: (pkgName: string) => boolean, } export type PkgAddress = { @@ -240,11 +241,17 @@ export default async function resolveDependencies ( .map(async (extendedWantedDep) => { const updateDepth = typeof extendedWantedDep.wantedDependency.updateDepth === 'number' ? extendedWantedDep.wantedDependency.updateDepth : options.updateDepth + const updateShouldContinue = options.currentDepth <= updateDepth + const update = updateShouldContinue && ( + !ctx.updateMatching || + !extendedWantedDep.infoFromLockfile?.dependencyLockfile || + ctx.updateMatching(extendedWantedDep.infoFromLockfile.dependencyLockfile.name ?? extendedWantedDep.wantedDependency.alias) + ) const resolveDependencyOpts: ResolveDependencyOptions = { ...resolveDepOpts, currentPkg: extendedWantedDep.infoFromLockfile ?? undefined, - proceed: extendedWantedDep.proceed, - update: options.currentDepth <= updateDepth, + proceed: extendedWantedDep.proceed || updateShouldContinue, + update, updateDepth, } const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts) @@ -430,13 +437,13 @@ function getInfoFromLockfile ( lockfile: Lockfile, registries: Registries, reference: string | undefined, - pkgName: string | undefined + alias: string | undefined ) { - if (!reference || !pkgName) { + if (!reference || !alias) { return null } - const depPath = dp.refToRelative(reference, pkgName) + const depPath = dp.refToRelative(reference, alias) if (!depPath) { return null @@ -602,6 +609,10 @@ async function resolveDependency ( let useManifestInfoFromLockfile = false let prepare!: boolean let hasBin!: boolean + pkg = ctx.readPackageHook + ? ctx.readPackageHook(pkgResponse.body.manifest || await pkgResponse.bundledManifest!()) + : pkgResponse.body.manifest || await pkgResponse.bundledManifest!() + if ( !options.update && currentPkg.dependencyLockfile && currentPkg.depPath && !pkgResponse.body.updated && @@ -613,16 +624,13 @@ async function resolveDependency ( useManifestInfoFromLockfile = true prepare = currentPkg.dependencyLockfile.prepare === true hasBin = currentPkg.dependencyLockfile.hasBin === true - pkg = Object.assign( - nameVerFromPkgSnapshot(currentPkg.depPath, currentPkg.dependencyLockfile), - currentPkg.dependencyLockfile - ) + pkg = { + ...nameVerFromPkgSnapshot(currentPkg.depPath, currentPkg.dependencyLockfile), + ...currentPkg.dependencyLockfile, + ...pkg, + } } else { // tslint:disable:no-string-literal - pkg = ctx.readPackageHook - ? ctx.readPackageHook(pkgResponse.body.manifest || await pkgResponse.bundledManifest!()) - : pkgResponse.body.manifest || await pkgResponse.bundledManifest!() - prepare = Boolean( pkgResponse.body.resolvedVia === 'git-repository' && typeof pkg.scripts?.prepare === 'string' diff --git a/packages/supi/src/install/extendInstallOptions.ts b/packages/supi/src/install/extendInstallOptions.ts index c0dcf37ce4..aa961b238c 100644 --- a/packages/supi/src/install/extendInstallOptions.ts +++ b/packages/supi/src/install/extendInstallOptions.ts @@ -29,6 +29,7 @@ export interface StrictInstallOptions { reporter: ReporterFunction, force: boolean, update: boolean, + updateMatching?: (pkgName: string) => boolean, updatePackageManifest: boolean, depth: number, lockfileDir: string, diff --git a/packages/supi/src/install/index.ts b/packages/supi/src/install/index.ts index a212d648bc..8cf49d7d61 100644 --- a/packages/supi/src/install/index.ts +++ b/packages/supi/src/install/index.ts @@ -597,10 +597,11 @@ async function installInContext ( || !opts.currentLockfileIsUpToDate || opts.force const _toResolveImporter = toResolveImporter.bind(null, { - defaultUpdateDepth: opts.update ? opts.depth : -1, + defaultUpdateDepth: (opts.update || opts.updateMatching) ? opts.depth : -1, lockfileOnly: opts.lockfileOnly, preferredVersions, storeDir: ctx.storeDir, + updateAll: Boolean(opts.updateMatching), virtualStoreDir: ctx.virtualStoreDir, workspacePackages: opts.workspacePackages, }) @@ -627,6 +628,7 @@ async function installInContext ( registries: opts.registries, storeController: opts.storeController, tag: opts.tag, + updateMatching: opts.updateMatching, virtualStoreDir: ctx.virtualStoreDir, wantedLockfile: ctx.wantedLockfile, workspacePackages: opts.workspacePackages, @@ -839,6 +841,7 @@ async function toResolveImporter ( lockfileOnly: boolean, preferredVersions?: PreferredVersions, storeDir: string, + updateAll: boolean, virtualStoreDir: string, workspacePackages: WorkspacePackages, }, @@ -870,7 +873,8 @@ async function toResolveImporter ( // so their update depth should be at least 0 const updateLocalTarballs = (dep: WantedDependency) => ({ ...dep, - updateDepth: prefIsLocalTarball(dep.pref) ? 0 : -1, + updateDepth: opts.updateAll ? + opts.defaultUpdateDepth : (prefIsLocalTarball(dep.pref) ? 0 : -1), }) wantedDependencies = [ ...project.wantedDependencies.map( diff --git a/packages/supi/test/install/misc.ts b/packages/supi/test/install/misc.ts index 51bb616127..63b8d1a42a 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', - 'is-positive', + 'qar', ] ) }) diff --git a/packages/supi/test/install/update.ts b/packages/supi/test/install/update.ts index 72be69c0c8..474906c275 100644 --- a/packages/supi/test/install/update.ts +++ b/packages/supi/test/install/update.ts @@ -180,3 +180,43 @@ test('update only the packages that were requested to be updated when hoisting i const lockfile = await project.readLockfile() t.deepEqual(Object.keys(lockfile.packages), ['/bar/100.0.0', '/foo/100.1.0']) }) + +test('update only the specified package', 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()) + + 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({ + depth: Infinity, + update: true, + updateMatching: (pkgName: string) => pkgName === 'foo', + })) + + 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.0']) + t.deepEqual(lockfile.packages['/foobarqar/1.0.0'].dependencies, { + bar: '100.0.0', + foo: '100.1.0', + 'is-positive': '3.1.0', + }) +})