diff --git a/.changeset/tricky-crabs-yell.md b/.changeset/tricky-crabs-yell.md new file mode 100644 index 00000000000..7693d858cab --- /dev/null +++ b/.changeset/tricky-crabs-yell.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-installation": major +"pnpm": major +--- + +`pnpm add --global pnpm` or (`pnpm add --global @pnpm/exe`) fails with an error suggesting to use `pnpm self-update`. diff --git a/pkg-manager/plugin-commands-installation/src/add.ts b/pkg-manager/plugin-commands-installation/src/add.ts index ef799203f08..20301e34ad8 100644 --- a/pkg-manager/plugin-commands-installation/src/add.ts +++ b/pkg-manager/plugin-commands-installation/src/add.ts @@ -193,10 +193,15 @@ export async function handler ( 'If you don\'t want to see this warning anymore, you may set the ignore-workspace-root-check setting to true.' ) } - if (opts.global && !opts.bin) { - throw new PnpmError('NO_GLOBAL_BIN_DIR', 'Unable to find the global bin directory', { - hint: 'Run "pnpm setup" to create it automatically, or set the global-bin-dir setting, or the PNPM_HOME env variable. The global bin directory should be in the PATH.', - }) + if (opts.global) { + if (!opts.bin) { + throw new PnpmError('NO_GLOBAL_BIN_DIR', 'Unable to find the global bin directory', { + hint: 'Run "pnpm setup" to create it automatically, or set the global-bin-dir setting, or the PNPM_HOME env variable. The global bin directory should be in the PATH.', + }) + } + if (params.includes('pnpm') || params.includes('@pnpm/exe')) { + throw new PnpmError('GLOBAL_PNPM_INSTALL', 'Use the "pnpm self-update" command to install or update pnpm') + } } const include = { diff --git a/pkg-manager/plugin-commands-installation/test/add.ts b/pkg-manager/plugin-commands-installation/test/add.ts index 8a83793cc3c..817efd390a8 100644 --- a/pkg-manager/plugin-commands-installation/test/add.ts +++ b/pkg-manager/plugin-commands-installation/test/add.ts @@ -353,3 +353,43 @@ test('add: fail when global bin directory is not found', async () => { } expect(err.code).toBe('ERR_PNPM_NO_GLOBAL_BIN_DIR') }) + +test('add: fail trying to install pnpm', async () => { + prepareEmpty() + + let err!: PnpmError + try { + await add.handler({ + ...DEFAULT_OPTIONS, + bin: path.resolve('project/bin'), + dir: path.resolve('project'), + global: true, + linkWorkspacePackages: false, + saveWorkspaceProtocol: false, + workspace: false, + }, ['pnpm']) + } catch (_err: any) { // eslint-disable-line + err = _err + } + expect(err.code).toBe('ERR_PNPM_GLOBAL_PNPM_INSTALL') +}) + +test('add: fail trying to install @pnpm/exe', async () => { + prepareEmpty() + + let err!: PnpmError + try { + await add.handler({ + ...DEFAULT_OPTIONS, + bin: path.resolve('project/bin'), + dir: path.resolve('project'), + global: true, + linkWorkspacePackages: false, + saveWorkspaceProtocol: false, + workspace: false, + }, ['@pnpm/exe']) + } catch (_err: any) { // eslint-disable-line + err = _err + } + expect(err.code).toBe('ERR_PNPM_GLOBAL_PNPM_INSTALL') +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d3b3775787..c036e1dfe63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5796,9 +5796,6 @@ importers: '@types/semver': specifier: 'catalog:' version: 7.5.3 - '@types/which': - specifier: 'catalog:' - version: 2.0.2 '@zkochan/retry': specifier: 'catalog:' version: 0.2.0 @@ -5886,9 +5883,6 @@ importers: tree-kill: specifier: 'catalog:' version: 1.2.2 - which: - specifier: 'catalog:' - version: '@pnpm/which@3.0.1' write-json-file: specifier: 'catalog:' version: 4.3.0 diff --git a/pnpm/package.json b/pnpm/package.json index c999d2d1653..2f6f572257d 100644 --- a/pnpm/package.json +++ b/pnpm/package.json @@ -86,7 +86,6 @@ "@types/pnpm__byline": "catalog:", "@types/ramda": "catalog:", "@types/semver": "catalog:", - "@types/which": "catalog:", "@zkochan/retry": "catalog:", "@zkochan/rimraf": "catalog:", "chalk": "catalog:", @@ -116,7 +115,6 @@ "symlink-dir": "catalog:", "tempy": "catalog:", "tree-kill": "catalog:", - "which": "catalog:", "write-json-file": "catalog:", "write-pkg": "catalog:", "write-yaml-file": "catalog:" diff --git a/pnpm/src/main.ts b/pnpm/src/main.ts index 29ab69c34ff..e8f1397ae8e 100644 --- a/pnpm/src/main.ts +++ b/pnpm/src/main.ts @@ -23,7 +23,6 @@ import { isCI } from 'ci-info' import path from 'path' import isEmpty from 'ramda/src/isEmpty' import stripAnsi from 'strip-ansi' -import which from 'which' import { checkForUpdates } from './checkForUpdates' import { pnpmCmds, rcOptionsTypes } from './cmd' import { formatUnknownOptionsError } from './formatError' @@ -170,21 +169,8 @@ export async function main (inputArgv: string[]): Promise { global[REPORTER_INITIALIZED] = reporterType } - const selfUpdate = config.global && (cmd === 'add' || cmd === 'update') && cliParams.includes(packageManager.name) - - if (selfUpdate) { + if (cmd === 'self-update') { await pnpmCmds.server(config as any, ['stop']) // eslint-disable-line @typescript-eslint/no-explicit-any - try { - const currentPnpmDir = path.dirname(which.sync('pnpm')) - if (path.relative(currentPnpmDir, config.bin) !== '') { - console.log(`The location of the currently running pnpm differs from the location where pnpm will be installed - Current pnpm location: ${currentPnpmDir} - Target location: ${config.bin} -`) - } - } catch { - // if pnpm not found, then ignore - } } if ( @@ -261,7 +247,7 @@ export async function main (inputArgv: string[]): Promise { if ( config.updateNotifier !== false && !isCI && - !selfUpdate && + cmd !== 'self-update' && !config.offline && !config.preferOffline && !config.fallbackCommandUsed && diff --git a/pnpm/test/install/selfUpdate.ts b/pnpm/test/install/selfUpdate.ts index 38017e9bc6a..3c6e7be2ec4 100644 --- a/pnpm/test/install/selfUpdate.ts +++ b/pnpm/test/install/selfUpdate.ts @@ -30,7 +30,7 @@ skipOnWindows('self-update stops the store server', async () => { XDG_DATA_HOME: path.resolve('data'), } - await execPnpm(['install', '-g', 'pnpm', '--store-dir', path.resolve('..', 'store'), '--reporter=append-only'], { env }) + await execPnpm(['self-update', `--config.store-dir=${path.resolve('..', 'store')}`, '--reporter=append-only'], { env }) expect(fs.existsSync(serverJsonPath)).toBeFalsy() project.isExecutable('../pnpm')