mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-11 09:28:24 -04:00
feat(env): install Node.js using a version range (#3629)
This commit is contained in:
5
.changeset/odd-drinks-care.md
Normal file
5
.changeset/odd-drinks-care.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-env": patch
|
||||
---
|
||||
|
||||
Allow to install a Node.js version using a semver range.
|
||||
@@ -41,7 +41,9 @@
|
||||
"load-json-file": "^6.2.0",
|
||||
"rename-overwrite": "^4.0.0",
|
||||
"render-help": "^1.0.1",
|
||||
"semver": "^7.3.4",
|
||||
"tempy": "^1.0.0",
|
||||
"version-selector-type": "^3.0.0",
|
||||
"write-json-file": "^4.3.0"
|
||||
},
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import path from 'path'
|
||||
import { docsUrl } from '@pnpm/cli-utils'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import fetch from '@pnpm/fetch'
|
||||
import cmdShim from '@zkochan/cmd-shim'
|
||||
import renderHelp from 'render-help'
|
||||
import semver from 'semver'
|
||||
import versionSelectorType from 'version-selector-type'
|
||||
import { getNodeDir, NvmNodeCommandOptions } from './node'
|
||||
|
||||
export function rcOptionsTypes () {
|
||||
@@ -36,6 +39,9 @@ export function help () {
|
||||
url: docsUrl('env'),
|
||||
usages: [
|
||||
'pnpm env use --global <version>',
|
||||
'pnpm env use --global 16',
|
||||
'pnpm env use --global lts',
|
||||
'pnpm env use --global argon',
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -49,14 +55,18 @@ export async function handler (opts: NvmNodeCommandOptions, params: string[]) {
|
||||
if (!opts.global) {
|
||||
throw new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use <version>" can only be used with the "--global" option currently')
|
||||
}
|
||||
const nodeVersion = await resolveNodeVersion(params[1])
|
||||
if (!nodeVersion) {
|
||||
throw new PnpmError('COULD_NOT_RESOLVE_NODEJS', `Couldn't find Node.js version matching ${params[1]}`)
|
||||
}
|
||||
const nodeDir = await getNodeDir({
|
||||
...opts,
|
||||
useNodeVersion: params[1],
|
||||
useNodeVersion: nodeVersion,
|
||||
})
|
||||
const src = path.join(nodeDir, process.platform === 'win32' ? 'node.exe' : 'node')
|
||||
const dest = path.join(opts.bin, 'node')
|
||||
await cmdShim(src, dest)
|
||||
return `Node.js ${params[1]} is activated
|
||||
return `Node.js ${nodeVersion} is activated
|
||||
${dest} -> ${src}`
|
||||
}
|
||||
default: {
|
||||
@@ -64,3 +74,35 @@ export async function handler (opts: NvmNodeCommandOptions, params: string[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface NodeVersion {
|
||||
version: string
|
||||
lts: false | string
|
||||
}
|
||||
|
||||
async function resolveNodeVersion (rawVersionSelector: string) {
|
||||
const response = await fetch('https://nodejs.org/download/release/index.json')
|
||||
const allVersions = (await response.json()) as NodeVersion[]
|
||||
const { versions, versionSelector } = filterVersions(allVersions, rawVersionSelector)
|
||||
const pickedVersion = semver.maxSatisfying(versions.map(({ version }) => version), versionSelector)
|
||||
if (!pickedVersion) return null
|
||||
return pickedVersion.substring(1)
|
||||
}
|
||||
|
||||
function filterVersions (versions: NodeVersion[], versionSelector: string) {
|
||||
if (versionSelector === 'lts') {
|
||||
return {
|
||||
versions: versions.filter(({ lts }) => lts !== false),
|
||||
versionSelector: '*',
|
||||
}
|
||||
}
|
||||
const vst = versionSelectorType(versionSelector)
|
||||
if (vst?.type === 'tag') {
|
||||
const wantedLtsVersion = vst.normalized.toLowerCase()
|
||||
return {
|
||||
versions: versions.filter(({ lts }) => typeof lts === 'string' && lts.toLowerCase() === wantedLtsVersion),
|
||||
versionSelector: '*',
|
||||
}
|
||||
}
|
||||
return { versions, versionSelector }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import PnpmError from '@pnpm/error'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
import { env } from '@pnpm/plugin-commands-env'
|
||||
import execa from 'execa'
|
||||
import PATH from 'path-name'
|
||||
|
||||
test('install node', async () => {
|
||||
test('install Node by exact version', async () => {
|
||||
tempDir()
|
||||
|
||||
await env.handler({
|
||||
@@ -25,3 +26,92 @@ test('install node', async () => {
|
||||
const dirs = fs.readdirSync(path.resolve('nodejs'))
|
||||
expect(dirs).toEqual(['16.4.0'])
|
||||
})
|
||||
|
||||
test('install Node by version range', async () => {
|
||||
tempDir()
|
||||
|
||||
await env.handler({
|
||||
bin: process.cwd(),
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
}, ['use', '6'])
|
||||
|
||||
const { stdout } = execa.sync('node', ['-v'], {
|
||||
env: {
|
||||
[PATH]: `${process.cwd()}${path.delimiter}${process.env[PATH] as string}`,
|
||||
},
|
||||
})
|
||||
expect(stdout.toString()).toBe('v6.17.1')
|
||||
|
||||
const dirs = fs.readdirSync(path.resolve('nodejs'))
|
||||
expect(dirs).toEqual(['6.17.1'])
|
||||
})
|
||||
|
||||
test('install the LTS version of Node', async () => {
|
||||
tempDir()
|
||||
|
||||
await env.handler({
|
||||
bin: process.cwd(),
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
}, ['use', 'lts'])
|
||||
|
||||
const { stdout: version } = execa.sync('node', ['-v'], {
|
||||
env: {
|
||||
[PATH]: `${process.cwd()}${path.delimiter}${process.env[PATH] as string}`,
|
||||
},
|
||||
})
|
||||
expect(version).toBeTruthy()
|
||||
|
||||
const dirs = fs.readdirSync(path.resolve('nodejs'))
|
||||
expect(dirs).toEqual([version.substring(1)])
|
||||
})
|
||||
|
||||
test('install Node by its LTS name', async () => {
|
||||
tempDir()
|
||||
|
||||
await env.handler({
|
||||
bin: process.cwd(),
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
}, ['use', 'argon'])
|
||||
|
||||
const { stdout: version } = execa.sync('node', ['-v'], {
|
||||
env: {
|
||||
[PATH]: `${process.cwd()}${path.delimiter}${process.env[PATH] as string}`,
|
||||
},
|
||||
})
|
||||
expect(version).toBe('v4.9.1')
|
||||
|
||||
const dirs = fs.readdirSync(path.resolve('nodejs'))
|
||||
expect(dirs).toEqual([version.substring(1)])
|
||||
})
|
||||
|
||||
test('fail if a non-existend Node.js version is tried to be installed', async () => {
|
||||
tempDir()
|
||||
|
||||
await expect(
|
||||
env.handler({
|
||||
bin: process.cwd(),
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
}, ['use', '6.999'])
|
||||
).rejects.toEqual(new PnpmError('COULD_NOT_RESOLVE_NODEJS', 'Couldn\'t find Node.js version matching 6.999'))
|
||||
})
|
||||
|
||||
test('fail if a non-existend Node.js LTS is tried to be installed', async () => {
|
||||
tempDir()
|
||||
|
||||
await expect(
|
||||
env.handler({
|
||||
bin: process.cwd(),
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
}, ['use', 'boo'])
|
||||
).rejects.toEqual(new PnpmError('COULD_NOT_RESOLVE_NODEJS', 'Couldn\'t find Node.js version matching boo'))
|
||||
})
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -1777,7 +1777,9 @@ importers:
|
||||
path-name: ^1.0.0
|
||||
rename-overwrite: ^4.0.0
|
||||
render-help: ^1.0.1
|
||||
semver: ^7.3.4
|
||||
tempy: ^1.0.0
|
||||
version-selector-type: ^3.0.0
|
||||
write-json-file: ^4.3.0
|
||||
dependencies:
|
||||
'@pnpm/cli-utils': link:../cli-utils
|
||||
@@ -1792,7 +1794,9 @@ importers:
|
||||
load-json-file: 6.2.0
|
||||
rename-overwrite: 4.0.0
|
||||
render-help: 1.0.2
|
||||
semver: 7.3.5
|
||||
tempy: 1.0.1
|
||||
version-selector-type: 3.0.0
|
||||
write-json-file: 4.3.0
|
||||
devDependencies:
|
||||
'@pnpm/plugin-commands-env': 'link:'
|
||||
|
||||
Reference in New Issue
Block a user