mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-25 15:07:06 -04:00
fix(env): use-node-version should work with prerelease versions (#4903)
This commit is contained in:
11
.changeset/violet-pots-occur.md
Normal file
11
.changeset/violet-pots-occur.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-env": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
The `use-node-version` setting should work with prerelease Node.js versions. For instance:
|
||||
|
||||
```
|
||||
use-node-version=18.0.0-rc.3
|
||||
```
|
||||
|
||||
@@ -6,7 +6,9 @@ import { createFetchFromRegistry } from '@pnpm/fetch'
|
||||
import cmdShim from '@zkochan/cmd-shim'
|
||||
import renderHelp from 'render-help'
|
||||
import { getNodeDir, NvmNodeCommandOptions } from './node'
|
||||
import getNodeMirror from './getNodeMirror'
|
||||
import resolveNodeVersion from './resolveNodeVersion'
|
||||
import { parseNodeVersionSelector } from './parseNodeVersionSelector'
|
||||
|
||||
export function rcOptionsTypes () {
|
||||
return {}
|
||||
@@ -58,14 +60,16 @@ export async function handler (opts: NvmNodeCommandOptions, params: string[]) {
|
||||
throw new PnpmError('NOT_IMPLEMENTED_YET', '"pnpm env use <version>" can only be used with the "--global" option currently')
|
||||
}
|
||||
const fetch = createFetchFromRegistry(opts)
|
||||
const { version: nodeVersion, releaseDir } = await resolveNodeVersion(fetch, params[1], opts.rawConfig)
|
||||
const { releaseDir, version } = parseNodeVersionSelector(params[1])
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseDir)
|
||||
const nodeVersion = await resolveNodeVersion(fetch, version, nodeMirrorBaseUrl)
|
||||
if (!nodeVersion) {
|
||||
throw new PnpmError('COULD_NOT_RESOLVE_NODEJS', `Couldn't find Node.js version matching ${params[1]}`)
|
||||
}
|
||||
const nodeDir = await getNodeDir(fetch, {
|
||||
...opts,
|
||||
useNodeVersion: nodeVersion,
|
||||
releaseDir,
|
||||
nodeMirrorBaseUrl,
|
||||
})
|
||||
const src = path.join(nodeDir, process.platform === 'win32' ? 'node.exe' : 'bin/node')
|
||||
const dest = path.join(opts.bin, process.platform === 'win32' ? 'node.exe' : 'node')
|
||||
|
||||
@@ -13,6 +13,7 @@ import loadJsonFile from 'load-json-file'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
import normalizeArch from './normalizeArch'
|
||||
import getNodeMirror from './getNodeMirror'
|
||||
import { parseNodeVersionSelector } from './parseNodeVersionSelector'
|
||||
|
||||
export type NvmNodeCommandOptions = Pick<Config,
|
||||
| 'bin'
|
||||
@@ -39,14 +40,8 @@ export type NvmNodeCommandOptions = Pick<Config,
|
||||
|
||||
export async function getNodeBinDir (opts: NvmNodeCommandOptions) {
|
||||
const fetch = createFetchFromRegistry(opts)
|
||||
const nodeDir = await getNodeDir(fetch, opts)
|
||||
return process.platform === 'win32' ? nodeDir : path.join(nodeDir, 'bin')
|
||||
}
|
||||
|
||||
export async function getNodeDir (fetch: FetchFromRegistry, opts: NvmNodeCommandOptions & { releaseDir?: string }) {
|
||||
const nodesDir = path.join(opts.pnpmHomeDir, 'nodejs')
|
||||
const nodesDir = getNodeVersionsBaseDir(opts.pnpmHomeDir)
|
||||
let wantedNodeVersion = opts.useNodeVersion ?? (await readNodeVersionsManifest(nodesDir))?.default
|
||||
await fs.promises.mkdir(nodesDir, { recursive: true })
|
||||
if (wantedNodeVersion == null) {
|
||||
const response = await fetch('https://registry.npmjs.org/node')
|
||||
wantedNodeVersion = (await response.json() as any)['dist-tags'].lts // eslint-disable-line
|
||||
@@ -57,16 +52,32 @@ export async function getNodeDir (fetch: FetchFromRegistry, opts: NvmNodeCommand
|
||||
default: wantedNodeVersion,
|
||||
})
|
||||
}
|
||||
const versionDir = path.join(nodesDir, wantedNodeVersion)
|
||||
const { version, releaseDir } = parseNodeVersionSelector(wantedNodeVersion)
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseDir)
|
||||
const nodeDir = await getNodeDir(fetch, {
|
||||
...opts,
|
||||
useNodeVersion: version,
|
||||
nodeMirrorBaseUrl,
|
||||
})
|
||||
return process.platform === 'win32' ? nodeDir : path.join(nodeDir, 'bin')
|
||||
}
|
||||
|
||||
function getNodeVersionsBaseDir (pnpmHomeDir: string) {
|
||||
return path.join(pnpmHomeDir, 'nodejs')
|
||||
}
|
||||
|
||||
export async function getNodeDir (fetch: FetchFromRegistry, opts: NvmNodeCommandOptions & { useNodeVersion: string, nodeMirrorBaseUrl: string }) {
|
||||
const nodesDir = getNodeVersionsBaseDir(opts.pnpmHomeDir)
|
||||
await fs.promises.mkdir(nodesDir, { recursive: true })
|
||||
const versionDir = path.join(nodesDir, opts.useNodeVersion)
|
||||
if (!fs.existsSync(versionDir)) {
|
||||
await installNode(fetch, wantedNodeVersion, versionDir, opts)
|
||||
await installNode(fetch, opts.useNodeVersion, versionDir, opts)
|
||||
}
|
||||
return versionDir
|
||||
}
|
||||
|
||||
async function installNode (fetch: FetchFromRegistry, wantedNodeVersion: string, versionDir: string, opts: NvmNodeCommandOptions & { releaseDir?: string }) {
|
||||
const nodeMirror = getNodeMirror(opts.rawConfig, opts.releaseDir ?? 'release')
|
||||
const { tarball, pkgName } = getNodeJSTarball(wantedNodeVersion, nodeMirror)
|
||||
async function installNode (fetch: FetchFromRegistry, wantedNodeVersion: string, versionDir: string, opts: NvmNodeCommandOptions & { nodeMirrorBaseUrl: string }) {
|
||||
const { tarball, pkgName } = getNodeJSTarball(wantedNodeVersion, opts.nodeMirrorBaseUrl)
|
||||
if (tarball.endsWith('.zip')) {
|
||||
await downloadAndUnpackZip(fetch, tarball, versionDir, pkgName)
|
||||
return
|
||||
|
||||
14
packages/plugin-commands-env/src/parseNodeVersionSelector.ts
Normal file
14
packages/plugin-commands-env/src/parseNodeVersionSelector.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function parseNodeVersionSelector (rawVersionSelector: string) {
|
||||
if (rawVersionSelector.includes('/')) {
|
||||
const [releaseDir, version] = rawVersionSelector.split('/')
|
||||
return { releaseDir, version }
|
||||
}
|
||||
const prereleaseMatch = rawVersionSelector.match(/-(nightly|rc|test|v8-canary)/)
|
||||
if (prereleaseMatch != null) {
|
||||
return { releaseDir: prereleaseMatch[1], version: rawVersionSelector }
|
||||
}
|
||||
if (['nightly', 'rc', 'test', 'release', 'v8-canary'].includes(rawVersionSelector)) {
|
||||
return { releaseDir: rawVersionSelector, version: 'latest' }
|
||||
}
|
||||
return { releaseDir: 'release', version: rawVersionSelector }
|
||||
}
|
||||
@@ -1,47 +1,22 @@
|
||||
import { Config } from '@pnpm/config'
|
||||
import { FetchFromRegistry } from '@pnpm/fetch'
|
||||
import semver from 'semver'
|
||||
import versionSelectorType from 'version-selector-type'
|
||||
import getNodeMirror from './getNodeMirror'
|
||||
|
||||
interface NodeVersion {
|
||||
version: string
|
||||
lts: false | string
|
||||
}
|
||||
|
||||
export default async function resolveNodeVersion (fetch: FetchFromRegistry, rawVersionSelector: string, rawConfig: Config['rawConfig']) {
|
||||
const { releaseDir, version } = parseNodeVersionSelector(rawVersionSelector)
|
||||
const nodeMirrorBaseUrl = getNodeMirror(rawConfig, releaseDir)
|
||||
export default async function resolveNodeVersion (fetch: FetchFromRegistry, version: string, nodeMirrorBaseUrl: string): Promise<string | null> {
|
||||
const response = await fetch(`${nodeMirrorBaseUrl}index.json`)
|
||||
const allVersions = (await response.json()) as NodeVersion[]
|
||||
if (version === 'latest') {
|
||||
return {
|
||||
version: allVersions[0].version.substring(1),
|
||||
releaseDir,
|
||||
}
|
||||
return allVersions[0].version.substring(1)
|
||||
}
|
||||
const { versions, versionSelector } = filterVersions(allVersions, version)
|
||||
const pickedVersion = semver.maxSatisfying(versions.map(({ version }) => version), versionSelector, { includePrerelease: true, loose: true })
|
||||
if (!pickedVersion) return { version: null, releaseDir }
|
||||
return {
|
||||
version: pickedVersion.substring(1),
|
||||
releaseDir,
|
||||
}
|
||||
}
|
||||
|
||||
function parseNodeVersionSelector (rawVersionSelector: string) {
|
||||
if (rawVersionSelector.includes('/')) {
|
||||
const [releaseDir, version] = rawVersionSelector.split('/')
|
||||
return { releaseDir, version }
|
||||
}
|
||||
const prereleaseMatch = rawVersionSelector.match(/-(nightly|rc|test|v8-canary)/)
|
||||
if (prereleaseMatch != null) {
|
||||
return { releaseDir: prereleaseMatch[1], version: rawVersionSelector }
|
||||
}
|
||||
if (['nightly', 'rc', 'test', 'release', 'v8-canary'].includes(rawVersionSelector)) {
|
||||
return { releaseDir: rawVersionSelector, version: 'latest' }
|
||||
}
|
||||
return { releaseDir: 'release', version: rawVersionSelector }
|
||||
if (!pickedVersion) return null
|
||||
return pickedVersion.substring(1)
|
||||
}
|
||||
|
||||
function filterVersions (versions: NodeVersion[], versionSelector: string) {
|
||||
|
||||
@@ -5,6 +5,28 @@ import { Readable } from 'stream'
|
||||
import { node } from '@pnpm/plugin-commands-env'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
|
||||
const fetchMock = jest.fn(async (url: string) => {
|
||||
if (url.endsWith('.zip')) {
|
||||
// The Windows code path for pnpm's node bootstrapping expects a subdir
|
||||
// within the .zip file.
|
||||
const pkgName = path.basename(url, '.zip')
|
||||
const zip = new AdmZip()
|
||||
zip.addFile(`${pkgName}/dummy-file`, Buffer.from('test'))
|
||||
|
||||
return new Response(Readable.from(zip.toBuffer()))
|
||||
}
|
||||
|
||||
return new Response(Readable.from(Buffer.alloc(0)))
|
||||
})
|
||||
|
||||
jest.mock('@pnpm/fetch', () => ({
|
||||
createFetchFromRegistry: () => fetchMock,
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockClear()
|
||||
})
|
||||
|
||||
test('check API (placeholder test)', async () => {
|
||||
expect(typeof node.getNodeDir).toBe('function')
|
||||
})
|
||||
@@ -25,23 +47,29 @@ test('install Node uses node-mirror:release option', async () => {
|
||||
useNodeVersion: '16.4.0',
|
||||
}
|
||||
|
||||
const fetchMock = jest.fn(async (url: string) => {
|
||||
if (url.endsWith('.zip')) {
|
||||
// The Windows code path for pnpm's node bootstrapping expects a subdir
|
||||
// within the .zip file.
|
||||
const pkgName = path.basename(url, '.zip')
|
||||
const zip = new AdmZip()
|
||||
zip.addFile(`${pkgName}/dummy-file`, Buffer.from('test'))
|
||||
|
||||
return new Response(Readable.from(zip.toBuffer()))
|
||||
}
|
||||
|
||||
return new Response(Readable.from(Buffer.alloc(0)))
|
||||
})
|
||||
|
||||
await node.getNodeDir(fetchMock, opts)
|
||||
await node.getNodeBinDir(opts)
|
||||
|
||||
for (const call of fetchMock.mock.calls) {
|
||||
expect(call[0]).toMatch(nodeMirrorRelease)
|
||||
}
|
||||
})
|
||||
|
||||
test('install and rc version of Node.js', async () => {
|
||||
tempDir()
|
||||
const configDir = path.resolve('config')
|
||||
|
||||
const opts: node.NvmNodeCommandOptions = {
|
||||
bin: process.cwd(),
|
||||
configDir,
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {},
|
||||
useNodeVersion: 'rc/18.0.0-rc.3',
|
||||
}
|
||||
|
||||
await node.getNodeBinDir(opts)
|
||||
|
||||
const platform = process.platform === 'win32' ? 'win' : process.platform
|
||||
const extension = process.platform === 'win32' ? 'zip' : 'tar.gz'
|
||||
expect(fetchMock.mock.calls[0][0]).toBe(`https://nodejs.org/download/rc/v18.0.0-rc.3/node-v18.0.0-rc.3-${platform}-x64.${extension}`)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { parseNodeVersionSelector } from '../lib/parseNodeVersionSelector'
|
||||
|
||||
test.each([
|
||||
['6', '6', 'release'],
|
||||
['16.0.0-rc.0', '16.0.0-rc.0', 'rc'],
|
||||
['rc/10', '10', 'rc'],
|
||||
['nightly', 'latest', 'nightly'],
|
||||
['lts', 'lts', 'release'],
|
||||
['argon', 'argon', 'release'],
|
||||
['latest', 'latest', 'release'],
|
||||
])('Node.js version selector is parsed', (spec, version, releaseDir) => {
|
||||
const node = parseNodeVersionSelector(spec)
|
||||
expect(node.version).toMatch(version)
|
||||
expect(node.releaseDir).toBe(releaseDir)
|
||||
})
|
||||
@@ -3,18 +3,15 @@ import resolveNodeVersion from '../lib/resolveNodeVersion'
|
||||
|
||||
const fetch = createFetchFromRegistry({})
|
||||
|
||||
const rawConfig = {}
|
||||
|
||||
test.each([
|
||||
['6', '6.17.1', 'release'],
|
||||
['16.0.0-rc.0', '16.0.0-rc.0', 'rc'],
|
||||
['rc/10', '10.23.0-rc.0', 'rc'],
|
||||
['nightly', /.+/, 'nightly'],
|
||||
['lts', /.+/, 'release'],
|
||||
['argon', '4.9.1', 'release'],
|
||||
['latest', /.+/, 'release'],
|
||||
])('Node.js %s is resolved', async (spec, version, releaseDir) => {
|
||||
const node = await resolveNodeVersion(fetch, spec, rawConfig)
|
||||
expect(node.version).toMatch(version)
|
||||
expect(node.releaseDir).toBe(releaseDir)
|
||||
['https://nodejs.org/download/release/', '6', '6.17.1'],
|
||||
['https://nodejs.org/download/rc/', '16.0.0-rc.0', '16.0.0-rc.0'],
|
||||
['https://nodejs.org/download/rc/', '10', '10.23.0-rc.0'],
|
||||
['https://nodejs.org/download/nightly/', 'latest', /.+/],
|
||||
['https://nodejs.org/download/release/', 'lts', /.+/],
|
||||
['https://nodejs.org/download/release/', 'argon', '4.9.1'],
|
||||
['https://nodejs.org/download/release/', 'latest', /.+/],
|
||||
])('Node.js %s is resolved', async (nodeMirrorBaseUrl, spec, expectedVersion) => {
|
||||
const version = await resolveNodeVersion(fetch, spec, nodeMirrorBaseUrl)
|
||||
expect(version).toMatch(expectedVersion)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user