mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
feat: new plugin for managing Node.js versions (#3459)
This commit is contained in:
6
.changeset/light-dolls-reply.md
Normal file
6
.changeset/light-dolls-reply.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
New setting added: `use-node-version`. When set, pnpm will install the specified version of Node.js and use it for running any lifecycle scripts.
|
||||
5
.changeset/rotten-balloons-double.md
Normal file
5
.changeset/rotten-balloons-double.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-nvm": minor
|
||||
---
|
||||
|
||||
Project created.
|
||||
5
.changeset/violet-drinks-develop.md
Normal file
5
.changeset/violet-drinks-develop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
---
|
||||
|
||||
New settings are returned: pnpmExecPath and pnpmHomeDir.
|
||||
@@ -33,6 +33,8 @@ export interface Config {
|
||||
saveWorkspaceProtocol?: boolean
|
||||
scriptShell?: string
|
||||
stream?: boolean
|
||||
pnpmExecPath: string
|
||||
pnpmHomeDir: string
|
||||
production?: boolean
|
||||
fetchRetries?: number
|
||||
fetchRetryFactor?: number
|
||||
@@ -66,6 +68,7 @@ export interface Config {
|
||||
ignoreCurrentPrefs?: boolean
|
||||
recursive?: boolean
|
||||
enablePrePostScripts?: boolean
|
||||
useNodeVersion?: string
|
||||
useStderr?: boolean
|
||||
|
||||
// proxy
|
||||
|
||||
@@ -94,6 +94,7 @@ export const types = Object.assign({
|
||||
stream: Boolean,
|
||||
'strict-peer-dependencies': Boolean,
|
||||
'use-beta-cli': Boolean,
|
||||
'use-node-version': String,
|
||||
'use-running-store-server': Boolean,
|
||||
'use-store-server': Boolean,
|
||||
'use-stderr': Boolean,
|
||||
@@ -409,6 +410,19 @@ export default async (
|
||||
pnpmConfig.noProxy = pnpmConfig['noproxy'] ?? getProcessEnv('no_proxy')
|
||||
}
|
||||
pnpmConfig.enablePnp = pnpmConfig['nodeLinker'] === 'pnp'
|
||||
if (process['pkg'] != null) {
|
||||
// If the pnpm CLI was bundled by vercel/pkg then we cannot use the js path for npm_execpath
|
||||
// because in that case the js is in a virtual filesystem inside the executor.
|
||||
// Instead, we use the path to the exe file.
|
||||
pnpmConfig.pnpmExecPath = process.execPath
|
||||
pnpmConfig.pnpmHomeDir = path.dirname(pnpmConfig.pnpmExecPath)
|
||||
} else if (require.main != null) {
|
||||
pnpmConfig.pnpmExecPath = require.main.filename
|
||||
pnpmConfig.pnpmHomeDir = path.dirname(pnpmConfig.pnpmExecPath)
|
||||
} else {
|
||||
pnpmConfig.pnpmExecPath = process.cwd()
|
||||
pnpmConfig.pnpmHomeDir = process.cwd()
|
||||
}
|
||||
|
||||
if (opts.checkUnknownSetting) {
|
||||
const settingKeys = Object.keys({
|
||||
|
||||
15
packages/plugin-commands-nvm/README.md
Normal file
15
packages/plugin-commands-nvm/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @pnpm/plugin-commands-nvm
|
||||
|
||||
> pnpm commands for managing Node.js
|
||||
|
||||
[](https://www.npmjs.com/package/@pnpm/plugin-commands-nvm)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
<pnpm|npm|yarn> add @pnpm/plugin-commands-nvm
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
3
packages/plugin-commands-nvm/jest.config.js
Normal file
3
packages/plugin-commands-nvm/jest.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require('../../jest.config.js')
|
||||
|
||||
module.exports = config
|
||||
44
packages/plugin-commands-nvm/package.json
Normal file
44
packages/plugin-commands-nvm/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@pnpm/plugin-commands-nvm",
|
||||
"version": "0.0.0",
|
||||
"description": "pnpm commands for managing Node.js",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.17"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint -c ../../eslint.json src/**/*.ts test/**/*.ts",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix"
|
||||
},
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-nvm",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
"nvm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-nvm#readme",
|
||||
"dependencies": {
|
||||
"@pnpm/cli-utils": "workspace:0.6.4",
|
||||
"@pnpm/fetch": "workspace:3.1.0",
|
||||
"execa": "^5.0.0",
|
||||
"load-json-file": "^6.2.0",
|
||||
"path-name": "^1.0.0",
|
||||
"render-help": "^1.0.1",
|
||||
"write-json-file": "^4.3.0"
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"devDependencies": {
|
||||
"@pnpm/prepare": "workspace:0.0.23"
|
||||
}
|
||||
}
|
||||
3
packages/plugin-commands-nvm/src/index.ts
Normal file
3
packages/plugin-commands-nvm/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as node from './node'
|
||||
|
||||
export { node }
|
||||
97
packages/plugin-commands-nvm/src/node.ts
Normal file
97
packages/plugin-commands-nvm/src/node.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { docsUrl } from '@pnpm/cli-utils'
|
||||
import fetch from '@pnpm/fetch'
|
||||
import execa from 'execa'
|
||||
import PATH from 'path-name'
|
||||
import renderHelp from 'render-help'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
|
||||
export const rcOptionsTypes = () => ({})
|
||||
|
||||
export const cliOptionsTypes = () => ({})
|
||||
|
||||
export const shorthands = {}
|
||||
|
||||
export const commandNames = ['node']
|
||||
|
||||
export function help () {
|
||||
return renderHelp({
|
||||
description: 'Run Node.js',
|
||||
descriptionLists: [],
|
||||
url: docsUrl('node'),
|
||||
usages: ['pnpm node'],
|
||||
})
|
||||
}
|
||||
|
||||
export async function handler (
|
||||
opts: {
|
||||
argv: {
|
||||
original: string[]
|
||||
}
|
||||
useNodeVersion?: string
|
||||
pnpmHomeDir: string
|
||||
}
|
||||
) {
|
||||
const nodeDir = await getNodeDir(opts.pnpmHomeDir, opts.useNodeVersion)
|
||||
const { exitCode } = await execa('node', opts.argv.original.slice(1), {
|
||||
env: {
|
||||
[PATH]: `${path.join(nodeDir, 'node_modules/.bin')}${path.delimiter}${process.env[PATH]!}`,
|
||||
},
|
||||
stdout: 'inherit',
|
||||
stdin: 'inherit',
|
||||
})
|
||||
return { exitCode }
|
||||
}
|
||||
|
||||
export async function getNodeDir (pnpmHomeDir: string, nodeVersion?: string) {
|
||||
const nodesDir = path.join(pnpmHomeDir, 'nodes')
|
||||
let wantedNodeVersion = nodeVersion ?? (await readNodeVersionsManifest(nodesDir))?.default
|
||||
await fs.promises.mkdir(nodesDir, { recursive: true })
|
||||
fs.writeFileSync(path.join(nodesDir, 'pnpm-workspace.yaml'), '', 'utf8')
|
||||
if (wantedNodeVersion == null) {
|
||||
const response = await fetch('https://registry.npmjs.org/node')
|
||||
wantedNodeVersion = (await response.json())['dist-tags'].lts
|
||||
if (wantedNodeVersion == null) {
|
||||
throw new Error('Could not resolve LTS version of Node.js')
|
||||
}
|
||||
await writeJsonFile(path.join(nodesDir, 'versions.json'), {
|
||||
default: wantedNodeVersion,
|
||||
})
|
||||
}
|
||||
const versionDir = path.join(nodesDir, wantedNodeVersion)
|
||||
if (!fs.existsSync(versionDir)) {
|
||||
await installNode(wantedNodeVersion, versionDir)
|
||||
}
|
||||
return versionDir
|
||||
}
|
||||
|
||||
async function installNode (wantedNodeVersion: string, versionDir: string) {
|
||||
await fs.promises.mkdir(versionDir, { recursive: true })
|
||||
await writeJsonFile(path.join(versionDir, 'package.json'), {})
|
||||
const { exitCode } = await execa('pnpm', ['add', `${getNodePkgName()}@${wantedNodeVersion}`], {
|
||||
cwd: versionDir,
|
||||
stdout: 'inherit',
|
||||
})
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(`Couldn't install Node.js ${wantedNodeVersion}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getNodePkgName () {
|
||||
const platform = process.platform === 'win32' ? 'win' : process.platform
|
||||
const arch = platform === 'win' && process.arch === 'ia32' ? 'x86' : process.arch
|
||||
return `node-${platform}-${arch}`
|
||||
}
|
||||
|
||||
async function readNodeVersionsManifest (nodesDir: string): Promise<{ default?: string }> {
|
||||
try {
|
||||
return await loadJsonFile<{ default?: string }>(path.join(nodesDir, 'versions.json'))
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return {}
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
28
packages/plugin-commands-nvm/test/node.test.ts
Normal file
28
packages/plugin-commands-nvm/test/node.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import fs from 'fs'
|
||||
import { node } from '@pnpm/plugin-commands-nvm'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
|
||||
test('run specific version of Node.js', async () => {
|
||||
tempDir()
|
||||
const { exitCode } = await node.handler({
|
||||
argv: {
|
||||
original: ['node', '-e', 'require("fs").writeFileSync("version",process.version, "utf8")'],
|
||||
},
|
||||
useNodeVersion: '14.0.0',
|
||||
pnpmHomeDir: process.cwd(),
|
||||
})
|
||||
expect(exitCode).toBe(0)
|
||||
expect(fs.readFileSync('version', 'utf8')).toBe('v14.0.0')
|
||||
})
|
||||
|
||||
test('run LTS version of Node.js by default', async () => {
|
||||
tempDir()
|
||||
const { exitCode } = await node.handler({
|
||||
argv: {
|
||||
original: ['node', '-e', 'require("fs").writeFileSync("version",process.version, "utf8")'],
|
||||
},
|
||||
pnpmHomeDir: process.cwd(),
|
||||
})
|
||||
expect(exitCode).toBe(0)
|
||||
expect(fs.readFileSync('version', 'utf8')).toMatch(/^v[0-9]+\.[0-9]+\.[0-9]+$/)
|
||||
})
|
||||
16
packages/plugin-commands-nvm/tsconfig.json
Normal file
16
packages/plugin-commands-nvm/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../fetch"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
packages/plugin-commands-nvm/tsconfig.lint.json
Normal file
8
packages/plugin-commands-nvm/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../typings/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
1
packages/pnpm/.gitignore
vendored
Normal file
1
packages/pnpm/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bin/nodes
|
||||
@@ -42,6 +42,7 @@
|
||||
"@pnpm/plugin-commands-import": "workspace:2.0.11",
|
||||
"@pnpm/plugin-commands-installation": "workspace:4.1.7",
|
||||
"@pnpm/plugin-commands-listing": "workspace:3.0.7",
|
||||
"@pnpm/plugin-commands-nvm": "workspace:0.0.0",
|
||||
"@pnpm/plugin-commands-outdated": "workspace:4.1.4",
|
||||
"@pnpm/plugin-commands-publishing": "workspace:3.1.5",
|
||||
"@pnpm/plugin-commands-rebuild": "workspace:4.0.3",
|
||||
@@ -160,7 +161,7 @@
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client yarn --dest dist",
|
||||
"postpublish": "publish-packed",
|
||||
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix && rimraf dist && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts && shx cp -r node_modules/ps-list/vendor dist/vendor && pkg ./dist/pnpm.cjs --out-path=../artifacts/win-x64 --targets=node14-win-x64 && pkg ./dist/pnpm.cjs --out-path=../artifacts/linux-x64 --targets=node14-linux-x64 && pkg ./dist/pnpm.cjs --out-path=../artifacts/macos-x64 --targets=node14-macos-x64"
|
||||
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && pnpm run lint -- --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts && shx cp -r node_modules/ps-list/vendor dist/vendor && pkg ./dist/pnpm.cjs --out-path=../artifacts/win-x64 --targets=node14-win-x64 && pkg ./dist/pnpm.cjs --out-path=../artifacts/linux-x64 --targets=node14-linux-x64 && pkg ./dist/pnpm.cjs --out-path=../artifacts/macos-x64 --targets=node14-macos-x64"
|
||||
},
|
||||
"publishconfig": {
|
||||
"tag": "next"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { audit } from '@pnpm/plugin-commands-audit'
|
||||
import { importCommand } from '@pnpm/plugin-commands-import'
|
||||
import { add, fetch, install, link, prune, remove, unlink, update } from '@pnpm/plugin-commands-installation'
|
||||
import { list, ll, why } from '@pnpm/plugin-commands-listing'
|
||||
import { node } from '@pnpm/plugin-commands-nvm'
|
||||
import { outdated } from '@pnpm/plugin-commands-outdated'
|
||||
import { pack, publish } from '@pnpm/plugin-commands-publishing'
|
||||
import { rebuild } from '@pnpm/plugin-commands-rebuild'
|
||||
@@ -68,6 +69,7 @@ const commands: Array<{
|
||||
link,
|
||||
list,
|
||||
ll,
|
||||
node,
|
||||
outdated,
|
||||
pack,
|
||||
prune,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { filterPackages } from '@pnpm/filter-workspace-packages'
|
||||
import findWorkspacePackages from '@pnpm/find-workspace-packages'
|
||||
import logger from '@pnpm/logger'
|
||||
import { ParsedCliArgs } from '@pnpm/parse-cli-args'
|
||||
import { node } from '@pnpm/plugin-commands-nvm'
|
||||
import chalk from 'chalk'
|
||||
import checkForUpdates from './checkForUpdates'
|
||||
import pnpmCmds, { rcOptionsTypes } from './cmd'
|
||||
@@ -61,7 +62,7 @@ export default async function run (inputArgv: string[]) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (unknownOptions.size > 0) {
|
||||
if (unknownOptions.size > 0 && cmd !== 'node') {
|
||||
const unknownOptionsArray = Array.from(unknownOptions.keys())
|
||||
if (unknownOptionsArray.every((option) => DEPRECATED_OPTIONS.has(option))) {
|
||||
let deprecationMsg = `${chalk.bgYellow.black('\u2009WARN\u2009')}`
|
||||
@@ -225,6 +226,10 @@ export default async function run (inputArgv: string[]) {
|
||||
})
|
||||
|
||||
try {
|
||||
if (config.useNodeVersion != null) {
|
||||
const nodePath = path.join(await node.getNodeDir(config.pnpmHomeDir, config.useNodeVersion), 'node_modules/.bin')
|
||||
config.extraBinPaths.push(nodePath)
|
||||
}
|
||||
let result = pnpmCmds[cmd ?? 'help'](
|
||||
// TypeScript doesn't currently infer that the type of config
|
||||
// is `Omit<typeof config, 'reporter'>` after the `delete config.reporter` statement
|
||||
|
||||
@@ -170,3 +170,14 @@ test('the bundled CLI prints the correct version, when executed from stdin', asy
|
||||
const { version } = await loadJsonFile<{ version: string }>(path.join(__dirname, '../package.json'))
|
||||
expect((await nodeProcess).stdout).toBe(version)
|
||||
})
|
||||
|
||||
test('use the specified Node.js version for running scripts', async () => {
|
||||
prepare({
|
||||
scripts: {
|
||||
test: "node -e \"require('fs').writeFileSync('version',process.version,'utf8')\"",
|
||||
},
|
||||
})
|
||||
await fs.writeFile('.npmrc', 'use-node-version=14.0.0', 'utf8')
|
||||
await execPnpm(['run', 'test'])
|
||||
expect(await fs.readFile('version', 'utf8')).toBe('v14.0.0')
|
||||
})
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
{
|
||||
"path": "../plugin-commands-listing"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-commands-nvm"
|
||||
},
|
||||
{
|
||||
"path": "../plugin-commands-outdated"
|
||||
},
|
||||
|
||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@@ -1925,6 +1925,29 @@ importers:
|
||||
strip-ansi: 6.0.0
|
||||
write-yaml-file: 4.2.0
|
||||
|
||||
packages/plugin-commands-nvm:
|
||||
specifiers:
|
||||
'@pnpm/cli-utils': workspace:0.6.4
|
||||
'@pnpm/fetch': workspace:3.1.0
|
||||
'@pnpm/plugin-commands-nvm': 'link:'
|
||||
'@pnpm/prepare': workspace:0.0.23
|
||||
execa: ^5.0.0
|
||||
load-json-file: ^6.2.0
|
||||
path-name: ^1.0.0
|
||||
render-help: ^1.0.1
|
||||
write-json-file: ^4.3.0
|
||||
dependencies:
|
||||
'@pnpm/cli-utils': link:../cli-utils
|
||||
'@pnpm/fetch': link:../fetch
|
||||
execa: 5.0.0
|
||||
load-json-file: 6.2.0
|
||||
path-name: 1.0.0
|
||||
render-help: 1.0.2
|
||||
write-json-file: 4.3.0
|
||||
devDependencies:
|
||||
'@pnpm/plugin-commands-nvm': 'link:'
|
||||
'@pnpm/prepare': link:../../privatePackages/prepare
|
||||
|
||||
packages/plugin-commands-outdated:
|
||||
specifiers:
|
||||
'@pnpm/cli-utils': workspace:0.6.4
|
||||
@@ -2354,6 +2377,7 @@ importers:
|
||||
'@pnpm/plugin-commands-import': workspace:2.0.11
|
||||
'@pnpm/plugin-commands-installation': workspace:4.1.7
|
||||
'@pnpm/plugin-commands-listing': workspace:3.0.7
|
||||
'@pnpm/plugin-commands-nvm': workspace:0.0.0
|
||||
'@pnpm/plugin-commands-outdated': workspace:4.1.4
|
||||
'@pnpm/plugin-commands-publishing': workspace:3.1.5
|
||||
'@pnpm/plugin-commands-rebuild': workspace:4.0.3
|
||||
@@ -2446,6 +2470,7 @@ importers:
|
||||
'@pnpm/plugin-commands-import': link:../plugin-commands-import
|
||||
'@pnpm/plugin-commands-installation': link:../plugin-commands-installation
|
||||
'@pnpm/plugin-commands-listing': link:../plugin-commands-listing
|
||||
'@pnpm/plugin-commands-nvm': link:../plugin-commands-nvm
|
||||
'@pnpm/plugin-commands-outdated': link:../plugin-commands-outdated
|
||||
'@pnpm/plugin-commands-publishing': link:../plugin-commands-publishing
|
||||
'@pnpm/plugin-commands-rebuild': link:../plugin-commands-rebuild
|
||||
|
||||
@@ -162,7 +162,7 @@ async function updateManifest (workspaceDir: string, manifest: ProjectManifest,
|
||||
type: 'git',
|
||||
url: 'git+https://github.com/pnpm/pnpm.git',
|
||||
}
|
||||
scripts.compile += ' && rimraf dist && pnpm run bundle \
|
||||
scripts.compile += ' && rimraf dist bin/nodes && pnpm run bundle \
|
||||
&& shx cp -r node-gyp-bin dist/node-gyp-bin \
|
||||
&& shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts \
|
||||
&& shx cp -r node_modules/ps-list/vendor dist/vendor \
|
||||
|
||||
Reference in New Issue
Block a user