mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
fix: finding a suitable global executables directory
ref #2649 PR #2661
This commit is contained in:
7
.changeset/itchy-grapes-battle.md
Normal file
7
.changeset/itchy-grapes-battle.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/global-bin-dir": minor
|
||||
---
|
||||
|
||||
`globalBinDir()` may accept an array of suitable executable directories.
|
||||
If one of these directories is in PATH and has bigger priority than the
|
||||
npm/pnpm/nodejs directories, then that directory will be used.
|
||||
10
.changeset/strong-plants-shake.md
Normal file
10
.changeset/strong-plants-shake.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/plugin-commands-installation": major
|
||||
---
|
||||
|
||||
A new setting is returned by `@pnpm/config`: `npmGlobalBinDir`.
|
||||
`npmGlobalBinDir` is the global executable directory used by npm.
|
||||
|
||||
This new config is used by `@pnpm/global-bin-dir` to find a suitable
|
||||
directory for the binstubs installed by pnpm globally.
|
||||
@@ -24,6 +24,7 @@ export interface Config {
|
||||
globalDir: string,
|
||||
dir: string,
|
||||
bin?: string,
|
||||
npmGlobalBinDir: string,
|
||||
ignoreScripts?: boolean
|
||||
save?: boolean,
|
||||
saveProd?: boolean,
|
||||
|
||||
@@ -196,6 +196,9 @@ export default async (
|
||||
? npmConfig.globalPrefix
|
||||
: findBestGlobalPrefixOnWindows(npmConfig.globalPrefix, process.env)
|
||||
)
|
||||
pnpmConfig.npmGlobalBinDir = process.platform === 'win32'
|
||||
? npmGlobalPrefix
|
||||
: path.resolve(npmGlobalPrefix, 'bin')
|
||||
pnpmConfig.globalDir = pnpmConfig.globalDir ? npmGlobalPrefix : path.join(npmGlobalPrefix, 'pnpm-global')
|
||||
pnpmConfig.lockfileDir = pnpmConfig.lockfileDir ?? pnpmConfig.lockfileDirectory ?? pnpmConfig.shrinkwrapDirectory
|
||||
pnpmConfig.useLockfile = (() => {
|
||||
@@ -224,7 +227,7 @@ export default async (
|
||||
process.platform === 'win32'
|
||||
? cliOptions.dir : path.resolve(cliOptions.dir, 'bin')
|
||||
)
|
||||
: globalBinDir()
|
||||
: globalBinDir([pnpmConfig.npmGlobalBinDir])
|
||||
pnpmConfig.allowNew = true
|
||||
pnpmConfig.ignoreCurrentPrefs = true
|
||||
pnpmConfig.saveProd = true
|
||||
|
||||
@@ -3,17 +3,23 @@ import { sync as canWriteToDir } from 'can-write-to-dir'
|
||||
import path = require('path')
|
||||
import PATH = require('path-name')
|
||||
|
||||
export default function () {
|
||||
export default function (knownCandidates: string[] = []) {
|
||||
if (!process.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 = process.env[PATH]?.split(path.delimiter) ?? []
|
||||
return pickBestGlobalBinDir(dirs)
|
||||
const nodeBinDir = path.dirname(process.execPath)
|
||||
return pickBestGlobalBinDir(dirs, [
|
||||
...knownCandidates,
|
||||
nodeBinDir,
|
||||
])
|
||||
}
|
||||
|
||||
function pickBestGlobalBinDir (dirs: string[]) {
|
||||
const nodeBinDir = path.dirname(process.execPath)
|
||||
const areDirsEqual = (dir1: string, dir2: string) =>
|
||||
path.relative(dir1, dir2) === ''
|
||||
|
||||
function pickBestGlobalBinDir (dirs: string[], knownCandidates: string[]) {
|
||||
const noWriteAccessDirs = [] as string[]
|
||||
for (const dir of dirs) {
|
||||
const lowCaseDir = dir.toLowerCase()
|
||||
@@ -22,7 +28,7 @@ function pickBestGlobalBinDir (dirs: string[]) {
|
||||
isUnderDir('nodejs', lowCaseDir) ||
|
||||
isUnderDir('npm', lowCaseDir) ||
|
||||
isUnderDir('pnpm', lowCaseDir) ||
|
||||
path.relative(nodeBinDir, dir) === ''
|
||||
knownCandidates.some((candidate) => areDirsEqual(candidate, dir))
|
||||
) {
|
||||
if (canWriteToDirAndExists(dir)) return dir
|
||||
noWriteAccessDirs.push(dir)
|
||||
|
||||
@@ -50,6 +50,12 @@ test('prefer a directory that has "nodejs", "npm", or "pnpm" in the path', (t) =
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('prefer directory that is passed in as a known suitable location', (t) => {
|
||||
canWriteToDir = () => true
|
||||
t.equal(globalBinDir([userGlobalBin]), userGlobalBin)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test("ignore directories that don't exist", (t) => {
|
||||
canWriteToDir = (dir) => {
|
||||
if (dir === nodeGlobalBin) {
|
||||
|
||||
@@ -74,6 +74,7 @@ export async function handler (
|
||||
opts: CreateStoreControllerOptions & Pick<Config,
|
||||
| 'cliOptions'
|
||||
| 'engineStrict'
|
||||
| 'npmGlobalBinDir'
|
||||
| 'saveDev'
|
||||
| 'saveOptional'
|
||||
| 'saveProd'
|
||||
@@ -107,7 +108,7 @@ export async function handler (
|
||||
const newManifest = await linkToGlobal(cwd, {
|
||||
...linkOpts,
|
||||
// A temporary workaround. global bin/prefix are always defined when --global is set
|
||||
globalBin: globalBinDir(),
|
||||
globalBin: globalBinDir([linkOpts.npmGlobalBinDir]),
|
||||
globalDir: linkOpts.globalDir!,
|
||||
manifest: manifest || {},
|
||||
})
|
||||
|
||||
@@ -37,6 +37,7 @@ test('prune removes external link that is not in package.json', async function (
|
||||
await link.handler({
|
||||
...DEFAULT_OPTIONS,
|
||||
dir: process.cwd(),
|
||||
npmGlobalBinDir: process.cwd(),
|
||||
storeDir,
|
||||
}, ['./local'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user