mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-27 18:46:18 -04:00
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:
9
.changeset/fix-global-bin-subdir.md
Normal file
9
.changeset/fix-global-bin-subdir.md
Normal 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.
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}.`
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
2
engine/runtime/commands/src/env/utils.ts
vendored
2
engine/runtime/commands/src/env/utils.ts
vendored
@@ -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
35
pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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 })
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user