mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
fix(config): checking global-bin-dir in PATH (#4751)
When global-bin-dir is pointing to a symlink. close #4744
This commit is contained in:
6
.changeset/chilled-taxis-hammer.md
Normal file
6
.changeset/chilled-taxis-hammer.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/config": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
When the global bin directory is set to a symlink, check not only the symlink in the PATH but also the target of the symlink [#4744](https://github.com/pnpm/pnpm/issues/4744).
|
||||
@@ -1,18 +1,18 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import { sync as canWriteToDir } from 'can-write-to-dir'
|
||||
import PATH from 'path-name'
|
||||
|
||||
export function checkGlobalBinDir (
|
||||
export async function checkGlobalBinDir (
|
||||
globalBinDir: string,
|
||||
{ env, shouldAllowWrite }: { env: Record<string, string | undefined>, shouldAllowWrite?: boolean }
|
||||
): void {
|
||||
): Promise<void> {
|
||||
if (!env[PATH]) {
|
||||
throw new PnpmError('NO_PATH_ENV',
|
||||
`Couldn't find a global directory for executables because the "${PATH}" environment variable is not set.`)
|
||||
}
|
||||
const dirs = env[PATH]?.split(path.delimiter) ?? []
|
||||
if (!dirs.some((dir) => areDirsEqual(globalBinDir, dir))) {
|
||||
if (!await globalBinDirIsInPath(globalBinDir, env)) {
|
||||
throw new PnpmError('GLOBAL_BIN_DIR_NOT_IN_PATH', `The configured global bin directory "${globalBinDir}" is not in PATH`)
|
||||
}
|
||||
if (shouldAllowWrite && !canWriteToDirAndExists(globalBinDir)) {
|
||||
@@ -20,6 +20,13 @@ export function checkGlobalBinDir (
|
||||
}
|
||||
}
|
||||
|
||||
async function globalBinDirIsInPath (globalBinDir: string, env: Record<string, string | undefined>) {
|
||||
const dirs = env[PATH]?.split(path.delimiter) ?? []
|
||||
if (dirs.some((dir) => areDirsEqual(globalBinDir, dir))) return true
|
||||
const realGlobalBinDir = await fs.realpath(globalBinDir)
|
||||
return dirs.some((dir) => areDirsEqual(realGlobalBinDir, dir))
|
||||
}
|
||||
|
||||
const areDirsEqual = (dir1: string, dir2: string) =>
|
||||
path.relative(dir1, dir2) === ''
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ export default async (
|
||||
pnpmConfig.bin = npmConfig.get('global-bin-dir') ?? env.PNPM_HOME
|
||||
if (pnpmConfig.bin) {
|
||||
fs.mkdirSync(pnpmConfig.bin, { recursive: true })
|
||||
checkGlobalBinDir(pnpmConfig.bin, { env, shouldAllowWrite: opts.globalDirShouldAllowWrite })
|
||||
await checkGlobalBinDir(pnpmConfig.bin, { env, shouldAllowWrite: opts.globalDirShouldAllowWrite })
|
||||
} else {
|
||||
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.',
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/// <reference path="../../../typings/index.d.ts"/>
|
||||
import fs from 'fs'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
import path from 'path'
|
||||
import pathName from 'path-name'
|
||||
import symlinkDir from 'symlink-dir'
|
||||
import { homedir } from 'os'
|
||||
import getConfig from '@pnpm/config'
|
||||
|
||||
@@ -63,3 +66,44 @@ test('respects global-bin-dir rather than dir', async () => {
|
||||
})
|
||||
expect(config.bin).toBe(globalBinDir)
|
||||
})
|
||||
|
||||
test('an exception is thrown when the global dir is not in PATH', async () => {
|
||||
await expect(
|
||||
getConfig({
|
||||
cliOptions: {
|
||||
global: true,
|
||||
dir: __dirname,
|
||||
},
|
||||
env: {
|
||||
[pathName]: process.env[pathName],
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(/is not in PATH/)
|
||||
})
|
||||
|
||||
test('the global directory may be a symlink to a directory that is in PATH', async () => {
|
||||
const tmp = tempDir()
|
||||
const globalBinDirTarget = path.join(tmp, 'global-target')
|
||||
fs.mkdirSync(globalBinDirTarget)
|
||||
const globalBinDirSymlink = path.join(tmp, 'global-symlink')
|
||||
await symlinkDir(globalBinDirTarget, globalBinDirSymlink)
|
||||
const { config } = await getConfig({
|
||||
cliOptions: {
|
||||
global: true,
|
||||
'global-bin-dir': globalBinDirSymlink,
|
||||
dir: __dirname,
|
||||
},
|
||||
env: {
|
||||
[pathName]: `${globalBinDirTarget}${path.delimiter}${process.env[pathName]!}`,
|
||||
},
|
||||
packageManager: {
|
||||
name: 'pnpm',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
expect(config.bin).toBe(globalBinDirSymlink)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user