mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-27 11:31:45 -04:00
6
.changeset/nervous-buckets-return.md
Normal file
6
.changeset/nervous-buckets-return.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/node.fetcher": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Emit a clear error message when users attempt to specify an undownloadable node version [#6916](https://github.com/pnpm/pnpm/pull/6916).
|
||||
4
env/plugin-commands-env/src/envList.ts
vendored
4
env/plugin-commands-env/src/envList.ts
vendored
@@ -6,7 +6,7 @@ import { PnpmError } from '@pnpm/error'
|
||||
import semver from 'semver'
|
||||
import { getNodeMirror } from './getNodeMirror'
|
||||
import { getNodeVersionsBaseDir, type NvmNodeCommandOptions } from './node'
|
||||
import { parseNodeEditionSpecifier } from './parseNodeEditionSpecifier'
|
||||
import { parseEnvSpecifier } from './parseEnvSpecifier'
|
||||
import { getNodeExecPathAndTargetDir, getNodeExecPathInNodeDir } from './utils'
|
||||
|
||||
export async function envList (opts: NvmNodeCommandOptions, params: string[]) {
|
||||
@@ -43,7 +43,7 @@ async function listLocalVersions (opts: NvmNodeCommandOptions) {
|
||||
|
||||
async function listRemoteVersions (opts: NvmNodeCommandOptions, versionSpec?: string) {
|
||||
const fetch = createFetchFromRegistry(opts)
|
||||
const { releaseChannel, versionSpecifier } = parseNodeEditionSpecifier(versionSpec ?? '')
|
||||
const { releaseChannel, versionSpecifier } = parseEnvSpecifier(versionSpec ?? '')
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseChannel)
|
||||
const nodeVersionList = await resolveNodeVersions(fetch, versionSpecifier, nodeMirrorBaseUrl)
|
||||
return nodeVersionList
|
||||
|
||||
4
env/plugin-commands-env/src/envRemove.ts
vendored
4
env/plugin-commands-env/src/envRemove.ts
vendored
@@ -6,7 +6,7 @@ import { globalInfo } from '@pnpm/logger'
|
||||
import { resolveNodeVersion } from '@pnpm/node.resolver'
|
||||
import { removeBin } from '@pnpm/remove-bins'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import { parseNodeEditionSpecifier } from './parseNodeEditionSpecifier'
|
||||
import { parseEnvSpecifier } from './parseEnvSpecifier'
|
||||
import { getNodeExecPathAndTargetDir } from './utils'
|
||||
import { getNodeMirror } from './getNodeMirror'
|
||||
import { getNodeVersionsBaseDir, type NvmNodeCommandOptions } from './node'
|
||||
@@ -17,7 +17,7 @@ export async function envRemove (opts: NvmNodeCommandOptions, params: string[])
|
||||
}
|
||||
|
||||
const fetch = createFetchFromRegistry(opts)
|
||||
const { releaseChannel, versionSpecifier } = parseNodeEditionSpecifier(params[0])
|
||||
const { releaseChannel, versionSpecifier } = parseEnvSpecifier(params[0])
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseChannel)
|
||||
const nodeVersion = await resolveNodeVersion(fetch, versionSpecifier, nodeMirrorBaseUrl)
|
||||
const nodeDir = getNodeVersionsBaseDir(opts.pnpmHomeDir)
|
||||
|
||||
4
env/plugin-commands-env/src/envUse.ts
vendored
4
env/plugin-commands-env/src/envUse.ts
vendored
@@ -9,7 +9,7 @@ import isWindows from 'is-windows'
|
||||
import symlinkDir from 'symlink-dir'
|
||||
import { getNodeDir, type NvmNodeCommandOptions } from './node'
|
||||
import { getNodeMirror } from './getNodeMirror'
|
||||
import { parseNodeEditionSpecifier } from './parseNodeEditionSpecifier'
|
||||
import { parseEnvSpecifier } from './parseEnvSpecifier'
|
||||
import { CURRENT_NODE_DIRNAME, getNodeExecPathInBinDir, getNodeExecPathInNodeDir } from './utils'
|
||||
|
||||
export async function envUse (opts: NvmNodeCommandOptions, params: string[]) {
|
||||
@@ -17,7 +17,7 @@ export async function envUse (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 { releaseChannel, versionSpecifier } = parseNodeEditionSpecifier(params[0])
|
||||
const { releaseChannel, versionSpecifier } = parseEnvSpecifier(params[0])
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseChannel)
|
||||
const nodeVersion = await resolveNodeVersion(fetch, versionSpecifier, nodeMirrorBaseUrl)
|
||||
if (!nodeVersion) {
|
||||
|
||||
6
env/plugin-commands-env/src/node.ts
vendored
6
env/plugin-commands-env/src/node.ts
vendored
@@ -8,7 +8,7 @@ import { getStorePath } from '@pnpm/store-path'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
import { getNodeMirror } from './getNodeMirror'
|
||||
import { parseNodeEditionSpecifier } from './parseNodeEditionSpecifier'
|
||||
import { parseNodeSpecifier } from './parseNodeSpecifier'
|
||||
|
||||
export type NvmNodeCommandOptions = Pick<Config,
|
||||
| 'bin'
|
||||
@@ -49,11 +49,11 @@ export async function getNodeBinDir (opts: NvmNodeCommandOptions) {
|
||||
default: wantedNodeVersion,
|
||||
})
|
||||
}
|
||||
const { versionSpecifier, releaseChannel } = parseNodeEditionSpecifier(wantedNodeVersion)
|
||||
const { useNodeVersion, releaseChannel } = parseNodeSpecifier(wantedNodeVersion)
|
||||
const nodeMirrorBaseUrl = getNodeMirror(opts.rawConfig, releaseChannel)
|
||||
const nodeDir = await getNodeDir(fetch, {
|
||||
...opts,
|
||||
useNodeVersion: versionSpecifier,
|
||||
useNodeVersion,
|
||||
nodeMirrorBaseUrl,
|
||||
})
|
||||
return process.platform === 'win32' ? nodeDir : path.join(nodeDir, 'bin')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export interface NodeEditionSpecifier {
|
||||
export interface EnvSpecifier {
|
||||
releaseChannel: string
|
||||
versionSpecifier: string
|
||||
}
|
||||
|
||||
export function parseNodeEditionSpecifier (specifier: string): NodeEditionSpecifier {
|
||||
export function parseEnvSpecifier (specifier: string): EnvSpecifier {
|
||||
if (specifier.includes('/')) {
|
||||
const [releaseChannel, versionSpecifier] = specifier.split('/')
|
||||
return { releaseChannel, versionSpecifier }
|
||||
44
env/plugin-commands-env/src/parseNodeSpecifier.ts
vendored
Normal file
44
env/plugin-commands-env/src/parseNodeSpecifier.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
|
||||
export interface NodeSpecifier {
|
||||
releaseChannel: string
|
||||
useNodeVersion: string
|
||||
}
|
||||
|
||||
const isStableVersion = (version: string) => /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version)
|
||||
const STABLE_RELEASE_ERROR_HINT = 'The correct syntax for stable release is strictly X.Y.Z or release/X.Y.Z'
|
||||
|
||||
export function parseNodeSpecifier (specifier: string): NodeSpecifier {
|
||||
if (specifier.includes('/')) {
|
||||
const [releaseChannel, useNodeVersion] = specifier.split('/')
|
||||
|
||||
if (releaseChannel === 'release') {
|
||||
if (!isStableVersion(useNodeVersion)) {
|
||||
throw new PnpmError('INVALID_NODE_VERSION', `"${specifier}" is not a valid node version`, {
|
||||
hint: STABLE_RELEASE_ERROR_HINT,
|
||||
})
|
||||
}
|
||||
} else if (!useNodeVersion.includes(releaseChannel)) {
|
||||
throw new PnpmError('MISMATCHED_RELEASE_CHANNEL', `The node version (${useNodeVersion}) must contain the release channel (${releaseChannel})`)
|
||||
}
|
||||
|
||||
return { releaseChannel, useNodeVersion }
|
||||
}
|
||||
|
||||
const prereleaseMatch = specifier.match(/^[0-9]+\.[0-9]+\.[0-9]+-(nightly|rc|test|v8-canary)(\..+)$/)
|
||||
if (prereleaseMatch != null) {
|
||||
return { releaseChannel: prereleaseMatch[1], useNodeVersion: specifier }
|
||||
}
|
||||
|
||||
if (isStableVersion(specifier)) {
|
||||
return { releaseChannel: 'release', useNodeVersion: specifier }
|
||||
}
|
||||
|
||||
let hint: string | undefined
|
||||
if (['nightly', 'rc', 'test', 'v8-canary'].includes(specifier)) {
|
||||
hint = `The correct syntax for ${specifier} release is strictly X.Y.Z-${specifier}.W`
|
||||
} else if (/^[0-9]+\.[0-9]+$/.test(specifier) || /^[0-9]+$/.test(specifier) || ['release', 'stable', 'latest'].includes(specifier)) {
|
||||
hint = STABLE_RELEASE_ERROR_HINT
|
||||
}
|
||||
throw new PnpmError('INVALID_NODE_VERSION', `"${specifier}" is not a valid node version`, { hint })
|
||||
}
|
||||
2
env/plugin-commands-env/test/node.test.ts
vendored
2
env/plugin-commands-env/test/node.test.ts
vendored
@@ -54,7 +54,7 @@ test('install Node uses node-mirror:release option', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('install and rc version of Node.js', async () => {
|
||||
test('install an rc version of Node.js', async () => {
|
||||
tempDir()
|
||||
const configDir = path.resolve('config')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseNodeEditionSpecifier } from '../lib/parseNodeEditionSpecifier'
|
||||
import { parseEnvSpecifier } from '../lib/parseEnvSpecifier'
|
||||
|
||||
test.each([
|
||||
['6', '6', 'release'],
|
||||
@@ -9,7 +9,7 @@ test.each([
|
||||
['argon', 'argon', 'release'],
|
||||
['latest', 'latest', 'release'],
|
||||
])('Node.js version selector is parsed', (editionSpecifier, versionSpecifier, releaseChannel) => {
|
||||
const node = parseNodeEditionSpecifier(editionSpecifier)
|
||||
const node = parseEnvSpecifier(editionSpecifier)
|
||||
expect(node.versionSpecifier).toMatch(versionSpecifier)
|
||||
expect(node.releaseChannel).toBe(releaseChannel)
|
||||
})
|
||||
45
env/plugin-commands-env/test/parseNodeSpecifier.ts
vendored
Normal file
45
env/plugin-commands-env/test/parseNodeSpecifier.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import { parseNodeSpecifier } from '../lib/parseNodeSpecifier'
|
||||
|
||||
test.each([
|
||||
['rc/16.0.0-rc.0', '16.0.0-rc.0', 'rc'],
|
||||
['16.0.0-rc.0', '16.0.0-rc.0', 'rc'],
|
||||
['release/16.0.0', '16.0.0', 'release'],
|
||||
['16.0.0', '16.0.0', 'release'],
|
||||
])('Node.js version selector is parsed', (editionSpecifier, useNodeVersion, releaseChannel) => {
|
||||
const node = parseNodeSpecifier(editionSpecifier)
|
||||
expect(node.useNodeVersion).toBe(useNodeVersion)
|
||||
expect(node.releaseChannel).toBe(releaseChannel)
|
||||
})
|
||||
|
||||
test.each([
|
||||
['rc/10', '10', 'rc'],
|
||||
['rc/10.0', '10.0', 'rc'],
|
||||
['rc/10.0.0', '10.0.0', 'rc'],
|
||||
['rc/10.0.0.test.0', '10.0.0.test.0', 'rc'],
|
||||
])('invalid Node.js specifier', (editionSpecifier, useNodeVersion, releaseChannel) => {
|
||||
expect(() => parseNodeSpecifier(editionSpecifier)).toThrow(`The node version (${useNodeVersion}) must contain the release channel (${releaseChannel})`)
|
||||
})
|
||||
|
||||
test.each([
|
||||
['nightly'],
|
||||
['rc'],
|
||||
['test'],
|
||||
['v8-canary'],
|
||||
])('invalid Node.js specifier', async (specifier) => {
|
||||
const promise = Promise.resolve().then(() => parseNodeSpecifier(specifier))
|
||||
await expect(promise).rejects.toThrow(`"${specifier}" is not a valid node version`)
|
||||
await expect(promise).rejects.toHaveProperty('hint', `The correct syntax for ${specifier} release is strictly X.Y.Z-${specifier}.W`)
|
||||
})
|
||||
|
||||
test.each([
|
||||
['release'],
|
||||
['stable'],
|
||||
['latest'],
|
||||
['release/16.0.0.release.0'],
|
||||
['16'],
|
||||
['16.0'],
|
||||
])('invalid Node.js specifier', async (specifier) => {
|
||||
const promise = Promise.resolve().then(() => parseNodeSpecifier(specifier))
|
||||
await expect(promise).rejects.toThrow(`"${specifier}" is not a valid node version`)
|
||||
await expect(promise).rejects.toHaveProperty('hint', 'The correct syntax for stable release is strictly X.Y.Z or release/X.Y.Z')
|
||||
})
|
||||
Reference in New Issue
Block a user