feat!: global bins should be created only in predefined locations (#4280)

This commit is contained in:
Zoltan Kochan
2022-01-31 18:01:37 +02:00
committed by GitHub
parent 067d9abbc2
commit 585e9ca9ec
21 changed files with 120 additions and 531 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/config": major
"pnpm": major
---
`pnpm install -g pkg` will add the global command only to a predefined location. pnpm will not try to add a bin to the global Node.js or npm folder. To set the global bin directory, either set the `PNPM_HOME` env variable or the [`global-bin-dir`](https://pnpm.io/npmrc#global-bin-dir) setting.

View File

@@ -34,14 +34,15 @@
"dependencies": {
"@pnpm/constants": "workspace:5.0.0",
"@pnpm/error": "workspace:2.0.0",
"@pnpm/global-bin-dir": "workspace:3.0.0",
"@pnpm/pnpmfile": "workspace:1.2.4",
"@pnpm/read-project-manifest": "workspace:2.0.11",
"@pnpm/types": "workspace:7.9.0",
"@zkochan/npm-conf": "2.0.2",
"camelcase": "^6.2.0",
"can-write-to-dir": "^1.1.1",
"is-subdir": "^1.1.1",
"normalize-registry-url": "2.0.0",
"path-name": "^1.0.0",
"ramda": "^0.27.1",
"realpath-missing": "^1.1.0",
"which": "^2.0.2"

View File

@@ -0,0 +1,33 @@
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 (
globalBinDir: string,
{ env, shouldAllowWrite }: { env: Record<string, string | undefined>, shouldAllowWrite?: boolean }
): 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))) {
throw new PnpmError('GLOBAL_BIN_DIR_NOT_IN_PATH', `The configured global bin directory "${globalBinDir}" is not in PATH`)
}
if (shouldAllowWrite && !canWriteToDirAndExists(globalBinDir)) {
throw new PnpmError('PNPM_DIR_NOT_WRITABLE', `The CLI has no write access to the pnpm home directory at ${globalBinDir}`)
}
}
const areDirsEqual = (dir1: string, dir2: string) =>
path.relative(dir1, dir2) === ''
function canWriteToDirAndExists (dir: string) {
try {
return canWriteToDir(dir)
} catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') throw err
return false
}
}

View File

@@ -2,7 +2,6 @@ import path from 'path'
import fs from 'fs'
import { LAYOUT_VERSION } from '@pnpm/constants'
import PnpmError from '@pnpm/error'
import globalBinDir from '@pnpm/global-bin-dir'
import { requireHooks } from '@pnpm/pnpmfile'
import { safeReadProjectManifestOnly } from '@pnpm/read-project-manifest'
import camelcase from 'camelcase'
@@ -12,6 +11,7 @@ import normalizeRegistryUrl from 'normalize-registry-url'
import fromPairs from 'ramda/src/fromPairs'
import realpathMissing from 'realpath-missing'
import whichcb from 'which'
import { checkGlobalBinDir } from './checkGlobalBinDir'
import getScopeRegistries from './getScopeRegistries'
import { getCacheDir, getConfigDir, getDataDir, getStateDir } from './dirs'
import {
@@ -131,8 +131,10 @@ export default async (
rcOptionsTypes?: Record<string, unknown>
workspaceDir?: string | undefined
checkUnknownSetting?: boolean
env?: Record<string, string | undefined>
}
): Promise<{ config: Config, warnings: string[] }> => {
const env = opts.env ?? process.env
const packageManager = opts.packageManager ?? { name: 'pnpm', version: 'undefined' }
const cliOptions = opts.cliOptions ?? {}
const warnings = new Array<string>()
@@ -275,7 +277,6 @@ export default async (
pnpmConfig.pnpmHomeDir = getDataDir(process)
if (cliOptions['global']) {
const knownGlobalBinDirCandidates: string[] = []
let globalDirRoot
if (pnpmConfig['globalDir']) {
globalDirRoot = pnpmConfig['globalDir']
@@ -284,18 +285,14 @@ export default async (
}
pnpmConfig.dir = path.join(globalDirRoot, LAYOUT_VERSION.toString())
const npmConfigGlobalBinDir = npmConfig.get('global-bin-dir')
if (typeof npmConfigGlobalBinDir === 'string') {
fs.mkdirSync(npmConfigGlobalBinDir, { recursive: true })
pnpmConfig.bin = npmConfigGlobalBinDir
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 })
} else {
pnpmConfig.bin = cliOptions.dir
? (
process.platform === 'win32'
? cliOptions.dir
: path.resolve(cliOptions.dir, 'bin')
)
: globalBinDir(knownGlobalBinDirCandidates, { shouldAllowWrite: opts.globalDirShouldAllowWrite === true })
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.',
})
}
pnpmConfig.save = true
pnpmConfig.allowNew = true

