fix: store global binaries in PNPM_HOME/bin subdirectory (#11038)

Previously, globally installed binaries were placed directly in
PNPM_HOME, which also contains internal directories (global/, store/).
This polluted shell autocompletion with non-executable entries.

Now binaries are stored in PNPM_HOME/bin, keeping the PATH clean.

Closes #10986
This commit is contained in:
Zoltan Kochan
2026-03-20 18:16:52 +01:00
committed by GitHub
parent 0407e36ab2
commit f0ae1b97d7
14 changed files with 78 additions and 61 deletions

View File

@@ -0,0 +1,9 @@
---
"@pnpm/config.reader": minor
"@pnpm/engine.pm.commands": minor
"pnpm": minor
---
Store globally installed binaries in a `bin` subdirectory of `PNPM_HOME` instead of directly in `PNPM_HOME`. This prevents internal directories like `global/` and `store/` from polluting shell autocompletion when `PNPM_HOME` is on PATH [#10986](https://github.com/pnpm/pnpm/issues/10986).
After upgrading, run `pnpm setup` to update your shell configuration.

View File

@@ -15,10 +15,12 @@ export async function checkGlobalBinDir (
`Couldn't find a global directory for executables because the "${PATH}" environment variable is not set.`)
}
if (!await globalBinDirIsInPath(globalBinDir, env)) {
throw new PnpmError('GLOBAL_BIN_DIR_NOT_IN_PATH', `The configured global bin directory "${globalBinDir}" is not in PATH`)
throw new PnpmError('GLOBAL_BIN_DIR_NOT_IN_PATH', `The configured global bin directory "${globalBinDir}" is not in PATH`, {
hint: 'Run "pnpm setup" to update your shell configuration.',
})
}
if (shouldAllowWrite && !canWriteToDirAndExists(globalBinDir)) {
throw new PnpmError('PNPM_DIR_NOT_WRITABLE', `The CLI has no write access to the pnpm home directory at ${globalBinDir}`)
throw new PnpmError('PNPM_DIR_NOT_WRITABLE', `The CLI has no write access to the global bin directory at ${globalBinDir}`)
}
}

View File

