mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-27 18:46:18 -04:00
feat: add pnpm runtime set command and deprecate pnpm env use (#10671)
* feat: add `pnpm runtime set` command and deprecate `pnpm env use` Add a new `pnpm runtime set <name> <version> [-g]` command (alias: `rt`) in a new `@pnpm/runtime.commands` package. It delegates to `pnpm add <name>@runtime:<version>` supporting node, deno, and bun. `pnpm env use` now prints a deprecation warning pointing users to the new command. * fix: address PR review feedback for runtime command - Use opts.dir as cwd when --global is not passed (instead of always using pnpmHomeDir) - Only pass --global-bin-dir when in global mode - Keep deprecated env command in help output for discoverability - Add Jest tests for the runtime command (7 tests)
This commit is contained in:
7
.changeset/runtime-set-command.md
Normal file
7
.changeset/runtime-set-command.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-env": patch
|
||||
"@pnpm/runtime.commands": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Added a new command `pnpm runtime set <runtime name> <runtime version spec> [-g]` for installing runtimes. Deprecated `pnpm env use` in favor of the new command.
|
||||
2
env/plugin-commands-env/src/envUse.ts
vendored
2
env/plugin-commands-env/src/envUse.ts
vendored
@@ -1,8 +1,10 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { runPnpmCli } from '@pnpm/exec.pnpm-cli-runner'
|
||||
import { globalWarn } from '@pnpm/logger'
|
||||
import { type NvmNodeCommandOptions } from './node.js'
|
||||
|
||||
export async function envUse (opts: NvmNodeCommandOptions, params: string[]): Promise<void> {
|
||||
globalWarn('"pnpm env use" is deprecated. Use "pnpm runtime set node <version> -g" instead.')
|
||||
if (!opts.global) {
|
||||
throw new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use <version>" can only be used with the "--global" option currently')
|
||||
}
|
||||
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -6720,6 +6720,9 @@ importers:
|
||||
'@pnpm/run-npm':
|
||||
specifier: workspace:*
|
||||
version: link:../exec/run-npm
|
||||
'@pnpm/runtime.commands':
|
||||
specifier: workspace:*
|
||||
version: link:../runtime/commands
|
||||
'@pnpm/store.cafs':
|
||||
specifier: workspace:*
|
||||
version: link:../store/cafs
|
||||
@@ -8279,6 +8282,37 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 7.1.5
|
||||
|
||||
runtime/commands:
|
||||
dependencies:
|
||||
'@pnpm/cli-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../cli/cli-utils
|
||||
'@pnpm/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../config/config
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/exec.pnpm-cli-runner':
|
||||
specifier: workspace:*
|
||||
version: link:../../exec/pnpm-cli-runner
|
||||
render-help:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.3
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: 'catalog:'
|
||||
version: 30.0.5
|
||||
'@pnpm/logger':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/logger
|
||||
'@pnpm/runtime.commands':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
cross-env:
|
||||
specifier: 'catalog:'
|
||||
version: 10.1.0
|
||||
|
||||
semver/peer-range:
|
||||
dependencies:
|
||||
semver:
|
||||
|
||||
@@ -37,6 +37,7 @@ packages:
|
||||
- releasing/*
|
||||
- resolving/*
|
||||
- reviewing/*
|
||||
- runtime/*
|
||||
- semver/*
|
||||
- store/*
|
||||
- text/*
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"@pnpm/plugin-commands-store-inspecting": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@pnpm/read-package-json": "workspace:*",
|
||||
"@pnpm/runtime.commands": "workspace:*",
|
||||
"@pnpm/read-project-manifest": "workspace:*",
|
||||
"@pnpm/registry-mock": "catalog:",
|
||||
"@pnpm/run-npm": "workspace:*",
|
||||
|
||||
@@ -243,10 +243,6 @@ function getHelpText ({ all }: { all: boolean }): string {
|
||||
description: 'Publishes a package to the registry',
|
||||
name: 'publish',
|
||||
},
|
||||
{
|
||||
description: 'Updates pnpm to the latest version',
|
||||
name: 'self-update',
|
||||
},
|
||||
{
|
||||
description: 'Create a package.json file',
|
||||
name: 'init',
|
||||
@@ -279,13 +275,22 @@ function getHelpText ({ all }: { all: boolean }): string {
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Manage your environments',
|
||||
title: 'Manage your engines',
|
||||
advanced: true,
|
||||
|
||||
list: [
|
||||
{
|
||||
description: 'Manage Node.js versions',
|
||||
name: 'env ',
|
||||
description: 'Manage runtimes',
|
||||
name: 'runtime',
|
||||
shortAlias: 'rt',
|
||||
},
|
||||
{
|
||||
description: 'Manage Node.js versions (deprecated, use runtime)',
|
||||
name: 'env',
|
||||
},
|
||||
{
|
||||
description: 'Updates pnpm to the latest version',
|
||||
name: 'self-update',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ import { generateCompletion, createCompletionServer } from '@pnpm/plugin-command
|
||||
import { config, getCommand, setCommand } from '@pnpm/plugin-commands-config'
|
||||
import { doctor } from '@pnpm/plugin-commands-doctor'
|
||||
import { env } from '@pnpm/plugin-commands-env'
|
||||
import { runtime } from '@pnpm/runtime.commands'
|
||||
import { deploy } from '@pnpm/plugin-commands-deploy'
|
||||
import { add, ci, dedupe, fetch, install, link, prune, remove, unlink, update, importCommand } from '@pnpm/plugin-commands-installation'
|
||||
import { selfUpdate } from '@pnpm/tools.plugin-commands-self-updater'
|
||||
@@ -130,6 +131,7 @@ const commands: CommandDefinition[] = [
|
||||
doctor,
|
||||
env,
|
||||
exec,
|
||||
runtime,
|
||||
fetch,
|
||||
generateCompletion,
|
||||
ignoredBuilds,
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
{
|
||||
"path": "../reviewing/plugin-commands-sbom"
|
||||
},
|
||||
{
|
||||
"path": "../runtime/commands"
|
||||
},
|
||||
{
|
||||
"path": "../store/cafs"
|
||||
},
|
||||
|
||||
56
runtime/commands/package.json
Normal file
56
runtime/commands/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@pnpm/runtime.commands",
|
||||
"version": "1000.0.0-0",
|
||||
"description": "pnpm commands for managing runtimes",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
"pnpm11",
|
||||
"runtime"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"repository": "https://github.com/pnpm/pnpm/tree/main/runtime/commands",
|
||||
"homepage": "https://github.com/pnpm/pnpm/tree/main/runtime/commands#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"_test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "tsgo --build && pnpm run lint --fix",
|
||||
"test": "pnpm run compile && pnpm run _test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/cli-utils": "workspace:*",
|
||||
"@pnpm/config": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/exec.pnpm-cli-runner": "workspace:*",
|
||||
"render-help": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pnpm/logger": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "catalog:",
|
||||
"@pnpm/logger": "workspace:*",
|
||||
"@pnpm/runtime.commands": "workspace:*",
|
||||
"cross-env": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.13"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@pnpm/jest-config"
|
||||
}
|
||||
}
|
||||
3
runtime/commands/src/index.ts
Normal file
3
runtime/commands/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as runtime from './runtime.js'
|
||||
|
||||
export { runtime }
|
||||
101
runtime/commands/src/runtime.ts
Normal file
101
runtime/commands/src/runtime.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { docsUrl } from '@pnpm/cli-utils'
|
||||
import { type Config } from '@pnpm/config'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { runPnpmCli } from '@pnpm/exec.pnpm-cli-runner'
|
||||
import renderHelp from 'render-help'
|
||||
|
||||
export type RuntimeCommandOptions = Pick<Config,
|
||||
| 'bin'
|
||||
| 'dir'
|
||||
| 'global'
|
||||
| 'pnpmHomeDir'
|
||||
> & Partial<Pick<Config,
|
||||
| 'storeDir'
|
||||
| 'cacheDir'
|
||||
>>
|
||||
|
||||
export const skipPackageManagerCheck = true
|
||||
|
||||
export function rcOptionsTypes (): Record<string, unknown> {
|
||||
return {}
|
||||
}
|
||||
|
||||
export function cliOptionsTypes (): Record<string, unknown> {
|
||||
return {
|
||||
global: Boolean,
|
||||
}
|
||||
}
|
||||
|
||||
export const commandNames = ['runtime', 'rt']
|
||||
|
||||
export function help (): string {
|
||||
return renderHelp({
|
||||
description: 'Manage runtimes.',
|
||||
descriptionLists: [
|
||||
{
|
||||
title: 'Commands',
|
||||
list: [
|
||||
{
|
||||
description: 'Installs the specified version of a runtime (e.g. node, deno, bun).',
|
||||
name: 'set',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Options',
|
||||
list: [
|
||||
{
|
||||
description: 'Installs the runtime globally',
|
||||
name: '--global',
|
||||
shortAlias: '-g',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
url: docsUrl('runtime'),
|
||||
usages: [
|
||||
'pnpm runtime set node 22 -g',
|
||||
'pnpm runtime set node lts -g',
|
||||
'pnpm runtime set node rc/22 -g',
|
||||
'pnpm runtime set deno 2 -g',
|
||||
'pnpm runtime set bun latest -g',
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export async function handler (opts: RuntimeCommandOptions, params: string[]): Promise<void> {
|
||||
if (params.length === 0) {
|
||||
throw new PnpmError('RUNTIME_NO_SUBCOMMAND', 'Please specify the subcommand', {
|
||||
hint: help(),
|
||||
})
|
||||
}
|
||||
switch (params[0]) {
|
||||
case 'set': {
|
||||
runtimeSet(opts, params.slice(1))
|
||||
return
|
||||
}
|
||||
default: {
|
||||
throw new PnpmError('RUNTIME_UNKNOWN_SUBCOMMAND', `Unknown subcommand: ${params[0]}`, {
|
||||
hint: help(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeSet (opts: RuntimeCommandOptions, params: string[]): void {
|
||||
const runtimeName = params[0]?.trim()
|
||||
if (!runtimeName) {
|
||||
throw new PnpmError('MISSING_RUNTIME_NAME', '"pnpm runtime set <name> <version>" requires a runtime name (e.g. node, deno, bun)')
|
||||
}
|
||||
|
||||
const versionSpec = params[1]?.trim()
|
||||
|
||||
const args = ['add', `${runtimeName}@runtime:${versionSpec ?? ''}`]
|
||||
if (opts.global) {
|
||||
args.push('--global')
|
||||
if (opts.bin) args.push('--global-bin-dir', opts.bin)
|
||||
}
|
||||
if (opts.storeDir) args.push('--store-dir', opts.storeDir)
|
||||
if (opts.cacheDir) args.push('--cache-dir', opts.cacheDir)
|
||||
runPnpmCli(args, { cwd: opts.global ? opts.pnpmHomeDir : opts.dir })
|
||||
}
|
||||
110
runtime/commands/test/runtime.test.ts
Normal file
110
runtime/commands/test/runtime.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
|
||||
const mockRunPnpmCli = jest.fn()
|
||||
jest.unstable_mockModule('@pnpm/exec.pnpm-cli-runner', () => ({
|
||||
runPnpmCli: mockRunPnpmCli,
|
||||
}))
|
||||
|
||||
const { runtime } = await import('@pnpm/runtime.commands')
|
||||
|
||||
beforeEach(() => {
|
||||
mockRunPnpmCli.mockClear()
|
||||
})
|
||||
|
||||
test('runtime set calls pnpm add with the correct arguments globally', async () => {
|
||||
await runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
cacheDir: '/tmp/cache',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
storeDir: '/tmp/store',
|
||||
}, ['set', 'node', '22'])
|
||||
|
||||
expect(mockRunPnpmCli).toHaveBeenCalledWith(
|
||||
['add', 'node@runtime:22', '--global', '--global-bin-dir', '/usr/local/bin', '--store-dir', '/tmp/store', '--cache-dir', '/tmp/cache'],
|
||||
{ cwd: '/tmp/pnpm-home' }
|
||||
)
|
||||
})
|
||||
|
||||
test('runtime set uses project dir when not global', async () => {
|
||||
await runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: false,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, ['set', 'node', '22'])
|
||||
|
||||
expect(mockRunPnpmCli).toHaveBeenCalledWith(
|
||||
['add', 'node@runtime:22'],
|
||||
{ cwd: '/tmp/project' }
|
||||
)
|
||||
})
|
||||
|
||||
test('runtime set without version spec', async () => {
|
||||
await runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, ['set', 'node'])
|
||||
|
||||
expect(mockRunPnpmCli).toHaveBeenCalledWith(
|
||||
['add', 'node@runtime:', '--global', '--global-bin-dir', '/usr/local/bin'],
|
||||
{ cwd: '/tmp/pnpm-home' }
|
||||
)
|
||||
})
|
||||
|
||||
test('runtime set works with deno', async () => {
|
||||
await runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, ['set', 'deno', '2'])
|
||||
|
||||
expect(mockRunPnpmCli).toHaveBeenCalledWith(
|
||||
['add', 'deno@runtime:2', '--global', '--global-bin-dir', '/usr/local/bin'],
|
||||
{ cwd: '/tmp/pnpm-home' }
|
||||
)
|
||||
})
|
||||
|
||||
test('fail if no subcommand is given', async () => {
|
||||
await expect(
|
||||
runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, [])
|
||||
).rejects.toEqual(new PnpmError('RUNTIME_NO_SUBCOMMAND', 'Please specify the subcommand'))
|
||||
|
||||
expect(mockRunPnpmCli).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('fail if unknown subcommand is given', async () => {
|
||||
await expect(
|
||||
runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, ['foo'])
|
||||
).rejects.toEqual(new PnpmError('RUNTIME_UNKNOWN_SUBCOMMAND', 'Unknown subcommand: foo'))
|
||||
|
||||
expect(mockRunPnpmCli).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('fail if runtime name is missing', async () => {
|
||||
await expect(
|
||||
runtime.handler({
|
||||
bin: '/usr/local/bin',
|
||||
dir: '/tmp/project',
|
||||
global: true,
|
||||
pnpmHomeDir: '/tmp/pnpm-home',
|
||||
}, ['set'])
|
||||
).rejects.toEqual(new PnpmError('MISSING_RUNTIME_NAME', '"pnpm runtime set <name> <version>" requires a runtime name (e.g. node, deno, bun)'))
|
||||
|
||||
expect(mockRunPnpmCli).not.toHaveBeenCalled()
|
||||
})
|
||||
18
runtime/commands/test/tsconfig.json
Normal file
18
runtime/commands/test/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"outDir": "../node_modules/.test.lib",
|
||||
"rootDir": "..",
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
||||
28
runtime/commands/tsconfig.json
Normal file
28
runtime/commands/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../cli/cli-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../config/config"
|
||||
},
|
||||
{
|
||||
"path": "../../exec/pnpm-cli-runner"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/logger"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
runtime/commands/tsconfig.lint.json
Normal file
8
runtime/commands/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user