mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
7
.changeset/kind-toys-relate.md
Normal file
7
.changeset/kind-toys-relate.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/plugin-commands-env": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
New option added for: `node-mirror:<releaseDir>`. The string value of this dynamic option is used as the base URL for downloading node when `use-node-version` is specified. The `<releaseDir>` portion of this argument can be any dir in `https://nodejs.org/download`. Which `<releaseDir>` dynamic config option gets selected depends on the value of `use-node-version`. If 'use-node-version' is a simple `x.x.x` version string, `<releaseDir>` becomes `release` and `node-mirror:release` is read. Defaults to `https://nodejs.org/download/<releaseDir>/`.
|
||||
@@ -53,6 +53,8 @@
|
||||
"@pnpm/prepare": "workspace:0.0.28",
|
||||
"@types/adm-zip": "^0.4.34",
|
||||
"execa": "npm:safe-execa@^0.1.1",
|
||||
"nock": "12.0.3",
|
||||
"node-fetch": "3.0.0-beta.9",
|
||||
"path-name": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ 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])
|
||||
const { version: nodeVersion, releaseDir } = await resolveNodeVersion(fetch, params[1], opts.rawConfig)
|
||||
if (!nodeVersion) {
|
||||
throw new PnpmError('COULD_NOT_RESOLVE_NODEJS', `Couldn't find Node.js version matching ${params[1]}`)
|
||||
}
|
||||
|
||||
12
packages/plugin-commands-env/src/getNodeMirror.ts
Normal file
12
packages/plugin-commands-env/src/getNodeMirror.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Config } from '@pnpm/config'
|
||||
|
||||
export default function getNodeMirror (rawConfig: Config['rawConfig'], releaseDir: string): string {
|
||||
// This is a dynamic lookup since the 'use-node-version' option is allowed to be '<releaseDir>/<version>'
|
||||
const configKey = `node-mirror:${releaseDir}`
|
||||
const nodeMirror = rawConfig[configKey] ?? `https://nodejs.org/download/${releaseDir}/`
|
||||
return normalizeNodeMirror(nodeMirror)
|
||||
}
|
||||
|
||||
function normalizeNodeMirror (nodeMirror: string): string {
|
||||
return nodeMirror.endsWith('/') ? nodeMirror : `${nodeMirror}/`
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import tempy from 'tempy'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import writeJsonFile from 'write-json-file'
|
||||
import normalizeArch from './normalizeArch'
|
||||
import getNodeMirror from './getNodeMirror'
|
||||
|
||||
export type NvmNodeCommandOptions = Pick<Config,
|
||||
| 'bin'
|
||||
@@ -31,6 +32,7 @@ export type NvmNodeCommandOptions = Pick<Config,
|
||||
| 'key'
|
||||
| 'localAddress'
|
||||
| 'noProxy'
|
||||
| 'rawConfig'
|
||||
| 'strictSsl'
|
||||
| 'storeDir'
|
||||
| 'useNodeVersion'
|
||||
@@ -66,7 +68,8 @@ export async function getNodeDir (fetch: FetchFromRegistry, opts: NvmNodeCommand
|
||||
|
||||
async function installNode (fetch: FetchFromRegistry, wantedNodeVersion: string, versionDir: string, opts: NvmNodeCommandOptions & { releaseDir?: string }) {
|
||||
await fs.promises.mkdir(versionDir, { recursive: true })
|
||||
const { tarball, pkgName } = getNodeJSTarball(wantedNodeVersion, opts.releaseDir ?? 'release')
|
||||
const nodeMirror = getNodeMirror(opts.rawConfig, opts.releaseDir ?? 'release')
|
||||
const { tarball, pkgName } = getNodeJSTarball(wantedNodeVersion, nodeMirror)
|
||||
if (tarball.endsWith('.zip')) {
|
||||
await downloadAndUnpackZip(fetch, tarball, versionDir, pkgName)
|
||||
return
|
||||
@@ -115,14 +118,14 @@ async function downloadAndUnpackZip (
|
||||
await fs.promises.unlink(tmp)
|
||||
}
|
||||
|
||||
function getNodeJSTarball (nodeVersion: string, releaseDir: string) {
|
||||
function getNodeJSTarball (nodeVersion: string, nodeMirror: string) {
|
||||
const platform = process.platform === 'win32' ? 'win' : process.platform
|
||||
const arch = normalizeArch(process.platform, process.arch)
|
||||
const extension = platform === 'win' ? 'zip' : 'tar.gz'
|
||||
const pkgName = `node-v${nodeVersion}-${platform}-${arch}`
|
||||
return {
|
||||
pkgName,
|
||||
tarball: `https://nodejs.org/download/${releaseDir}/v${nodeVersion}/${pkgName}.${extension}`,
|
||||
tarball: `${nodeMirror}v${nodeVersion}/${pkgName}.${extension}`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
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) {
|
||||
export default async function resolveNodeVersion (fetch: FetchFromRegistry, rawVersionSelector: string, rawConfig: Config['rawConfig']) {
|
||||
const { releaseDir, version } = parseNodeVersionSelector(rawVersionSelector)
|
||||
const response = await fetch(`https://nodejs.org/download/${releaseDir}/index.json`)
|
||||
const nodeMirrorBaseUrl = getNodeMirror(rawConfig, releaseDir)
|
||||
const response = await fetch(`${nodeMirrorBaseUrl}index.json`)
|
||||
const allVersions = (await response.json()) as NodeVersion[]
|
||||
if (version === 'latest') {
|
||||
return {
|
||||
|
||||
@@ -4,6 +4,7 @@ import PnpmError from '@pnpm/error'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
import { env } from '@pnpm/plugin-commands-env'
|
||||
import * as execa from 'execa'
|
||||
import nock from 'nock'
|
||||
import PATH from 'path-name'
|
||||
|
||||
test('install Node (and npm, npx) by exact version of Node.js', async () => {
|
||||
@@ -49,6 +50,29 @@ test('install Node (and npm, npx) by exact version of Node.js', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test('resolveNodeVersion uses node-mirror:release option', async () => {
|
||||
tempDir()
|
||||
const configDir = path.resolve('config')
|
||||
|
||||
const nockScope = nock('https://pnpm-node-mirror-test.localhost')
|
||||
.get('/download/release/index.json')
|
||||
.reply(200, [])
|
||||
|
||||
await expect(
|
||||
env.handler({
|
||||
bin: process.cwd(),
|
||||
configDir,
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {
|
||||
'node-mirror:release': 'https://pnpm-node-mirror-test.localhost/download/release',
|
||||
},
|
||||
}, ['use', '16.4.0'])
|
||||
).rejects.toEqual(new PnpmError('COULD_NOT_RESOLVE_NODEJS', 'Couldn\'t find Node.js version matching 16.4.0'))
|
||||
|
||||
expect(nockScope.isDone()).toBeTruthy()
|
||||
})
|
||||
|
||||
test('fail if a non-existend Node.js version is tried to be installed', async () => {
|
||||
tempDir()
|
||||
|
||||
|
||||
23
packages/plugin-commands-env/test/getNodeMirror.test.ts
Normal file
23
packages/plugin-commands-env/test/getNodeMirror.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import getNodeMirror from '@pnpm/plugin-commands-env/lib/getNodeMirror'
|
||||
|
||||
test.each([
|
||||
['release', { 'node-mirror:release': 'http://test.mirror.localhost/release' }, 'http://test.mirror.localhost/release/'],
|
||||
['nightly', { 'node-mirror:nightly': 'http://test.mirror.localhost/nightly' }, 'http://test.mirror.localhost/nightly/'],
|
||||
['rc', { 'node-mirror:rc': 'http://test.mirror.localhost/rc' }, 'http://test.mirror.localhost/rc/'],
|
||||
['test', { 'node-mirror:test': 'http://test.mirror.localhost/test' }, 'http://test.mirror.localhost/test/'],
|
||||
['v8-canary', { 'node-mirror:v8-canary': 'http://test.mirror.localhost/v8-canary' }, 'http://test.mirror.localhost/v8-canary/'],
|
||||
])('getNodeMirror(%s, %s)', (releaseDir, rawConfig, expected) => {
|
||||
expect(getNodeMirror(rawConfig, releaseDir)).toBe(expected)
|
||||
})
|
||||
|
||||
test('getNodeMirror uses defaults', () => {
|
||||
const rawConfig = {}
|
||||
expect(getNodeMirror(rawConfig, 'release')).toBe('https://nodejs.org/download/release/')
|
||||
})
|
||||
|
||||
test('getNodeMirror returns base url with trailing /', () => {
|
||||
const rawConfig = {
|
||||
'node-mirror:release': 'http://test.mirror.localhost',
|
||||
}
|
||||
expect(getNodeMirror(rawConfig, 'release')).toBe('http://test.mirror.localhost/')
|
||||
})
|
||||
@@ -1,5 +1,47 @@
|
||||
import AdmZip from 'adm-zip'
|
||||
import { Response } from 'node-fetch'
|
||||
import path from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import { node } from '@pnpm/plugin-commands-env'
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
|
||||
test('check API (placeholder test)', async () => {
|
||||
expect(typeof node.getNodeDir).toBe('function')
|
||||
})
|
||||
|
||||
test('install Node uses node-mirror:release option', async () => {
|
||||
tempDir()
|
||||
const configDir = path.resolve('config')
|
||||
|
||||
const nodeMirrorRelease = 'https://pnpm-node-mirror-test.localhost/download/release'
|
||||
const opts: node.NvmNodeCommandOptions = {
|
||||
bin: process.cwd(),
|
||||
configDir,
|
||||
global: true,
|
||||
pnpmHomeDir: process.cwd(),
|
||||
rawConfig: {
|
||||
'node-mirror:release': nodeMirrorRelease,
|
||||
},
|
||||
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)
|
||||
|
||||
for (const call of fetchMock.mock.calls) {
|
||||
expect(call[0]).toMatch(nodeMirrorRelease)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,8 @@ import resolveNodeVersion from '@pnpm/plugin-commands-env/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'],
|
||||
@@ -12,7 +14,7 @@ test.each([
|
||||
['argon', '4.9.1', 'release'],
|
||||
['latest', /.+/, 'release'],
|
||||
])('Node.js %s is resolved', async (spec, version, releaseDir) => {
|
||||
const node = await resolveNodeVersion(fetch, spec)
|
||||
const node = await resolveNodeVersion(fetch, spec, rawConfig)
|
||||
expect(node.version).toMatch(version)
|
||||
expect(node.releaseDir).toBe(releaseDir)
|
||||
})
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -2050,6 +2050,8 @@ importers:
|
||||
adm-zip: ^0.5.5
|
||||
execa: npm:safe-execa@^0.1.1
|
||||
load-json-file: ^6.2.0
|
||||
nock: 12.0.3
|
||||
node-fetch: 3.0.0-beta.9
|
||||
path-name: ^1.0.0
|
||||
rename-overwrite: ^4.0.0
|
||||
render-help: ^1.0.1
|
||||
@@ -2080,6 +2082,8 @@ importers:
|
||||
'@pnpm/prepare': link:../../privatePackages/prepare
|
||||
'@types/adm-zip': 0.4.34
|
||||
execa: /safe-execa/0.1.1
|
||||
nock: 12.0.3
|
||||
node-fetch: 3.0.0-beta.9
|
||||
path-name: 1.0.0
|
||||
|
||||
packages/plugin-commands-installation:
|
||||
|
||||
Reference in New Issue
Block a user