diff --git a/.changeset/violet-pots-occur.md b/.changeset/violet-pots-occur.md new file mode 100644 index 0000000000..4972705e0f --- /dev/null +++ b/.changeset/violet-pots-occur.md @@ -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 +``` + diff --git a/packages/plugin-commands-env/src/env.ts b/packages/plugin-commands-env/src/env.ts index f8d16abbf4..8d4d779602 100644 --- a/packages/plugin-commands-env/src/env.ts +++ b/packages/plugin-commands-env/src/env.ts @@ -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 " 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') diff --git a/packages/plugin-commands-env/src/node.ts b/packages/plugin-commands-env/src/node.ts index 34a62b4446..398661ff19 100644 --- a/packages/plugin-commands-env/src/node.ts +++ b/packages/plugin-commands-env/src/node.ts @@ -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 { 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) { diff --git a/packages/plugin-commands-env/test/node.test.ts b/packages/plugin-commands-env/test/node.test.ts index 8c98f6a20e..585fd88d20 100644 --- a/packages/plugin-commands-env/test/node.test.ts +++ b/packages/plugin-commands-env/test/node.test.ts @@ -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}`) +}) diff --git a/packages/plugin-commands-env/test/parseNodeVersionSelector.test.ts b/packages/plugin-commands-env/test/parseNodeVersionSelector.test.ts new file mode 100644 index 0000000000..2363505e14 --- /dev/null +++ b/packages/plugin-commands-env/test/parseNodeVersionSelector.test.ts @@ -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) +}) diff --git a/packages/plugin-commands-env/test/resolveNodeVersion.test.ts b/packages/plugin-commands-env/test/resolveNodeVersion.test.ts index 0e51771763..df9337f7ad 100644 --- a/packages/plugin-commands-env/test/resolveNodeVersion.test.ts +++ b/packages/plugin-commands-env/test/resolveNodeVersion.test.ts @@ -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) })