diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b093c8032..34139840eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8660,6 +8660,9 @@ importers: render-help: specifier: 'catalog:' version: 1.0.3 + semver: + specifier: 'catalog:' + version: 7.7.4 symlink-dir: specifier: 'catalog:' version: 6.0.5 @@ -8682,6 +8685,9 @@ importers: '@types/ramda': specifier: 'catalog:' version: 0.29.12 + '@types/semver': + specifier: 'catalog:' + version: 7.5.3 cross-spawn: specifier: 'catalog:' version: 7.0.6 diff --git a/tools/plugin-commands-self-updater/package.json b/tools/plugin-commands-self-updater/package.json index 3610a8fed3..da767f94a5 100644 --- a/tools/plugin-commands-self-updater/package.json +++ b/tools/plugin-commands-self-updater/package.json @@ -44,6 +44,7 @@ "path-temp": "catalog:", "ramda": "catalog:", "render-help": "catalog:", + "semver": "catalog:", "symlink-dir": "catalog:" }, "peerDependencies": { @@ -56,6 +57,7 @@ "@pnpm/tools.plugin-commands-self-updater": "workspace:*", "@types/cross-spawn": "catalog:", "@types/ramda": "catalog:", + "@types/semver": "catalog:", "cross-spawn": "catalog:", "nock": "catalog:" }, diff --git a/tools/plugin-commands-self-updater/src/selfUpdate.ts b/tools/plugin-commands-self-updater/src/selfUpdate.ts index 06e07966f7..0610c3d486 100644 --- a/tools/plugin-commands-self-updater/src/selfUpdate.ts +++ b/tools/plugin-commands-self-updater/src/selfUpdate.ts @@ -9,6 +9,7 @@ import { readProjectManifest } from '@pnpm/read-project-manifest' import { linkBins } from '@pnpm/link-bins' import pick from 'ramda/src/pick' import renderHelp from 'render-help' +import semver from 'semver' import { installPnpmToTools } from './installPnpmToTools.js' export function rcOptionsTypes (): Record { @@ -23,6 +24,15 @@ export function cliOptionsTypes (): Record { export const commandNames = ['self-update'] +// Migration guidance printed once when `pnpm self-update` crosses a major +// boundary. Add an entry here for each future major that ships breaking +// changes users need to act on. +const MAJOR_UPGRADE_HINTS: Record = { + 11: + 'pnpm v11 removed or renamed several v10 settings. ' + + 'See https://pnpm.io/11.x/migration for migration instructions.', +} + export function help (): string { return renderHelp({ description: 'Updates pnpm to the latest version (or the one specified)', @@ -69,6 +79,30 @@ export async function handler ( throw new PnpmError('CANNOT_RESOLVE_PNPM', `Cannot find "${bareSpecifier}" version of pnpm`) } + // Determine the "previous" pnpm version being upgraded FROM. If the + // project pins pnpm via `packageManager`, the pin is the source of + // truth — the running pnpm binary may already be at a newer major + // (e.g. a globally-installed v11 operating on a project still pinned + // to v10). Otherwise fall back to the running binary. Skip the hint + // entirely on a no-op (target === previous). + const targetVersion = resolution.manifest.version + let previousVersion: string | undefined + if (opts.wantedPackageManager?.name === packageManager.name && opts.managePackageManagerVersions) { + if (opts.wantedPackageManager.version !== targetVersion) { + previousVersion = opts.wantedPackageManager.version + } + } else if (packageManager.version !== targetVersion) { + previousVersion = packageManager.version + } + const previousMajor = previousVersion != null + ? semver.coerce(previousVersion)?.major + : undefined + const targetMajor = semver.major(targetVersion) + if (previousMajor != null && targetMajor > previousMajor) { + const hint = MAJOR_UPGRADE_HINTS[targetMajor] + if (hint) globalWarn(hint) + } + if (opts.wantedPackageManager?.name === packageManager.name && opts.managePackageManagerVersions) { if (opts.wantedPackageManager?.version !== resolution.manifest.version) { const { manifest, writeProjectManifest } = await readProjectManifest(opts.rootProjectManifestDir)