Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: do not allow to install pnpm globally via pnpm add #8728

Merged
merged 2 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/tricky-crabs-yell.md
Original file line number Diff line number Diff line change
@@ -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`.
13 changes: 9 additions & 4 deletions pkg-manager/plugin-commands-installation/src/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
40 changes: 40 additions & 0 deletions pkg-manager/plugin-commands-installation/test/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions pnpm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:",
Expand Down Expand Up @@ -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:"
Expand Down
18 changes: 2 additions & 16 deletions pnpm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -170,21 +169,8 @@ export async function main (inputArgv: string[]): Promise<void> {
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 (
Expand Down Expand Up @@ -261,7 +247,7 @@ export async function main (inputArgv: string[]): Promise<void> {
if (
config.updateNotifier !== false &&
!isCI &&
!selfUpdate &&
cmd !== 'self-update' &&
!config.offline &&
!config.preferOffline &&
!config.fallbackCommandUsed &&
Expand Down
2 changes: 1 addition & 1 deletion pnpm/test/install/selfUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down