@@ -324,7 +324,7 @@ export async function getConfig (opts: {
pnpmConfig.authInfos = networkConfigs.authInfos ?? {} // TODO: remove `?? {}` (when possible)
pnpmConfig.sslConfigs = networkConfigs.sslConfigs
Object.assign(pnpmConfig, getDefaultAuthInfo(pnpmConfig.rawConfig))
pnpmConfig.pnpmHomeDir = getDataDir(process)
pnpmConfig.pnpmHomeDir = getDataDir({ env, platform: process.platform })
let globalDirRoot
if (pnpmConfig.globalDir) {
globalDirRoot = pnpmConfig.globalDir
@@ -335,7 +335,7 @@ export async function getConfig (opts: {
pnpmConfig.dir = cwd
if (cliOptions['global']) {
delete pnpmConfig.workspaceDir
pnpmConfig.bin = npmConfig.get('global-bin-dir') ?? env.PNPM_HOME
pnpmConfig.bin = npmConfig.get('global-bin-dir') ?? path.join(pnpmConfig.pnpmHomeDir, 'bin')
if (pnpmConfig.bin) {
fs.mkdirSync(pnpmConfig.bin, { recursive: true })
await checkGlobalBinDir(pnpmConfig.bin, { env, shouldAllowWrite: opts.globalDirShouldAllowWrite })

View File

@@ -34,7 +34,7 @@ for (const suffix of [
const env = {
PNPM_HOME: import.meta.dirname,
[PATH]: import.meta.dirname,
[PATH]: path.join(import.meta.dirname, 'bin'),
}
const f = fixtures(import.meta.dirname)

View File

@@ -132,8 +132,8 @@ export async function handler (
storeDir: store.dir,
})
// Link bins to pnpmHomeDir so the updated pnpm is the active global binary
await linkBins(path.join(baseDir, 'node_modules'), opts.pnpmHomeDir, { warn: globalWarn })
// 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 })
if (alreadyExisted) {
return `The ${bareSpecifier} version, v${resolution.manifest.version}, is already present on the system. It was activated by linking it from ${baseDir}.`

View File

@@ -135,14 +135,16 @@ export async function handler (
}
): Promise<string> {
const execPath = getExecPath()
const binDir = path.join(opts.pnpmHomeDir, 'bin')
if (execPath.match(/\.[cm]?js$/) == null) {
installCliGlobally(execPath, opts.pnpmHomeDir)
createPnpxScripts(opts.pnpmHomeDir)
createPnpxScripts(binDir)
}
try {
const report = await addDirToEnvPath(opts.pnpmHomeDir, {
configSectionName: 'pnpm',
proxyVarName: 'PNPM_HOME',
proxyVarSubDir: 'bin',
overwrite: opts.force,
position: 'start',
})

View File

@@ -56,7 +56,7 @@ function prepareOptions (dir: string) {
rawLocalConfig: {},
sort: false,
rootProjectManifestDir: dir,
bin: dir,
bin: path.join(dir, 'bin'),
workspaceConcurrency: 1,
extraEnv: {},
pnpmfile: '',
@@ -147,7 +147,7 @@ test('self-update', async () => {
const pnpmPkgJson = JSON.parse(fs.readFileSync(path.join(globalDir, installDirName!, 'node_modules/pnpm/package.json'), 'utf8'))
expect(pnpmPkgJson.version).toBe('9.1.0')
const pnpmEnv = prependDirsToPath([opts.pnpmHomeDir])
const pnpmEnv = prependDirsToPath([path.join(opts.pnpmHomeDir, 'bin')])
const { status, stdout } = spawn.sync('pnpm', ['-v'], {
env: {
...process.env,
@@ -180,7 +180,7 @@ test('self-update by exact version', async () => {
const pnpmPkgJson = JSON.parse(fs.readFileSync(path.join(globalDir, installDirName!, 'node_modules/pnpm/package.json'), 'utf8'))
expect(pnpmPkgJson.version).toBe('9.1.0')
const pnpmEnv = prependDirsToPath([opts.pnpmHomeDir])
const pnpmEnv = prependDirsToPath([path.join(opts.pnpmHomeDir, 'bin')])
const { status, stdout } = spawn.sync('pnpm', ['-v'], {
env: {
...process.env,
@@ -418,7 +418,7 @@ console.log('9.2.0')`, 'utf8')
expect(output).toBe(`The latest version, v9.2.0, is already present on the system. It was activated by linking it from ${installDir}.`)
const pnpmEnv = prependDirsToPath([opts.pnpmHomeDir])
const pnpmEnv = prependDirsToPath([path.join(opts.pnpmHomeDir, 'bin')])
const { status, stdout } = spawn.sync('pnpm', ['-v'], {
env: {
...process.env,
@@ -438,7 +438,7 @@ test('self-update works globally without package.json', async () => {
...prepareOptions(dir),
globalPkgDir: path.join(pnpmHomeDir, 'global', 'v11'),
pnpmHomeDir,
bin: pnpmHomeDir,
bin: path.join(pnpmHomeDir, 'bin'),
}
mockRegistryForUpdate(opts.registries.default, '9.1.0', createMetadata('9.1.0', opts.registries.default))
@@ -457,7 +457,7 @@ test('self-update works globally without package.json', async () => {
expect(globalInstallDir).toBeDefined()
expect(fs.existsSync(path.join(globalDir, globalInstallDir!, 'node_modules', 'pnpm', 'package.json'))).toBe(true)
const pnpmEnv = prependDirsToPath([pnpmHomeDir])
const pnpmEnv = prependDirsToPath([path.join(pnpmHomeDir, 'bin')])
const { status, stdout } = spawn.sync('pnpm', ['-v'], {
env: {
...process.env,

View File

@@ -1,7 +1,7 @@
import path from 'node:path'
export function getNodeExecPathInBinDir (pnpmHomeDir: string): string {
return path.resolve(pnpmHomeDir, process.platform === 'win32' ? 'node.exe' : 'node')
return path.resolve(pnpmHomeDir, 'bin', process.platform === 'win32' ? 'node.exe' : 'node')
}
export function getNodeExecPathInNodeDir (nodeDir: string): string {

35
pnpm-lock.yaml generated
View File

@@ -257,8 +257,8 @@ catalogs:
specifier: ^2.0.0
version: 2.0.0
'@pnpm/os.env.path-extender':
specifier: ^2.0.3
version: 2.0.3
specifier: ^3.0.0
version: 3.0.0
'@pnpm/patch-package':
specifier: 0.0.1
version: 0.0.1
@@ -3499,7 +3499,7 @@ importers:
version: link:../../../lockfile/types
'@pnpm/os.env.path-extender':
specifier: 'catalog:'
version: 2.0.3
version: 3.0.0
'@pnpm/resolving.npm-resolver':
specifier: workspace:*
version: link:../../../resolving/npm-resolver
@@ -10528,17 +10528,17 @@ packages:
resolution: {integrity: sha512-YTJCXyUGOrJuj4QqhSKqZa1vlVAm82h1/uw00ZmD/kL2OViggtyUwWyIe62kpwWVPwEYixfGjfvaFKVJy2mjzA==}
engines: {node: '>=18.12'}
'@pnpm/os.env.path-extender-posix@2.1.0':
resolution: {integrity: sha512-oE0WCU2GCdOS/ChBi0dAFkktpxe0EPvCswP6+15yaHuydrCBTAaoB9cqSQaQCYrrDbabhn3kSxTex2T5xhq9fg==}
engines: {node: '>=18.12'}
'@pnpm/os.env.path-extender-posix@3.0.0':
resolution: {integrity: sha512-541CmvX6CPc/YkLPEiqxXRj9dEd4Bd/LVeL4WZ4WZQaK/R/Rld2pxljoEHtx6bI91z2p+esG2OFOvnaHVyYr2A==}
engines: {node: '>=22.13'}
'@pnpm/os.env.path-extender-windows@2.0.3':
resolution: {integrity: sha512-zJlGIoRLNpZh9xhCgC6nTvqvXzw1wKhiH3TuroJQ0xHxDwVxk7kAbF4wSROBcVcdOnK4zlxsA1Vc0jAPjxInog==}
engines: {node: '>=18.12'}
'@pnpm/os.env.path-extender-windows@3.0.0':
resolution: {integrity: sha512-P2z4D6TnCTfe0CL+p2lgtkOovbJbmb4t5xf1GJ/s3Lptf68HGhqbY5E3z1tg7vT9Hs4WN8kL6fbS+0378VOdYw==}
engines: {node: '>=22.13'}
'@pnpm/os.env.path-extender@2.0.3':
resolution: {integrity: sha512-Tnrcwi0kGBaMPT9amiWg2ANbdIWMxVJS/d62lNMHbWX3erY2MAG9Y7Si46qdshrYlOzeqHX/lrW5AjlgF/zNeQ==}
engines: {node: '>=18.12'}
'@pnpm/os.env.path-extender@3.0.0':
resolution: {integrity: sha512-gNA3d1w8m7JlyXfjurpqZ+SwWq91JVhrUcsNIGh64r/4Nxnq3o+yxUQyxyrBFlL8luW6OcL0cFfYdAkv19n78Q==}
engines: {node: '>=22.13'}
'@pnpm/package-bins@1000.0.17':
resolution: {integrity: sha512-DD2XTIIWwN1xVRdocKRaIl4eSMp3EdhhxqogfEwEiMbmGkoE4lbnTg3f81VEfhOGl1vTaS5UoM2qxW1FGrpaZQ==}
@@ -18489,20 +18489,21 @@ snapshots:
'@pnpm/util.lex-comparator': 3.0.2
sort-keys: 4.2.0
'@pnpm/os.env.path-extender-posix@2.1.0':
'@pnpm/os.env.path-extender-posix@3.0.0':
dependencies:
'@pnpm/error': 1000.0.5
'@pnpm/os.env.path-extender-windows@2.0.3':
'@pnpm/os.env.path-extender-windows@3.0.0':
dependencies:
'@pnpm/error': 1000.0.5
safe-execa: 0.1.4
string.prototype.matchall: 4.0.12
'@pnpm/os.env.path-extender@2.0.3':
'@pnpm/os.env.path-extender@3.0.0':
dependencies:
'@pnpm/os.env.path-extender-posix': 2.1.0
'@pnpm/os.env.path-extender-windows': 2.0.3
'@pnpm/error': 1000.0.5
'@pnpm/os.env.path-extender-posix': 3.0.0
'@pnpm/os.env.path-extender-windows': 3.0.0
'@pnpm/package-bins@1000.0.17':
dependencies:

View File

@@ -90,7 +90,7 @@ catalog:
'@pnpm/npm-conf': 3.0.2
'@pnpm/npm-lifecycle': ^1001.0.0
'@pnpm/npm-package-arg': ^2.0.0
'@pnpm/os.env.path-extender': ^2.0.3
'@pnpm/os.env.path-extender': ^3.0.0
'@pnpm/patch-package': 0.0.1
'@pnpm/registry-mock': 5.2.4
'@pnpm/semver-diff': ^1.1.0

View File

@@ -19,13 +19,14 @@ test('pnpm bin', async () => {
test('pnpm bin -g', async () => {
tempDir()
const binDir = path.join(process.cwd(), 'bin')
const env = {
PNPM_HOME: process.cwd(),
[PATH_NAME]: process.cwd(),
[PATH_NAME]: binDir,
}
const result = execPnpmSync(['bin', '-g'], { env })
expect(result.status).toBe(0)
expect(result.stdout.toString().trim()).toEqual(env.PNPM_HOME)
expect(result.stdout.toString().trim()).toEqual(binDir)
})

View File

@@ -57,7 +57,7 @@ test('global installation', async () => {
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
await execPnpm(['add', '--global', 'is-positive'], { env })
@@ -87,7 +87,7 @@ test('global install warns when project has packageManager configured', async ()
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const { status } = execPnpmSync([
'add',
@@ -103,7 +103,7 @@ test('global installation to custom directory with --global-dir', async () => {
prepare()
const global = path.resolve('..', 'global')
const pnpmHome = path.join(global, 'pnpm')
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome }
await execPnpm(['add', '--global', '--global-dir=../global', 'is-positive'], { env })
@@ -121,7 +121,7 @@ test('always install latest when doing global installation without spec', async
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
await execPnpm(['add', '-g', '@pnpm.e2e/peer-c@1'], { env })
await execPnpm(['add', '-g', '@pnpm.e2e/peer-c'], { env })
@@ -143,7 +143,7 @@ test('run lifecycle events of global packages in correct working directory', asy
fs.mkdirSync(pnpmHome, { recursive: true })
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
[PATH_NAME]: `${path.join(pnpmHome, 'bin')}${path.delimiter}${process.env[PATH_NAME]!}`,
PNPM_HOME: pnpmHome,
XDG_DATA_HOME: global,
}
@@ -188,7 +188,7 @@ test.skip('dangerously-allow-all-builds=true in global config', async () => {
].join('\n'))
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
[PATH_NAME]: `${path.join(pnpmHome, 'bin')}${path.delimiter}${process.env[PATH_NAME]!}`,
HOME: home,
XDG_CONFIG_HOME: cfgHome,
PNPM_HOME: pnpmHome,
@@ -246,7 +246,7 @@ test.skip('dangerously-allow-all-builds=false in global config', async () => {
].join('\n'))
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
[PATH_NAME]: `${path.join(pnpmHome, 'bin')}${path.delimiter}${process.env[PATH_NAME]!}`,
HOME: home,
XDG_CONFIG_HOME: cfgHome,
PNPM_HOME: pnpmHome,
@@ -277,7 +277,7 @@ test('global update to latest', async () => {
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
await execPnpm(['add', '--global', 'is-positive@1'], { env })
await execPnpm(['update', '--global', '--latest'], { env })
@@ -294,7 +294,7 @@ test('global update should not crash if there are no global packages', async ()
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
expect(execPnpmSync(['update', '--global'], { env }).status).toBe(0)
})
@@ -305,7 +305,7 @@ test('global add cleans up stale bins when re-adding a package with different bi
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(pnpmHome, { recursive: true })
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
// Create v1 tarball with bin "old-bin"
const pkgDir = path.resolve('..', 'my-tool')
@@ -320,7 +320,7 @@ test('global add cleans up stale bins when re-adding a package with different bi
execPnpmSync(['pack', '--pack-destination', pkgDir], { cwd: path.join(pkgDir, 'package') })
await execPnpm(['add', '-g', tarballV1], { env })
expect(fs.existsSync(path.join(pnpmHome, 'old-bin'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'old-bin'))).toBeTruthy()
// Create v2 tarball with bin "new-bin"
fs.writeFileSync(path.join(pkgDir, 'package', 'package.json'), JSON.stringify({
@@ -335,8 +335,8 @@ test('global add cleans up stale bins when re-adding a package with different bi
await execPnpm(['add', '-g', tarballV2], { env })
// old-bin should be gone, new-bin should exist
expect(fs.existsSync(path.join(pnpmHome, 'old-bin'))).toBeFalsy()
expect(fs.existsSync(path.join(pnpmHome, 'new-bin'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'old-bin'))).toBeFalsy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'new-bin'))).toBeTruthy()
})
test('global add refuses to install when bin name conflicts with another global package', async () => {
@@ -345,7 +345,7 @@ test('global add refuses to install when bin name conflicts with another global
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(pnpmHome, { recursive: true })
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
// Create two local packages that both expose a bin called "my-bin"
const pkgA = path.resolve('..', 'pkg-a')
@@ -395,8 +395,9 @@ test('global add from a local directory using "."', () => {
}))
fs.writeFileSync(path.join(localPkg, 'index.js'), '#!/usr/bin/env node\nconsole.log("hello")\n')
const globalBin = path.join(pnpmHome, 'bin')
const env = {
[PATH_NAME]: pnpmHome,
[PATH_NAME]: globalBin,
PNPM_HOME: pnpmHome,
XDG_DATA_HOME: global,
pnpm_config_store_dir: path.resolve('..', 'store'),
@@ -411,17 +412,17 @@ test('global add from a local directory using "."', () => {
expect(findGlobalPkg(globalPkgDir(pnpmHome), 'my-local-tool')).toBeTruthy()
// Verify the bin was linked
expect(fs.existsSync(path.join(pnpmHome, 'my-local-tool'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'my-local-tool'))).toBeTruthy()
// Install globally using a file: relative selector
execPnpmSync(['add', '-g', 'file:./'], { cwd: localPkg, env, expectSuccess: true })
expect(findGlobalPkg(globalPkgDir(pnpmHome), 'my-local-tool')).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'my-local-tool'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'my-local-tool'))).toBeTruthy()
// Install globally using a link: relative selector
execPnpmSync(['add', '-g', 'link:../my-local-tool'], { cwd: process.cwd(), env, expectSuccess: true })
expect(findGlobalPkg(globalPkgDir(pnpmHome), 'my-local-tool')).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'my-local-tool'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'my-local-tool'))).toBeTruthy()
})
test('global remove deletes install group and bin shims', async () => {
@@ -430,7 +431,7 @@ test('global remove deletes install group and bin shims', async () => {
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(pnpmHome, { recursive: true })
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
// Create two packages with bins and install them together as a group
const pkgA = path.resolve('..', 'tool-a')
@@ -453,13 +454,13 @@ test('global remove deletes install group and bin shims', async () => {
// Install as a group
await execPnpm(['add', '-g', pkgA, pkgB], { env })
expect(fs.existsSync(path.join(pnpmHome, 'tool-a-bin'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'tool-b-bin'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'tool-a-bin'))).toBeTruthy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'tool-b-bin'))).toBeTruthy()
// Remove one package — entire group (both bins) should be removed
await execPnpm(['remove', '-g', 'tool-a'], { env })
expect(fs.existsSync(path.join(pnpmHome, 'tool-a-bin'))).toBeFalsy()
expect(fs.existsSync(path.join(pnpmHome, 'tool-b-bin'))).toBeFalsy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'tool-a-bin'))).toBeFalsy()
expect(fs.existsSync(path.join(pnpmHome, 'bin', 'tool-b-bin'))).toBeFalsy()
expect(findGlobalPkg(globalPkgDir(pnpmHome), 'tool-a')).toBeNull()
expect(findGlobalPkg(globalPkgDir(pnpmHome), 'tool-b')).toBeNull()
})

View File

@@ -25,7 +25,7 @@ test('pnpm root -g', async () => {
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const env = { [PATH_NAME]: path.join(pnpmHome, 'bin'), PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const result = execPnpmSync(['root', '-g'], { env })

View File

@@ -31,10 +31,11 @@ test('uninstall global package with its bin files', async () => {
prepare()
const global = process.cwd()
const globalBin = path.resolve(global, 'bin')
const pnpmHome = path.resolve(global, 'pnpm')
const globalBin = path.join(pnpmHome, 'bin')
const env = {
PNPM_HOME: globalBin,
PNPM_HOME: pnpmHome,
[PATH]: `${globalBin}${path.delimiter}${process.env[PATH] ?? ''}`,
XDG_DATA_HOME: global,
}