From e0918712aed81d586ed18df60fac2f28bf62d398 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Fri, 28 Feb 2025 01:51:58 +0100 Subject: [PATCH] fix: self-update should never install a brokne pnpm CLI (#9194) --- .changeset/beige-comics-taste.md | 6 +++ pnpm-lock.yaml | 9 ++++ .../plugin-commands-self-updater/package.json | 3 ++ .../src/selfUpdate.ts | 54 +++++++++++++------ 4 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 .changeset/beige-comics-taste.md diff --git a/.changeset/beige-comics-taste.md b/.changeset/beige-comics-taste.md new file mode 100644 index 0000000000..ec92b9c3ad --- /dev/null +++ b/.changeset/beige-comics-taste.md @@ -0,0 +1,6 @@ +--- +"@pnpm/tools.plugin-commands-self-updater": patch +"pnpm": patch +--- + +`pnpm self-update` should not leave a directory with a broken pnpm installation if the installation fails. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70331a2f77..e056021d43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7810,9 +7810,18 @@ importers: '@pnpm/tools.path': specifier: workspace:* version: link:../path + '@zkochan/rimraf': + specifier: 'catalog:' + version: 3.0.2 + path-temp: + specifier: 'catalog:' + version: 2.1.0 ramda: specifier: 'catalog:' version: '@pnpm/ramda@0.28.1' + rename-overwrite: + specifier: 'catalog:' + version: 6.0.2 render-help: specifier: 'catalog:' version: 1.0.3 diff --git a/tools/plugin-commands-self-updater/package.json b/tools/plugin-commands-self-updater/package.json index 87613862e9..60127606a6 100644 --- a/tools/plugin-commands-self-updater/package.json +++ b/tools/plugin-commands-self-updater/package.json @@ -39,7 +39,10 @@ "@pnpm/plugin-commands-installation": "workspace:*", "@pnpm/read-project-manifest": "workspace:*", "@pnpm/tools.path": "workspace:*", + "@zkochan/rimraf": "catalog:", + "path-temp": "catalog:", "ramda": "catalog:", + "rename-overwrite": "catalog:", "render-help": "catalog:" }, "devDependencies": { diff --git a/tools/plugin-commands-self-updater/src/selfUpdate.ts b/tools/plugin-commands-self-updater/src/selfUpdate.ts index 6f774c98c6..ae6bd05a30 100644 --- a/tools/plugin-commands-self-updater/src/selfUpdate.ts +++ b/tools/plugin-commands-self-updater/src/selfUpdate.ts @@ -11,7 +11,10 @@ import { add, type InstallCommandOptions } from '@pnpm/plugin-commands-installat import { readProjectManifest } from '@pnpm/read-project-manifest' import { getToolDirPath } from '@pnpm/tools.path' import { linkBins } from '@pnpm/link-bins' +import { sync as rimraf } from '@zkochan/rimraf' +import { fastPathTemp as pathTemp } from 'path-temp' import pick from 'ramda/src/pick' +import renameOverwrite from 'rename-overwrite' import renderHelp from 'render-help' export function rcOptionsTypes (): Record { @@ -80,24 +83,41 @@ export async function handler ( version: resolution.manifest.version, }, }) - if (fs.existsSync(dir)) { - await linkBins(path.join(dir, opts.modulesDir ?? 'node_modules'), opts.pnpmHomeDir, - { - warn: globalWarn, - } - ) - return `The ${pref} version, v${resolution.manifest.version}, is already present on the system. It was activated by linking it from ${dir}.` + const alreadyExists = fs.existsSync(dir) + if (!alreadyExists) { + const stage = pathTemp(dir) + fs.mkdirSync(stage, { recursive: true }) + fs.writeFileSync(path.join(stage, 'package.json'), '{}') + try { + await add.handler( + { + ...opts, + dir: stage, + lockfileDir: stage, + // We want to avoid symlinks because of the rename step, + // which breaks the junctions on Windows. + nodeLinker: 'hoisted', + // This won't be used but there is currently no way to skip the bin creation + // and we can't create the bin shims in the pnpm home directory + // because the stage directory will be renamed. + bin: path.join(stage, 'node_modules/.bin'), + }, + [`${currentPkgName}@${resolution.manifest.version}`] + ) + renameOverwrite.sync(stage, dir) + } catch (err: unknown) { + try { + rimraf(stage) + } catch {} // eslint-disable-line:no-empty + throw err + } } - fs.mkdirSync(dir, { recursive: true }) - fs.writeFileSync(path.join(dir, 'package.json'), '{}') - await add.handler( + await linkBins(path.join(dir, opts.modulesDir ?? 'node_modules'), opts.pnpmHomeDir, { - ...opts, - dir, - lockfileDir: dir, - bin: opts.pnpmHomeDir, - }, - [`${currentPkgName}@${resolution.manifest.version}`] + warn: globalWarn, + } ) - return undefined + return alreadyExists + ? `The ${pref} version, v${resolution.manifest.version}, is already present on the system. It was activated by linking it from ${dir}.` + : undefined }