fix: finding a suitable global executables directory

ref #2649
PR  #2661
This commit is contained in:
Zoltan Kochan
2020-07-02 17:33:40 +03:00
committed by GitHub
parent 10d0055364
commit 915828b464
8 changed files with 42 additions and 7 deletions

View 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.

View 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.

View File

@@ -24,6 +24,7 @@ export interface Config {
globalDir: string,
dir: string,
bin?: string,
npmGlobalBinDir: string,
ignoreScripts?: boolean
save?: boolean,
saveProd?: boolean,

View File

@@ -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

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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 || {},
})

View File

@@ -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'])