mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-22 21:53:02 -04:00
fix(self-update): refresh legacy v10 bootstrap shim at PNPM_HOME (#11467)
pnpm v10 setup added PNPM_HOME (not PNPM_HOME/bin) to PATH and wrote a pnpm bootstrap shim there. After upgrading to v11, that shim still points into the old .tools/<version> install, so PATH continues to resolve `pnpm` to the pre-update version even though the new version was installed under global/v11. Detect that layout during self-update, refresh the shims at PNPM_HOME so the upgrade actually takes effect, and warn the user to run `pnpm setup` for a clean migration to the v11 PATH layout. Closes #11464.
This commit is contained in:
6
.changeset/self-update-refresh-v10-shim.md
Normal file
6
.changeset/self-update-refresh-v10-shim.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/engine.pm.commands": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed `pnpm self-update` on installations originally set up by pnpm v10. v10 added `PNPM_HOME` directly to PATH and wrote a `pnpm` bootstrap shim there. v11 setup writes shims under `PNPM_HOME/bin` instead, so when a v10 user upgrades to v11 the legacy shim at `PNPM_HOME` keeps pointing into the old `.tools/<version>` install — `pnpm --version` continues to report the pre-update version even though the new version was installed under `global/v11`. Self-update now detects this layout, refreshes the legacy shims so the upgrade actually takes effect, and prints a hint suggesting `pnpm setup` to migrate PATH to the v11 layout. [#11464](https://github.com/pnpm/pnpm/issues/11464).
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { linkBins } from '@pnpm/bins.linker'
|
||||
@@ -206,12 +207,39 @@ export async function handler (
|
||||
// Link bins to pnpmHomeDir/bin so the updated pnpm is the active global binary
|
||||
await linkBins(path.join(baseDir, 'node_modules'), path.join(opts.pnpmHomeDir, 'bin'), { warn: globalWarn })
|
||||
|
||||
// pnpm v10 setup linked bins directly into pnpmHomeDir and added that
|
||||
// directory to PATH (instead of pnpmHomeDir/bin as v11 does). When a v10
|
||||
// user upgrades to v11 the legacy shims at pnpmHomeDir keep pointing into
|
||||
// the old `.tools/<version>` install — so PATH still resolves `pnpm` to the
|
||||
// pre-update version. Detect that case and refresh the legacy shims so the
|
||||
// upgrade actually takes effect, then warn the user to run `pnpm setup`
|
||||
// for a clean migration to the v11 layout. See pnpm/pnpm#11464.
|
||||
if (hasLegacyHomeDirShim(opts.pnpmHomeDir)) {
|
||||
await linkBins(path.join(baseDir, 'node_modules'), opts.pnpmHomeDir, { warn: globalWarn })
|
||||
globalWarn(
|
||||
'Detected a pnpm v10 installation layout at PNPM_HOME. The pnpm shims ' +
|
||||
'at PNPM_HOME have been refreshed so the new version is active, but ' +
|
||||
'pnpm v11 expects bins in PNPM_HOME/bin. Run "pnpm setup" to migrate ' +
|
||||
'your PATH to the v11 layout.'
|
||||
)
|
||||
}
|
||||
|
||||
if (alreadyExisted) {
|
||||
return `The ${bareSpecifier} version, v${resolution.manifest.version}, is already present on the system. It was activated by linking it from ${baseDir}.`
|
||||
}
|
||||
return `Successfully updated pnpm to v${resolution.manifest.version}`
|
||||
}
|
||||
|
||||
// A fresh v11 setup never writes a `pnpm` shim at pnpmHomeDir itself — only
|
||||
// under pnpmHomeDir/bin. The presence of a `pnpm` (or `pnpm.cmd`) file
|
||||
// directly at pnpmHomeDir is therefore a reliable v10-layout marker.
|
||||
function hasLegacyHomeDirShim (pnpmHomeDir: string): boolean {
|
||||
for (const name of ['pnpm', 'pnpm.cmd']) {
|
||||
if (fs.existsSync(path.join(pnpmHomeDir, name))) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the updated version constraint for devEngines.packageManager.
|
||||
* - Exact versions and simple ranges (^, ~) are updated to the new version,
|
||||
|
||||
@@ -169,6 +169,50 @@ test('self-update', async () => {
|
||||
expect(stdout.toString().trim()).toBe('9.1.0')
|
||||
})
|
||||
|
||||
test('self-update refreshes legacy v10 bootstrap shim at pnpmHomeDir', async () => {
|
||||
// pnpm v10 setup added pnpmHomeDir (not pnpmHomeDir/bin) to PATH and wrote
|
||||
// a `pnpm` bootstrap shim there. After upgrading to v11, that shim still
|
||||
// points into the old `.tools/<version>` install, so PATH continues to
|
||||
// resolve to the pre-update pnpm. Self-update on v11 must refresh the
|
||||
// legacy shim so the upgrade actually takes effect for users still on the
|
||||
// v10 PATH layout. See pnpm/pnpm#11464.
|
||||
const opts = prepare()
|
||||
// Simulate a leftover v10 bootstrap shim. Content is irrelevant — the
|
||||
// detector only cares about file presence, and linkBins will overwrite it.
|
||||
fs.writeFileSync(path.join(opts.pnpmHomeDir, 'pnpm'), '#!/bin/sh\necho stale\n', { mode: 0o755 })
|
||||
if (process.platform === 'win32') {
|
||||
fs.writeFileSync(path.join(opts.pnpmHomeDir, 'pnpm.cmd'), '@echo stale\n')
|
||||
}
|
||||
mockRegistryForUpdate(opts.registries.default, '9.1.0', createMetadata('9.1.0', opts.registries.default))
|
||||
|
||||
await selfUpdate.handler(opts, [])
|
||||
|
||||
// Invoking pnpm via pnpmHomeDir (the v10 PATH layout, NOT pnpmHomeDir/bin)
|
||||
// must now resolve to the freshly installed version.
|
||||
const pnpmEnv = prependDirsToPath([opts.pnpmHomeDir])
|
||||
const { status, stdout } = spawn.sync('pnpm', ['-v'], {
|
||||
env: {
|
||||
...process.env,
|
||||
[pnpmEnv.name]: pnpmEnv.value,
|
||||
},
|
||||
})
|
||||
expect(status).toBe(0)
|
||||
expect(stdout.toString().trim()).toBe('9.1.0')
|
||||
})
|
||||
|
||||
test('self-update does not write shims to pnpmHomeDir on a clean v11 layout', async () => {
|
||||
// Mirror image of the previous test: when there is no v10-style shim at
|
||||
// pnpmHomeDir, self-update must NOT start writing bins there. Otherwise we
|
||||
// would clutter pnpmHomeDir on every fresh-v11 self-update.
|
||||
const opts = prepare()
|
||||
mockRegistryForUpdate(opts.registries.default, '9.1.0', createMetadata('9.1.0', opts.registries.default))
|
||||
|
||||
await selfUpdate.handler(opts, [])
|
||||
|
||||
expect(fs.existsSync(path.join(opts.pnpmHomeDir, 'pnpm'))).toBe(false)
|
||||
expect(fs.existsSync(path.join(opts.pnpmHomeDir, 'pnpm.cmd'))).toBe(false)
|
||||
})
|
||||
|
||||
test('self-update by exact version', async () => {
|
||||
const opts = prepare()
|
||||
const metadata = createMetadata('9.2.0', opts.registries.default, ['9.1.0'])
|
||||
|
||||
Reference in New Issue
Block a user