diff --git a/.changeset/fix-global-bin-subdir.md b/.changeset/fix-global-bin-subdir.md new file mode 100644 index 0000000000..f06dbc09aa --- /dev/null +++ b/.changeset/fix-global-bin-subdir.md @@ -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. diff --git a/config/reader/src/checkGlobalBinDir.ts b/config/reader/src/checkGlobalBinDir.ts index 289540dc98..63ca5b9d74 100644 --- a/config/reader/src/checkGlobalBinDir.ts +++ b/config/reader/src/checkGlobalBinDir.ts @@ -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}`) } } diff --git a/config/reader/src/index.ts b/config/reader/src/index.ts index 5f1e71cf16..6cf9e8da58 100644 --- a/config/reader/src/index.ts +++ b/config/reader/src/index.ts @@ -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 }) diff --git a/config/reader/test/index.ts b/config/reader/test/index.ts index d57656696d..6ea942e283 100644 --- a/config/reader/test/index.ts +++ b/config/reader/test/index.ts @@ -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) diff --git a/engine/pm/commands/src/self-updater/selfUpdate.ts b/engine/pm/commands/src/self-updater/selfUpdate.ts index 02335fdb91..fdad55da13 100644 --- a/engine/pm/commands/src/self-updater/selfUpdate.ts +++ b/engine/pm/commands/src/self-updater/selfUpdate.ts @@ -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}.` diff --git a/engine/pm/commands/src/setup/setup.ts b/engine/pm/commands/src/setup/setup.ts index 6c26d88635..24ce0b5c30 100644 --- a/engine/pm/commands/src/setup/setup.ts +++ b/engine/pm/commands/src/setup/setup.ts @@ -135,14 +135,16 @@ export async function handler ( } ): Promise { 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', }) diff --git a/engine/pm/commands/test/self-updater/selfUpdate.test.ts b/engine/pm/commands/test/self-updater/selfUpdate.test.ts index cf0c04ec66..c61d6ef281 100644 --- a/engine/pm/commands/test/self-updater/selfUpdate.test.ts +++ b/engine/pm/commands/test/self-updater/selfUpdate.test.ts @@ -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, diff --git a/engine/runtime/commands/src/env/utils.ts b/engine/runtime/commands/src/env/utils.ts index 43983ce2de..fa2ea7d41e 100644 --- a/engine/runtime/commands/src/env/utils.ts +++ b/engine/runtime/commands/src/env/utils.ts @@ -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 { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 118292f73b..73ae2d9afa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 26945f0d31..8c644aaa29 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -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 diff --git a/pnpm/test/bin.ts b/pnpm/test/bin.ts index b143b381cc..9a7d41b54a 100644 --- a/pnpm/test/bin.ts +++ b/pnpm/test/bin.ts @@ -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) }) diff --git a/pnpm/test/install/global.ts b/pnpm/test/install/global.ts index 91012b7dbb..7117fdfcdb 100644 --- a/pnpm/test/install/global.ts +++ b/pnpm/test/install/global.ts @@ -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() }) diff --git a/pnpm/test/root.ts b/pnpm/test/root.ts index f3ee5d7949..73ddd9c47f 100644 --- a/pnpm/test/root.ts +++ b/pnpm/test/root.ts @@ -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 }) diff --git a/pnpm/test/uninstall.ts b/pnpm/test/uninstall.ts index b31ec0143f..ce6e72fa0a 100644 --- a/pnpm/test/uninstall.ts +++ b/pnpm/test/uninstall.ts @@ -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, }