View File

@@ -1,8 +1,10 @@
/// <reference path="../../../typings/index.d.ts"/>
import path from 'path'
import pathName from 'path-name'
import { homedir } from 'os'
import getConfig from '@pnpm/config'
const globalBinDir = path.join(homedir(), '.local', 'pnpm')
const isWindows = process.platform === 'win32'
jest.mock('@zkochan/npm-conf/lib/conf', () => {
@@ -34,12 +36,15 @@ test('respects global-bin-dir in npmrc', async () => {
cliOptions: {
global: true,
},
env: {
[pathName]: `${globalBinDir}${path.delimiter}${process.env[pathName]!}`,
},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(config.bin).toBe(path.join(homedir(), '.local', 'pnpm'))
expect(config.bin).toBe(globalBinDir)
})
test('respects global-bin-dir rather than dir', async () => {
@@ -48,10 +53,13 @@ test('respects global-bin-dir rather than dir', async () => {
global: true,
dir: __dirname,
},
env: {
[pathName]: `${globalBinDir}${path.delimiter}${process.env[pathName]!}`,
},
packageManager: {
name: 'pnpm',
version: '1.0.0',
},
})
expect(config.bin).toBe(path.join(homedir(), '.local', 'pnpm'))
expect(config.bin).toBe(globalBinDir)
})

View File

@@ -1,6 +1,7 @@
/// <reference path="../../../typings/index.d.ts"/>
import { promises as fs } from 'fs'
import path from 'path'
import PATH from 'path-name'
import getConfig from '@pnpm/config'
import PnpmError from '@pnpm/error'
import prepare, { prepareEmpty } from '@pnpm/prepare'
@@ -17,6 +18,11 @@ delete process.env.npm_config_virtual_store_dir
delete process.env.npm_config_shared_workspace_lockfile
delete process.env.npm_config_side_effects_cache
const env = {
PNPM_HOME: __dirname,
[PATH]: __dirname,
}
test('getConfig()', async () => {
const { config } = await getConfig({
cliOptions: {},
@@ -39,6 +45,7 @@ test('throw error if --link-workspace-packages is used with --global', async ()
global: true,
'link-workspace-packages': true,
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -56,6 +63,7 @@ test('correct settings on global install', async () => {
global: true,
save: false,
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -71,6 +79,7 @@ test('throw error if --shared-workspace-lockfile is used with --global', async (
global: true,
'shared-workspace-lockfile': true,
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -89,6 +98,7 @@ test('throw error if --lockfile-dir is used with --global', async () => {
global: true,
'lockfile-dir': '/home/src',
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -107,6 +117,7 @@ test('throw error if --hoist-pattern is used with --global', async () => {
global: true,
'hoist-pattern': 'eslint',
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -125,6 +136,7 @@ test('throw error if --virtual-store-dir is used with --global', async () => {
global: true,
'virtual-store-dir': 'pkgs',
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',
@@ -167,6 +179,7 @@ test('when using --global, link-workspace-packages, shared-workspace-shrinwrap a
cliOptions: {
global: true,
},
env,
packageManager: {
name: 'pnpm',
version: '1.0.0',

View File

@@ -18,9 +18,6 @@
{
"path": "../error"
},
{
"path": "../global-bin-dir"
},
{
"path": "../pnpmfile"
},

View File

@@ -1,92 +0,0 @@
# @pnpm/global-bin-dir
## 3.0.0
### Major Changes
- 47a1e9696: Always prefer the pnpm home directory, when searching for global bin location.
## 2.0.0
### Major Changes
- 97b986fbc: Node.js 10 support is dropped. At least Node.js 12.17 is required for the package to work.
### Patch Changes
- Updated dependencies [97b986fbc]
- @pnpm/error@2.0.0
## 1.2.6
### Patch Changes
- 941c5e8de: `npx pnpm install --global pnpm` should not install pnpm to the temporary directory of npx.
## 1.2.5
### Patch Changes
- Updated dependencies [0c5f1bcc9]
- @pnpm/error@1.4.0
## 1.2.4
### Patch Changes
- 846887de3: When searching for a suitable global bin directory, search for symlinked node, npm, pnpm commands, not only command files.
## 1.2.3
### Patch Changes
- Updated dependencies [75a36deba]
- @pnpm/error@1.3.1
## 1.2.2
### Patch Changes
- 4d4d22b63: A directory is considered a valid global executable directory for pnpm, if it contains a node, or npm, or pnpm executable, not directory.
## 1.2.1
### Patch Changes
- Updated dependencies [6d480dd7a]
- @pnpm/error@1.3.0
## 1.2.0
### Minor Changes
- ad69677a7: Add a new optional argument. When the argument is `false`, a global bin directory is returned even if the process has no write access to it.
## 1.1.1
### Patch Changes
- 245221baa: When searching a suitable global executables directory, take any directory from the PATH that has a node, pnpm, or npm command in it.
## 1.1.0
### Minor Changes
- 915828b46: `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.
## 1.0.1
### Patch Changes
- 2c190d49d: When looking for suitable directories for global executables, ignore case.
When comparing to the currently running Node.js executable directory,
ignore any trailing slash. `/foo/bar` is the same as `/foo/bar/`.
## 1.0.0
### Major Changes
- 1146b76d2: Finds a directory that is in PATH and we have permission to write to it.

View File

@@ -1,15 +0,0 @@
# @pnpm/global-bin-dir
> Finds a directory that is in PATH and we have permission to write to it
[![npm version](https://img.shields.io/npm/v/@pnpm/global-bin-dir.svg)](https://www.npmjs.com/package/@pnpm/global-bin-dir)
## Installation
```sh
pnpm add @pnpm/global-bin-dir
```
## License
MIT

View File

@@ -1 +0,0 @@
module.exports = require('../../jest.config.js')

View File

@@ -1,41 +0,0 @@
{
"name": "@pnpm/global-bin-dir",
"version": "3.0.0",
"description": "Finds a directory that is in PATH and we have permission to write to i",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"!*.map"
],
"engines": {
"node": ">=12.17"
},
"scripts": {
"lint": "eslint src/**/*.ts test/**/*.ts",
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm run compile",
"compile": "tsc --build && pnpm run lint -- --fix",
"_test": "jest"
},
"repository": "https://github.com/pnpm/pnpm/blob/main/packages/global-bin-dir",
"keywords": [
"pnpm6",
"pnpm"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"homepage": "https://github.com/pnpm/pnpm/blob/main/packages/global-bin-dir#readme",
"funding": "https://opencollective.com/pnpm",
"dependencies": {
"@pnpm/error": "workspace:2.0.0",
"can-write-to-dir": "^1.1.1",
"path-name": "^1.0.0"
},
"devDependencies": {
"@pnpm/global-bin-dir": "workspace:3.0.0",
"is-windows": "^1.0.2"
}
}

View File

@@ -1,91 +0,0 @@
import 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 default function (
knownCandidates: string[] = [],
{ shouldAllowWrite = true }: { shouldAllowWrite?: boolean } = {}
) {
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) ?? []
const nodeBinDir = path.dirname(process.execPath)
return pickBestGlobalBinDir(dirs, [
...knownCandidates,
nodeBinDir,
], shouldAllowWrite)
}
const areDirsEqual = (dir1: string, dir2: string) =>
path.relative(dir1, dir2) === ''
function pickBestGlobalBinDir (
dirs: string[],
knownCandidates: string[],
shouldAllowWrite: boolean
) {
const noWriteAccessDirs = [] as string[]
const pnpmDir = dirs.find((dir) => isUnderDir('pnpm', dir.toLowerCase()))
if (pnpmDir != null) {
if (canWriteToDirAndExists(pnpmDir)) return pnpmDir
throw new PnpmError('PNPM_DIR_NOT_WRITABLE', `The CLI has no write access to the pnpm home directory at ${pnpmDir}`)
}
for (const dir of dirs) {
const lowCaseDir = dir.toLowerCase()
if ((
isUnderDir('node', lowCaseDir) ||
isUnderDir('nodejs', lowCaseDir) ||
isUnderDir('npm', lowCaseDir) ||
knownCandidates.some((candidate) => areDirsEqual(candidate, dir)) ||
dirHasNodeRelatedCommand(dir)
) && !isUnderDir('_npx', lowCaseDir)) {
if (canWriteToDirAndExists(dir)) return dir
noWriteAccessDirs.push(dir)
}
}
if (noWriteAccessDirs.length === 0) {
throw new PnpmError('NO_GLOBAL_BIN_DIR', "Couldn't find a suitable global executables directory.", {
hint: `There should be a node, nodejs, npm, or pnpm directory in the "${PATH}" environment variable`,
})
}
if (shouldAllowWrite) {
throw new PnpmError('GLOBAL_BIN_DIR_PERMISSION', 'No write access to the found global executable directories', {
hint: `The found directories:
${noWriteAccessDirs.join('\n')}`,
})
}
return noWriteAccessDirs[0]
}
const NODE_RELATED_COMMANDS = new Set(['pnpm', 'npm', 'node'])
function dirHasNodeRelatedCommand (dir: string) {
try {
return fs.readdirSync(dir, { withFileTypes: true })
// We are searching for files or symlinks, not directories
.filter((entry) => !entry.isDirectory())
.map(({ name }) => name.toLowerCase())
.some((file) => NODE_RELATED_COMMANDS.has(file.split('.')[0]))
} catch (err: any) { // eslint-disable-line
return false
}
}
function isUnderDir (dir: string, target: string) {
target = target.endsWith(path.sep) ? target : `${target}${path.sep}`
return target.includes(`${path.sep}${dir}${path.sep}`) ||
target.includes(`${path.sep}.${dir}${path.sep}`)
}
function canWriteToDirAndExists (dir: string) {
try {
return canWriteToDir(dir)
} catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') throw err
return false
}
}

View File

@@ -1,214 +0,0 @@
import path from 'path'
import PnpmError from '@pnpm/error'
import { sync as _canWriteToDir } from 'can-write-to-dir'
import isWindows from 'is-windows'
import globalBinDir from '../src/index'
const makePath =
isWindows()
? (...paths: string[]) => `C:\\${path.join(...paths)}`
: (...paths: string[]) => `/${path.join(...paths)}`
let canWriteToDir!: typeof _canWriteToDir
let readdirSync = (dir: string) => [] as Array<{ name: string, isDirectory: () => boolean }>
const FAKE_PATH = 'FAKE_PATH'
function makeFileEntry (name: string) {
return { name, isDirectory: () => false }
}
function makeDirEntry (name: string) {
return { name, isDirectory: () => true }
}
jest.mock('can-write-to-dir', () => ({
sync: (dir: string) => canWriteToDir(dir),
}))
jest.mock('fs', () => {
const originalModule = jest.requireActual('fs')
return {
...originalModule,
readdirSync: (dir: string) => readdirSync(dir),
}
})
let originalPath: string | undefined
beforeEach(() => {
originalPath = process.env[FAKE_PATH]
})
afterEach(() => {
process.env[FAKE_PATH] = originalPath
})
jest.mock('path-name', () => 'FAKE_PATH')
const userGlobalBin = makePath('usr', 'local', 'bin')
const nodeGlobalBin = makePath('home', 'z', '.nvs', 'node', '12.0.0', 'x64', 'bin')
const npmGlobalBin = makePath('home', 'z', '.npm')
const pnpmGlobalBin = makePath('home', 'z', '.pnpm')
const npxGlobalBin = makePath('home', 'z', '.npm', '_npx', '123')
const otherDir = makePath('some', 'dir')
const currentExecDir = makePath('current', 'exec')
const dirWithTrailingSlash = `${makePath('current', 'slash')}${path.sep}`
const BIG_PATH = [
npxGlobalBin,
userGlobalBin,
nodeGlobalBin,
npmGlobalBin,
otherDir,
currentExecDir,
dirWithTrailingSlash,
].join(path.delimiter)
test('prefer the pnpm home directory', () => {
process.env[FAKE_PATH] = [
npmGlobalBin,
currentExecDir,
nodeGlobalBin,
pnpmGlobalBin,
].join(path.delimiter)
canWriteToDir = () => true
expect(globalBinDir()).toStrictEqual(pnpmGlobalBin)
})
test('fail if there is no write access to the pnpm home directory', () => {
process.env[FAKE_PATH] = [
npmGlobalBin,
currentExecDir,
nodeGlobalBin,
pnpmGlobalBin,
].join(path.delimiter)
canWriteToDir = (dir) => dir !== pnpmGlobalBin
expect(() => globalBinDir()).toThrow(`The CLI has no write access to the pnpm home directory at ${pnpmGlobalBin}`)
})
test('prefer a directory that has "nodejs" or "npm" in the path', () => {
process.env[FAKE_PATH] = BIG_PATH
canWriteToDir = () => true
expect(globalBinDir()).toStrictEqual(nodeGlobalBin)
canWriteToDir = (dir) => dir !== nodeGlobalBin
expect(globalBinDir()).toStrictEqual(npmGlobalBin)
})
test('prefer directory that is passed in as a known suitable location', () => {
process.env[FAKE_PATH] = BIG_PATH
canWriteToDir = () => true
expect(globalBinDir([userGlobalBin])).toStrictEqual(userGlobalBin)
})
test("ignore directories that don't exist", () => {
process.env[FAKE_PATH] = BIG_PATH
canWriteToDir = (dir) => {
if (dir === nodeGlobalBin) {
const err = new Error('Not exists')
err['code'] = 'ENOENT'
throw err
}
return true
}
expect(globalBinDir()).toEqual(npmGlobalBin)
})
test('prefer the directory of the currently executed nodejs command', () => {
process.env[FAKE_PATH] = BIG_PATH
const originalExecPath = process.execPath
process.execPath = path.join(currentExecDir, 'n')
canWriteToDir = (dir) => dir !== nodeGlobalBin && dir !== npmGlobalBin && dir !== pnpmGlobalBin
expect(globalBinDir()).toEqual(currentExecDir)
process.execPath = path.join(dirWithTrailingSlash, 'n')
expect(globalBinDir()).toEqual(dirWithTrailingSlash)
process.execPath = originalExecPath
})
test('when the process has no write access to any of the suitable directories, throw an error', () => {
process.env[FAKE_PATH] = BIG_PATH
canWriteToDir = (dir) => dir === otherDir
let err!: PnpmError
try {
globalBinDir()
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err).toBeDefined()
expect(err.code).toEqual('ERR_PNPM_GLOBAL_BIN_DIR_PERMISSION')
})
test('when the process has no write access to any of the suitable directories, but opts.shouldAllowWrite is false, return the first match', () => {
process.env[FAKE_PATH] = BIG_PATH
canWriteToDir = (dir) => dir === otherDir
expect(globalBinDir([], { shouldAllowWrite: false })).toEqual(nodeGlobalBin)
})
test('throw an exception if non of the directories in the PATH are suitable', () => {
process.env[FAKE_PATH] = [otherDir].join(path.delimiter)
canWriteToDir = () => true
let err!: PnpmError
try {
globalBinDir()
} catch (_err: any) { // eslint-disable-line
err = _err
}
expect(err).toBeDefined()
expect(err.code).toEqual('ERR_PNPM_NO_GLOBAL_BIN_DIR')
})
test('throw exception if PATH is not set', () => {
delete process.env[FAKE_PATH]
expect(() => globalBinDir()).toThrow(/Couldn't find a global directory/)
})
test('prefer a directory that has "Node" in the path', () => {
const capitalizedNodeGlobalBin = makePath('home', 'z', '.nvs', 'Node', '12.0.0', 'x64', 'bin')
process.env[FAKE_PATH] = capitalizedNodeGlobalBin
canWriteToDir = () => true
expect(globalBinDir()).toEqual(capitalizedNodeGlobalBin)
})
test('select a directory that has a node command in it', () => {
const dir1 = makePath('foo')
const dir2 = makePath('bar')
process.env[FAKE_PATH] = [
dir1,
dir2,
].join(path.delimiter)
canWriteToDir = () => true
readdirSync = (dir) => dir === dir2 ? [makeFileEntry('node')] : []
expect(globalBinDir()).toEqual(dir2)
})
test('do not select a directory that has a node directory in it', () => {
const dir1 = makePath('foo')
const dir2 = makePath('bar')
process.env[FAKE_PATH] = [
dir1,
dir2,
].join(path.delimiter)
canWriteToDir = () => true
readdirSync = (dir) => dir === dir2 ? [makeDirEntry('node')] : []
expect(() => globalBinDir()).toThrow(/Couldn't find a suitable/)
})
test('select a directory that has a node.bat command in it', () => {
const dir1 = makePath('foo')
const dir2 = makePath('bar')
process.env[FAKE_PATH] = [
dir1,
dir2,
].join(path.delimiter)
canWriteToDir = () => true
readdirSync = (dir) => dir === dir2 ? [makeFileEntry('node.bat')] : []
expect(globalBinDir()).toEqual(dir2)
})

View File

@@ -1,16 +0,0 @@
{
"extends": "@pnpm/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": [
"src/**/*.ts",
"../../typings/**/*.d.ts"
],
"references": [
{
"path": "../error"
}
]
}

View File

@@ -1,8 +0,0 @@
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
"test/**/*.ts",
"../../typings/**/*.d.ts"
]
}

View File

@@ -1,7 +1,7 @@
import { promises as fs } from 'fs'
import path from 'path'
import PATH_NAME from 'path-name'
import { tempDir } from '@pnpm/prepare'
import PATH from 'path-name'
import { execPnpmSync } from './utils'
test('pnpm bin', async () => {
@@ -17,8 +17,13 @@ test('pnpm bin', async () => {
test('pnpm bin -g', async () => {
tempDir()
const result = execPnpmSync(['bin', '-g'])
const env = {
PNPM_HOME: process.cwd(),
[PATH_NAME]: process.cwd(),
}
const result = execPnpmSync(['bin', '-g'], { env })
expect(result.status).toStrictEqual(0)
expect(process.env[PATH]!).toContain(result.stdout.toString().trim())
expect(result.stdout.toString().trim()).toEqual(env.PNPM_HOME)
})

View File

@@ -1,5 +1,6 @@
import path from 'path'
import { promises as fs } from 'fs'
import PATH_NAME from 'path-name'
import fs from 'fs'
import { LAYOUT_VERSION } from '@pnpm/constants'
import prepare from '@pnpm/prepare'
import isWindows from 'is-windows'
@@ -12,9 +13,10 @@ import {
test('global installation', async () => {
prepare()
const global = path.resolve('..', 'global')
await fs.mkdir(global)
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { XDG_DATA_HOME: global }
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
await execPnpm(['install', '--global', 'is-positive'], { env })
@@ -33,8 +35,11 @@ test('global installation', async () => {
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 }
await execPnpm(['add', '--global', '--global-dir=../global', 'is-positive'])
await execPnpm(['add', '--global', '--global-dir=../global', 'is-positive'], { env })
const { default: isPositive } = await import(path.resolve(`../global/${LAYOUT_VERSION}/node_modules/is-positive`))
expect(typeof isPositive).toBe('function')
@@ -45,9 +50,10 @@ test('always install latest when doing global installation without spec', async
await addDistTag('peer-c', '2.0.0', 'latest')
const global = path.resolve('..', 'global')
await fs.mkdir(global)
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { XDG_DATA_HOME: global }
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
await execPnpm(['install', '-g', 'peer-c@1'], { env })
await execPnpm(['install', '-g', 'peer-c'], { env })
@@ -68,9 +74,14 @@ test('run lifecycle events of global packages in correct working directory', asy
prepare()
const global = path.resolve('..', 'global')
await fs.mkdir(global)
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { XDG_DATA_HOME: global }
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
PNPM_HOME: pnpmHome,
XDG_DATA_HOME: global,
}
await execPnpm(['install', '-g', 'postinstall-calls-pnpm@1.0.0'], { env })

View File

@@ -1,4 +1,5 @@
import path from 'path'
import PATH_NAME from 'path-name'
import { promises as fs } from 'fs'
import prepare from '@pnpm/prepare'
import isWindows from 'is-windows'
@@ -22,9 +23,14 @@ skipOnWindows('self-update stops the store server', async () => {
expect(serverJson.connectionOptions).toBeTruthy()
const global = path.resolve('global')
const pnpmHome = path.join(global, 'pnpm')
await fs.mkdir(global)
const env = { XDG_DATA_HOME: global }
const env = {
[PATH_NAME]: `${pnpmHome}${path.delimiter}${process.env[PATH_NAME]!}`,
PNPM_HOME: pnpmHome,
XDG_DATA_HOME: global,
}
await execPnpm(['install', '-g', 'pnpm', '--store-dir', path.resolve('..', 'store')], { env })

View File

@@ -1,12 +1,13 @@
import { promises as fs } from 'fs'
import fs from 'fs'
import path from 'path'
import PATH_NAME from 'path-name'
import { LAYOUT_VERSION } from '@pnpm/constants'
import { tempDir } from '@pnpm/prepare'
import { execPnpmSync } from './utils'
test('pnpm root', async () => {
tempDir()
await fs.writeFile('package.json', '{}', 'utf8')
fs.writeFileSync('package.json', '{}', 'utf8')
const result = execPnpmSync(['root'])
@@ -19,9 +20,10 @@ test('pnpm root -g', async () => {
tempDir()
const global = path.resolve('global')
await fs.mkdir(global)
const pnpmHome = path.join(global, 'pnpm')
fs.mkdirSync(global)
const env = { XDG_DATA_HOME: global }
const env = { [PATH_NAME]: pnpmHome, PNPM_HOME: pnpmHome, XDG_DATA_HOME: global }
const result = execPnpmSync(['root', '-g'], { env })

View File

@@ -36,6 +36,7 @@ test('uninstall global package with its bin files', async () => {
const env = {
NPM_CONFIG_PREFIX: global,
PNPM_HOME: globalBin,
[PATH]: `${globalBin}${path.delimiter}${process.env[PATH] ?? ''}`,
}
if (process.env.APPDATA) env['APPDATA'] = global

23
pnpm-lock.yaml generated
View File

@@ -341,7 +341,6 @@ importers:
'@pnpm/config': workspace:13.13.0
'@pnpm/constants': workspace:5.0.0
'@pnpm/error': workspace:2.0.0
'@pnpm/global-bin-dir': workspace:3.0.0
'@pnpm/pnpmfile': workspace:1.2.4
'@pnpm/prepare': workspace:*
'@pnpm/read-project-manifest': workspace:2.0.11
@@ -350,8 +349,10 @@ importers:
'@types/which': ^2.0.0
'@zkochan/npm-conf': 2.0.2
camelcase: ^6.2.0
can-write-to-dir: ^1.1.1
is-subdir: ^1.1.1
normalize-registry-url: 2.0.0
path-name: ^1.0.0
ramda: ^0.27.1
realpath-missing: ^1.1.0
symlink-dir: ^5.0.0
@@ -359,14 +360,15 @@ importers:
dependencies:
'@pnpm/constants': link:../constants
'@pnpm/error': link:../error
'@pnpm/global-bin-dir': link:../global-bin-dir
'@pnpm/pnpmfile': link:../pnpmfile
'@pnpm/read-project-manifest': link:../read-project-manifest
'@pnpm/types': link:../types
'@zkochan/npm-conf': 2.0.2
camelcase: 6.3.0
can-write-to-dir: 1.1.1
is-subdir: 1.2.0
normalize-registry-url: 2.0.0
path-name: 1.0.0
ramda: 0.27.2
realpath-missing: 1.1.0
which: 2.0.2
@@ -1024,21 +1026,6 @@ importers:
'@types/semver': 7.3.9
is-windows: 1.0.2
packages/global-bin-dir:
specifiers:
'@pnpm/error': workspace:2.0.0
'@pnpm/global-bin-dir': workspace:3.0.0
can-write-to-dir: ^1.1.1
is-windows: ^1.0.2
path-name: ^1.0.0
dependencies:
'@pnpm/error': link:../error
can-write-to-dir: 1.1.1
path-name: 1.0.0
devDependencies:
'@pnpm/global-bin-dir': 'link:'
is-windows: 1.0.2
packages/graceful-fs:
specifiers:
'@pnpm/graceful-fs': workspace:1.0.0
@@ -8231,7 +8218,7 @@ packages:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5 || * || *'
eslint: '>=5 || *'
dependencies:
eslint: 8.8.0
eslint-visitor-keys: 2.1.0