mirror of
https://github.com/pnpm/pnpm.git
synced 2026-02-02 19:22:52 -05:00
feat: verify the integrity of Node.js artifacts (#9750)
This commit is contained in:
5
.changeset/cold-falcons-report.md
Normal file
5
.changeset/cold-falcons-report.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/fetching-types": minor
|
||||
---
|
||||
|
||||
Export type Response.
|
||||
7
.changeset/short-goats-attack.md
Normal file
7
.changeset/short-goats-attack.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-env": patch
|
||||
"@pnpm/node.fetcher": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
The integrities of the downloaded Node.js artifacts are verified [#9750](https://github.com/pnpm/pnpm/pull/9750).
|
||||
@@ -235,6 +235,7 @@
|
||||
"sels",
|
||||
"semistrict",
|
||||
"serverjs",
|
||||
"shasums",
|
||||
"sheetjs",
|
||||
"shlex",
|
||||
"sindresorhus",
|
||||
@@ -285,6 +286,7 @@
|
||||
"workleap",
|
||||
"wrappy",
|
||||
"xmarw",
|
||||
"yazl",
|
||||
"zkochan",
|
||||
"zoli",
|
||||
"zoltan"
|
||||
|
||||
2
env/node.fetcher/package.json
vendored
2
env/node.fetcher/package.json
vendored
@@ -41,6 +41,7 @@
|
||||
"adm-zip": "catalog:",
|
||||
"detect-libc": "catalog:",
|
||||
"rename-overwrite": "catalog:",
|
||||
"ssri": "catalog:",
|
||||
"tempy": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -48,6 +49,7 @@
|
||||
"@pnpm/node.fetcher": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@types/adm-zip": "catalog:",
|
||||
"@types/ssri": "catalog:",
|
||||
"node-fetch": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
30
env/node.fetcher/src/getNodeArtifactAddress.ts
vendored
Normal file
30
env/node.fetcher/src/getNodeArtifactAddress.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { getNormalizedArch } from './normalizeArch'
|
||||
|
||||
export interface NodeArtifactAddress {
|
||||
basename: string
|
||||
extname: string
|
||||
dirname: string
|
||||
}
|
||||
|
||||
export interface GetNodeArtifactAddressOptions {
|
||||
version: string
|
||||
baseUrl: string
|
||||
platform: string
|
||||
arch: string
|
||||
}
|
||||
|
||||
export function getNodeArtifactAddress ({
|
||||
version,
|
||||
baseUrl,
|
||||
platform,
|
||||
arch,
|
||||
}: GetNodeArtifactAddressOptions): NodeArtifactAddress {
|
||||
const isWindowsPlatform = platform === 'win32'
|
||||
const normalizedPlatform = isWindowsPlatform ? 'win' : platform
|
||||
const normalizedArch = getNormalizedArch(platform, arch, version)
|
||||
return {
|
||||
dirname: `${baseUrl}v${version}`,
|
||||
basename: `node-v${version}-${normalizedPlatform}-${normalizedArch}`,
|
||||
extname: isWindowsPlatform ? '.zip' : '.tar.gz',
|
||||
}
|
||||
}
|
||||
17
env/node.fetcher/src/getNodeTarball.ts
vendored
17
env/node.fetcher/src/getNodeTarball.ts
vendored
@@ -1,17 +0,0 @@
|
||||
import { getNormalizedArch } from './normalizeArch'
|
||||
|
||||
export function getNodeTarball (
|
||||
nodeVersion: string,
|
||||
nodeMirror: string,
|
||||
processPlatform: string,
|
||||
processArch: string
|
||||
): { pkgName: string, tarball: string } {
|
||||
const platform = processPlatform === 'win32' ? 'win' : processPlatform
|
||||
const arch = getNormalizedArch(processPlatform, processArch, nodeVersion)
|
||||
const extension = platform === 'win' ? 'zip' : 'tar.gz'
|
||||
const pkgName = `node-v${nodeVersion}-${platform}-${arch}`
|
||||
return {
|
||||
pkgName,
|
||||
tarball: `${nodeMirror}v${nodeVersion}/${pkgName}.${extension}`,
|
||||
}
|
||||
}
|
||||
260
env/node.fetcher/src/index.ts
vendored
260
env/node.fetcher/src/index.ts
vendored
@@ -4,15 +4,22 @@ import { PnpmError } from '@pnpm/error'
|
||||
import {
|
||||
type FetchFromRegistry,
|
||||
type RetryTimeoutOptions,
|
||||
type Response,
|
||||
} from '@pnpm/fetching-types'
|
||||
import { pickFetcher } from '@pnpm/pick-fetcher'
|
||||
import { createCafsStore } from '@pnpm/create-cafs-store'
|
||||
import { createTarballFetcher } from '@pnpm/tarball-fetcher'
|
||||
import { type FetchFunction } from '@pnpm/fetcher-base'
|
||||
import AdmZip from 'adm-zip'
|
||||
import renameOverwrite from 'rename-overwrite'
|
||||
import tempy from 'tempy'
|
||||
import { isNonGlibcLinux } from 'detect-libc'
|
||||
import { getNodeTarball } from './getNodeTarball'
|
||||
import ssri from 'ssri'
|
||||
import { getNodeArtifactAddress } from './getNodeArtifactAddress'
|
||||
|
||||
// Constants
|
||||
const DEFAULT_NODE_MIRROR_BASE_URL = 'https://nodejs.org/download/release/'
|
||||
const SHA256_REGEX = /^[a-f0-9]{64}$/
|
||||
|
||||
export interface FetchNodeOptions {
|
||||
storeDir: string
|
||||
@@ -21,16 +28,149 @@ export interface FetchNodeOptions {
|
||||
retry?: RetryTimeoutOptions
|
||||
}
|
||||
|
||||
export async function fetchNode (fetch: FetchFromRegistry, version: string, targetDir: string, opts: FetchNodeOptions): Promise<void> {
|
||||
if (await isNonGlibcLinux()) {
|
||||
throw new PnpmError('MUSL', 'The current system uses the "MUSL" C standard library. Node.js currently has prebuilt artifacts only for the "glibc" libc, so we can install Node.js only for glibc')
|
||||
}
|
||||
const nodeMirrorBaseUrl = opts.nodeMirrorBaseUrl ?? 'https://nodejs.org/download/release/'
|
||||
const { tarball, pkgName } = getNodeTarball(version, nodeMirrorBaseUrl, process.platform, process.arch)
|
||||
if (tarball.endsWith('.zip')) {
|
||||
await downloadAndUnpackZip(fetch, tarball, targetDir, pkgName)
|
||||
interface NodeArtifactInfo {
|
||||
url: string
|
||||
integrity: string
|
||||
isZip: boolean
|
||||
basename: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and installs a Node.js version to the specified target directory.
|
||||
*
|
||||
* @param fetch - Function to fetch resources from registry
|
||||
* @param version - Node.js version to install
|
||||
* @param targetDir - Directory where Node.js should be installed
|
||||
* @param opts - Configuration options for the fetch operation
|
||||
* @throws {PnpmError} When system uses MUSL libc, integrity verification fails, or download fails
|
||||
*/
|
||||
export async function fetchNode (
|
||||
fetch: FetchFromRegistry,
|
||||
version: string,
|
||||
targetDir: string,
|
||||
opts: FetchNodeOptions
|
||||
): Promise<void> {
|
||||
await validateSystemCompatibility()
|
||||
|
||||
const nodeMirrorBaseUrl = opts.nodeMirrorBaseUrl ?? DEFAULT_NODE_MIRROR_BASE_URL
|
||||
const artifactInfo = await getNodeArtifactInfo(fetch, version, nodeMirrorBaseUrl)
|
||||
|
||||
if (artifactInfo.isZip) {
|
||||
await downloadAndUnpackZip(fetch, artifactInfo, targetDir)
|
||||
return
|
||||
}
|
||||
|
||||
await downloadAndUnpackTarball(fetch, artifactInfo, targetDir, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the current system is compatible with Node.js installation.
|
||||
*
|
||||
* @throws {PnpmError} When system uses MUSL libc
|
||||
*/
|
||||
async function validateSystemCompatibility (): Promise<void> {
|
||||
if (await isNonGlibcLinux()) {
|
||||
throw new PnpmError(
|
||||
'MUSL',
|
||||
'The current system uses the "MUSL" C standard library. Node.js currently has prebuilt artifacts only for the "glibc" libc, so we can install Node.js only for glibc'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Node.js artifact information including URL, integrity, and file type.
|
||||
*
|
||||
* @param fetch - Function to fetch resources from registry
|
||||
* @param version - Node.js version
|
||||
* @param nodeMirrorBaseUrl - Base URL for Node.js mirror
|
||||
* @returns Promise resolving to artifact information
|
||||
* @throws {PnpmError} When integrity file cannot be fetched or parsed
|
||||
*/
|
||||
async function getNodeArtifactInfo (
|
||||
fetch: FetchFromRegistry,
|
||||
version: string,
|
||||
nodeMirrorBaseUrl: string
|
||||
): Promise<NodeArtifactInfo> {
|
||||
const tarball = getNodeArtifactAddress({
|
||||
version,
|
||||
baseUrl: nodeMirrorBaseUrl,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
})
|
||||
|
||||
const tarballFileName = `${tarball.basename}${tarball.extname}`
|
||||
const shasumsFileUrl = `${tarball.dirname}/SHASUMS256.txt`
|
||||
const url = `${tarball.dirname}/${tarballFileName}`
|
||||
|
||||
const integrity = await loadArtifactIntegrity(fetch, shasumsFileUrl, tarballFileName)
|
||||
|
||||
return {
|
||||
url,
|
||||
integrity,
|
||||
isZip: tarball.extname === '.zip',
|
||||
basename: tarball.basename,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and verifies the integrity hash for a Node.js artifact.
|
||||
*
|
||||
* @param fetch - Function to fetch resources from registry
|
||||
* @param integritiesFileUrl - URL of the SHASUMS256.txt file
|
||||
* @param fileName - Name of the file to find integrity for
|
||||
* @returns Promise resolving to the integrity hash in base64 format
|
||||
* @throws {PnpmError} When integrity file cannot be fetched or parsed
|
||||
*/
|
||||
async function loadArtifactIntegrity (
|
||||
fetch: FetchFromRegistry,
|
||||
integritiesFileUrl: string,
|
||||
fileName: string
|
||||
): Promise<string> {
|
||||
const res = await fetch(integritiesFileUrl)
|
||||
if (!res.ok) {
|
||||
throw new PnpmError(
|
||||
'NODE_FETCH_INTEGRITY_FAILED',
|
||||
`Failed to fetch integrity file: ${integritiesFileUrl} (status: ${res.status})`
|
||||
)
|
||||
}
|
||||
|
||||
const body = await res.text()
|
||||
const line = body.split('\n').find(line => line.trim().endsWith(` ${fileName}`))
|
||||
|
||||
if (!line) {
|
||||
throw new PnpmError(
|
||||
'NODE_INTEGRITY_HASH_NOT_FOUND',
|
||||
`SHA-256 hash not found in SHASUMS256.txt for: ${fileName}`
|
||||
)
|
||||
}
|
||||
|
||||
const [sha256] = line.trim().split(/\s+/)
|
||||
if (!SHA256_REGEX.test(sha256)) {
|
||||
throw new PnpmError(
|
||||
'NODE_MALFORMED_INTEGRITY_HASH',
|
||||
`Malformed SHA-256 for ${fileName}: ${sha256}`
|
||||
)
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(sha256, 'hex')
|
||||
const base64 = buffer.toString('base64')
|
||||
return `sha256-${base64}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and unpacks a tarball using the tarball fetcher.
|
||||
*
|
||||
* @param fetch - Function to fetch resources from registry
|
||||
* @param artifactInfo - Information about the Node.js artifact
|
||||
* @param targetDir - Directory where Node.js should be installed
|
||||
* @param opts - Configuration options for the fetch operation
|
||||
*/
|
||||
async function downloadAndUnpackTarball (
|
||||
fetch: FetchFromRegistry,
|
||||
artifactInfo: NodeArtifactInfo,
|
||||
targetDir: string,
|
||||
opts: FetchNodeOptions
|
||||
): Promise<void> {
|
||||
const getAuthHeader = () => undefined
|
||||
const fetchers = createTarballFetcher(fetch, getAuthHeader, {
|
||||
retry: opts.retry,
|
||||
@@ -39,13 +179,23 @@ export async function fetchNode (fetch: FetchFromRegistry, version: string, targ
|
||||
rawConfig: {},
|
||||
unsafePerm: false,
|
||||
})
|
||||
|
||||
const cafs = createCafsStore(opts.storeDir)
|
||||
const fetchTarball = pickFetcher(fetchers, { tarball })
|
||||
const { filesIndex } = await fetchTarball(cafs, { tarball } as any, { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
filesIndexFile: path.join(opts.storeDir, encodeURIComponent(tarball)), // TODO: change the name or don't save an index file for node.js tarballs
|
||||
const fetchTarball = pickFetcher(fetchers, { tarball: artifactInfo.url }) as FetchFunction
|
||||
|
||||
// Create a unique index file name for Node.js tarballs
|
||||
const indexFileName = `node-${encodeURIComponent(artifactInfo.url)}`
|
||||
const filesIndexFile = path.join(opts.storeDir, indexFileName)
|
||||
|
||||
const { filesIndex } = await fetchTarball(cafs, {
|
||||
tarball: artifactInfo.url,
|
||||
integrity: artifactInfo.integrity,
|
||||
}, {
|
||||
filesIndexFile,
|
||||
lockfileDir: process.cwd(),
|
||||
pkg: {},
|
||||
})
|
||||
|
||||
cafs.importPackage(targetDir, {
|
||||
filesResponse: {
|
||||
filesIndex: filesIndex as Record<string, string>,
|
||||
@@ -56,21 +206,81 @@ export async function fetchNode (fetch: FetchFromRegistry, version: string, targ
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and unpacks a zip file containing Node.js.
|
||||
*
|
||||
* @param fetchFromRegistry - Function to fetch resources from registry
|
||||
* @param artifactInfo - Information about the Node.js artifact
|
||||
* @param targetDir - Directory where Node.js should be installed
|
||||
* @throws {PnpmError} When integrity verification fails or extraction fails
|
||||
*/
|
||||
async function downloadAndUnpackZip (
|
||||
fetchFromRegistry: FetchFromRegistry,
|
||||
zipUrl: string,
|
||||
targetDir: string,
|
||||
pkgName: string
|
||||
artifactInfo: NodeArtifactInfo,
|
||||
targetDir: string
|
||||
): Promise<void> {
|
||||
const response = await fetchFromRegistry(zipUrl)
|
||||
const response = await fetchFromRegistry(artifactInfo.url)
|
||||
const tmp = path.join(tempy.directory(), 'pnpm.zip')
|
||||
const dest = fs.createWriteStream(tmp)
|
||||
await new Promise((resolve, reject) => {
|
||||
response.body!.pipe(dest).on('error', reject).on('close', resolve)
|
||||
})
|
||||
const zip = new AdmZip(tmp)
|
||||
const nodeDir = path.dirname(targetDir)
|
||||
zip.extractAllTo(nodeDir, true)
|
||||
await renameOverwrite(path.join(nodeDir, pkgName), targetDir)
|
||||
await fs.promises.unlink(tmp)
|
||||
|
||||
try {
|
||||
await downloadWithIntegrityCheck(response, tmp, artifactInfo.integrity, artifactInfo.url)
|
||||
await extractZipToTarget(tmp, artifactInfo.basename, targetDir)
|
||||
} finally {
|
||||
// Clean up temporary file
|
||||
try {
|
||||
await fs.promises.unlink(tmp)
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file with integrity verification.
|
||||
*
|
||||
* @param response - Fetch response containing the file data
|
||||
* @param tmpPath - Temporary file path to save the download
|
||||
* @param expectedIntegrity - Expected SHA-256 integrity hash
|
||||
* @param url - URL being downloaded (for error messages)
|
||||
* @throws {PnpmError} When integrity verification fails
|
||||
*/
|
||||
async function downloadWithIntegrityCheck (
|
||||
response: Response,
|
||||
tmpPath: string,
|
||||
expectedIntegrity: string,
|
||||
url: string
|
||||
): Promise<void> {
|
||||
// Collect all chunks from the response
|
||||
const chunks: Buffer[] = []
|
||||
for await (const chunk of response.body!) {
|
||||
chunks.push(chunk as Buffer)
|
||||
}
|
||||
const data = Buffer.concat(chunks)
|
||||
|
||||
// Verify integrity if provided
|
||||
ssri.checkData(data, expectedIntegrity, { error: true })
|
||||
|
||||
// Write the verified data to file
|
||||
await fs.promises.writeFile(tmpPath, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a zip file to the target directory.
|
||||
*
|
||||
* @param zipPath - Path to the zip file
|
||||
* @param basename - Base name of the file (without extension)
|
||||
* @param targetDir - Directory where contents should be extracted
|
||||
* @throws {PnpmError} When extraction fails
|
||||
*/
|
||||
async function extractZipToTarget (
|
||||
zipPath: string,
|
||||
basename: string,
|
||||
targetDir: string
|
||||
): Promise<void> {
|
||||
const zip = new AdmZip(zipPath)
|
||||
const nodeDir = path.dirname(targetDir)
|
||||
const extractedDir = path.join(nodeDir, basename)
|
||||
|
||||
zip.extractAllTo(nodeDir, true)
|
||||
await renameOverwrite(extractedDir, targetDir)
|
||||
}
|
||||
|
||||
66
env/node.fetcher/test/getNodeArtifactAddress.test.ts
vendored
Normal file
66
env/node.fetcher/test/getNodeArtifactAddress.test.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import { getNodeArtifactAddress } from '../lib/getNodeArtifactAddress'
|
||||
|
||||
test.each([
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'win32',
|
||||
'ia32',
|
||||
{
|
||||
basename: 'node-v16.0.0-win-x86',
|
||||
dirname: 'https://nodejs.org/download/release/v16.0.0',
|
||||
extname: '.zip',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'linux',
|
||||
'arm',
|
||||
{
|
||||
basename: 'node-v16.0.0-linux-armv7l',
|
||||
dirname: 'https://nodejs.org/download/release/v16.0.0',
|
||||
extname: '.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'linux',
|
||||
'x64',
|
||||
{
|
||||
basename: 'node-v16.0.0-linux-x64',
|
||||
dirname: 'https://nodejs.org/download/release/v16.0.0',
|
||||
extname: '.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'15.14.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'darwin',
|
||||
'arm64',
|
||||
{
|
||||
basename: 'node-v15.14.0-darwin-x64',
|
||||
dirname: 'https://nodejs.org/download/release/v15.14.0',
|
||||
extname: '.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'darwin',
|
||||
'arm64',
|
||||
{
|
||||
basename: 'node-v16.0.0-darwin-arm64',
|
||||
dirname: 'https://nodejs.org/download/release/v16.0.0',
|
||||
extname: '.tar.gz',
|
||||
},
|
||||
],
|
||||
])('getNodeArtifactAddress', (version, nodeMirrorBaseUrl, platform, arch, tarball) => {
|
||||
expect(getNodeArtifactAddress({
|
||||
version,
|
||||
baseUrl: nodeMirrorBaseUrl,
|
||||
platform,
|
||||
arch,
|
||||
})).toStrictEqual(tarball)
|
||||
})
|
||||
56
env/node.fetcher/test/getNodeTarball.test.ts
vendored
56
env/node.fetcher/test/getNodeTarball.test.ts
vendored
@@ -1,56 +0,0 @@
|
||||
import { getNodeTarball } from '../lib/getNodeTarball'
|
||||
|
||||
test.each([
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'win32',
|
||||
'ia32',
|
||||
{
|
||||
pkgName: 'node-v16.0.0-win-x86',
|
||||
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-win-x86.zip',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'linux',
|
||||
'arm',
|
||||
{
|
||||
pkgName: 'node-v16.0.0-linux-armv7l',
|
||||
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-linux-armv7l.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'linux',
|
||||
'x64',
|
||||
{
|
||||
pkgName: 'node-v16.0.0-linux-x64',
|
||||
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-linux-x64.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'15.14.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'darwin',
|
||||
'arm64',
|
||||
{
|
||||
pkgName: 'node-v15.14.0-darwin-x64',
|
||||
tarball: 'https://nodejs.org/download/release/v15.14.0/node-v15.14.0-darwin-x64.tar.gz',
|
||||
},
|
||||
],
|
||||
[
|
||||
'16.0.0',
|
||||
'https://nodejs.org/download/release/',
|
||||
'darwin',
|
||||
'arm64',
|
||||
{
|
||||
pkgName: 'node-v16.0.0-darwin-arm64',
|
||||
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-darwin-arm64.tar.gz',
|
||||
},
|
||||
],
|
||||
])('getNodeTarball', (version, nodeMirrorBaseUrl, platform, arch, tarball) => {
|
||||
expect(getNodeTarball(version, nodeMirrorBaseUrl, platform, arch)).toStrictEqual(tarball)
|
||||
})
|
||||
6
env/plugin-commands-env/package.json
vendored
6
env/plugin-commands-env/package.json
vendored
@@ -58,17 +58,17 @@
|
||||
"@pnpm/logger": "workspace:*",
|
||||
"@pnpm/plugin-commands-env": "workspace:*",
|
||||
"@pnpm/prepare": "workspace:*",
|
||||
"@types/adm-zip": "catalog:",
|
||||
"@types/graceful-fs": "catalog:",
|
||||
"@types/is-windows": "catalog:",
|
||||
"@types/semver": "catalog:",
|
||||
"@types/tar-stream": "catalog:",
|
||||
"adm-zip": "catalog:",
|
||||
"@types/yazl": "catalog:",
|
||||
"execa": "catalog:",
|
||||
"nock": "catalog:",
|
||||
"node-fetch": "catalog:",
|
||||
"path-name": "catalog:",
|
||||
"tar-stream": "catalog:"
|
||||
"tar-stream": "catalog:",
|
||||
"yazl": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
|
||||
27
env/plugin-commands-env/test/node.test.ts
vendored
27
env/plugin-commands-env/test/node.test.ts
vendored
@@ -1,10 +1,10 @@
|
||||
import AdmZip from 'adm-zip'
|
||||
import { Response } from 'node-fetch'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { Readable } from 'stream'
|
||||
import tar from 'tar-stream'
|
||||
import { globalWarn } from '@pnpm/logger'
|
||||
import { ZipFile } from 'yazl'
|
||||
import {
|
||||
getNodeDir,
|
||||
getNodeBinDir,
|
||||
@@ -15,6 +15,18 @@ import {
|
||||
import { tempDir } from '@pnpm/prepare'
|
||||
|
||||
const fetchMock = jest.fn(async (url: string) => {
|
||||
if (url.endsWith('SHASUMS256.txt')) {
|
||||
return new Response(`
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v16.4.0-darwin-arm64.tar.gz
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v16.4.0-linux-arm64.tar.gz
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v16.4.0-linux-x64.tar.gz
|
||||
a08f3386090e6511772b949d41970b75a6b71d28abb551dff9854ceb1929dae1 node-v16.4.0-win-x64.zip
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v18.0.0-rc.3-darwin-arm64.tar.gz
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v18.0.0-rc.3-linux-arm64.tar.gz
|
||||
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef node-v18.0.0-rc.3-linux-x64.tar.gz
|
||||
07e6121cba611b57f310a489f76c413b6246e79cffe1e9538b2478ffee11c99e node-v18.0.0-rc.3-win-x64.zip
|
||||
`)
|
||||
}
|
||||
if (url.endsWith('.tar.gz')) {
|
||||
const pack = tar.pack()
|
||||
pack.finalize()
|
||||
@@ -23,10 +35,15 @@ const fetchMock = jest.fn(async (url: string) => {
|
||||
// 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'))
|
||||
const zipfile = new ZipFile()
|
||||
|
||||
return new Response(Readable.from(zip.toBuffer()))
|
||||
zipfile.addBuffer(Buffer.from('test'), `${pkgName}/dummy-file`, {
|
||||
mtime: new Date(0), // fixed timestamp for determinism
|
||||
mode: 0o100644, // fixed file permissions
|
||||
})
|
||||
|
||||
zipfile.end()
|
||||
return new Response(Readable.from(zipfile.outputStream))
|
||||
}
|
||||
|
||||
return new Response(Readable.from(Buffer.alloc(0)))
|
||||
@@ -94,7 +111,7 @@ test('install an rc version of Node.js', async () => {
|
||||
const platform = process.platform === 'win32' ? 'win' : process.platform
|
||||
const arch = process.arch
|
||||
const extension = process.platform === 'win32' ? 'zip' : 'tar.gz'
|
||||
expect(fetchMock.mock.calls[0][0]).toBe(
|
||||
expect(fetchMock.mock.calls[1][0]).toBe(
|
||||
`https://nodejs.org/download/rc/v18.0.0-rc.3/node-v18.0.0-rc.3-${platform}-${arch}.${extension}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type RetryTimeoutOptions } from '@zkochan/retry'
|
||||
import { type Response, type RequestInit as NodeRequestInit } from 'node-fetch'
|
||||
|
||||
export type { RetryTimeoutOptions }
|
||||
export type { RetryTimeoutOptions, Response }
|
||||
|
||||
export interface RequestInit extends NodeRequestInit {
|
||||
retry?: RetryTimeoutOptions
|
||||
|
||||
66
pnpm-lock.yaml
generated
66
pnpm-lock.yaml
generated
@@ -183,6 +183,9 @@ catalogs:
|
||||
'@types/yarnpkg__lockfile':
|
||||
specifier: ^1.1.9
|
||||
version: 1.1.9
|
||||
'@types/yazl':
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
'@types/zkochan__table':
|
||||
specifier: npm:@types/table@6.0.0
|
||||
version: 6.0.0
|
||||
@@ -651,6 +654,9 @@ catalogs:
|
||||
yaml-tag:
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0
|
||||
yazl:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
|
||||
overrides:
|
||||
'@yarnpkg/fslib@2': '3'
|
||||
@@ -2069,6 +2075,9 @@ importers:
|
||||
rename-overwrite:
|
||||
specifier: 'catalog:'
|
||||
version: 6.0.2
|
||||
ssri:
|
||||
specifier: 'catalog:'
|
||||
version: 10.0.5
|
||||
tempy:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.1
|
||||
@@ -2085,6 +2094,9 @@ importers:
|
||||
'@types/adm-zip':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.7
|
||||
'@types/ssri':
|
||||
specifier: 'catalog:'
|
||||
version: 7.1.5
|
||||
node-fetch:
|
||||
specifier: 'catalog:'
|
||||
version: '@pnpm/node-fetch@1.0.0'
|
||||
@@ -2193,9 +2205,6 @@ importers:
|
||||
'@pnpm/prepare':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/prepare
|
||||
'@types/adm-zip':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.7
|
||||
'@types/graceful-fs':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.9
|
||||
@@ -2208,9 +2217,9 @@ importers:
|
||||
'@types/tar-stream':
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.3
|
||||
adm-zip:
|
||||
'@types/yazl':
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.16
|
||||
version: 3.3.0
|
||||
execa:
|
||||
specifier: 'catalog:'
|
||||
version: safe-execa@0.1.2
|
||||
@@ -2226,6 +2235,9 @@ importers:
|
||||
tar-stream:
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.0
|
||||
yazl:
|
||||
specifier: 'catalog:'
|
||||
version: 3.3.1
|
||||
|
||||
env/system-node-version:
|
||||
dependencies:
|
||||
@@ -10271,6 +10283,9 @@ packages:
|
||||
'@types/yarnpkg__lockfile@1.1.9':
|
||||
resolution: {integrity: sha512-GD4Fk15UoP5NLCNor51YdfL9MSdldKCqOC9EssrRw3HVfar9wUZ5y8Lfnp+qVD6hIinLr8ygklDYnmlnlQo12Q==}
|
||||
|
||||
'@types/yazl@3.3.0':
|
||||
resolution: {integrity: sha512-mFL6lGkk2N5u5nIxpNV/K5LW3qVSbxhJrMxYGOOxZndWxMgCamr/iCsq/1t9kd8pEwhuNP91LC5qZm/qS9pOEw==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@6.18.1':
|
||||
resolution: {integrity: sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
@@ -10821,6 +10836,10 @@ packages:
|
||||
bser@2.1.1:
|
||||
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
|
||||
|
||||
buffer-crc32@1.0.0:
|
||||
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
|
||||
@@ -15360,6 +15379,9 @@ packages:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yazl@3.3.1:
|
||||
resolution: {integrity: sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==}
|
||||
|
||||
yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -16326,7 +16348,7 @@ snapshots:
|
||||
'@jest/console@29.7.0':
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
chalk: 4.1.2
|
||||
jest-message-util: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
@@ -16372,7 +16394,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
jest-mock: 29.7.0
|
||||
|
||||
'@jest/expect-utils@29.7.0':
|
||||
@@ -16390,7 +16412,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@sinonjs/fake-timers': 10.3.0
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
jest-message-util: 29.7.0
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
@@ -16412,7 +16434,7 @@ snapshots:
|
||||
'@jest/transform': 29.7.0(@babel/types@7.26.10)
|
||||
'@jest/types': 29.6.3
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
chalk: 4.1.2
|
||||
collect-v8-coverage: 1.0.2
|
||||
exit: 0.1.2
|
||||
@@ -18030,6 +18052,10 @@ snapshots:
|
||||
|
||||
'@types/yarnpkg__lockfile@1.1.9': {}
|
||||
|
||||
'@types/yazl@3.3.0':
|
||||
dependencies:
|
||||
'@types/node': 22.15.29
|
||||
|
||||
'@typescript-eslint/eslint-plugin@6.18.1(@typescript-eslint/parser@6.18.1(eslint@8.57.1)(typescript@5.5.4))(eslint@8.57.1)(typescript@5.5.4)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@@ -18825,6 +18851,8 @@ snapshots:
|
||||
dependencies:
|
||||
node-int64: 0.4.0
|
||||
|
||||
buffer-crc32@1.0.0: {}
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-equal@1.0.1: {}
|
||||
@@ -20991,7 +21019,7 @@ snapshots:
|
||||
'@jest/expect': 29.7.0
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
chalk: 4.1.2
|
||||
co: 4.6.0
|
||||
dedent: 1.6.0
|
||||
@@ -21088,7 +21116,7 @@ snapshots:
|
||||
'@jest/environment': 29.7.0
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
|
||||
@@ -21098,7 +21126,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/graceful-fs': 4.1.9
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
anymatch: 3.1.3
|
||||
fb-watchman: 2.0.2
|
||||
graceful-fs: 4.2.11(patch_hash=68ebc232025360cb3dcd3081f4067f4e9fc022ab6b6f71a3230e86c7a5b337d1)
|
||||
@@ -21137,7 +21165,7 @@ snapshots:
|
||||
jest-mock@29.7.0:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
jest-util: 29.7.0
|
||||
|
||||
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
|
||||
@@ -21172,7 +21200,7 @@ snapshots:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/transform': 29.7.0(@babel/types@7.26.10)
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
chalk: 4.1.2
|
||||
emittery: 0.13.1
|
||||
graceful-fs: 4.2.11(patch_hash=68ebc232025360cb3dcd3081f4067f4e9fc022ab6b6f71a3230e86c7a5b337d1)
|
||||
@@ -21201,7 +21229,7 @@ snapshots:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/transform': 29.7.0(@babel/types@7.26.10)
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
chalk: 4.1.2
|
||||
cjs-module-lexer: 1.4.3
|
||||
collect-v8-coverage: 1.0.2
|
||||
@@ -21267,7 +21295,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
emittery: 0.13.1
|
||||
@@ -21276,7 +21304,7 @@ snapshots:
|
||||
|
||||
jest-worker@29.7.0:
|
||||
dependencies:
|
||||
'@types/node': 18.19.34
|
||||
'@types/node': 22.15.29
|
||||
jest-util: 29.7.0
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
@@ -24019,6 +24047,10 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yazl@3.3.1:
|
||||
dependencies:
|
||||
buffer-crc32: 1.0.0
|
||||
|
||||
yn@3.1.1: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
@@ -102,6 +102,7 @@ catalog:
|
||||
'@types/which': ^2.0.2
|
||||
'@types/write-file-atomic': ^4.0.3
|
||||
'@types/yarnpkg__lockfile': ^1.1.9
|
||||
'@types/yazl': ^3.3.0
|
||||
'@types/zkochan__table': npm:@types/table@6.0.0
|
||||
'@yarnpkg/core': 4.2.0
|
||||
'@yarnpkg/extensions': 2.0.3
|
||||
@@ -261,6 +262,7 @@ catalog:
|
||||
write-pkg: 4.0.0
|
||||
write-yaml-file: ^5.0.0
|
||||
yaml-tag: 1.1.0
|
||||
yazl: ^3.3.1
|
||||
|
||||
enableGlobalVirtualStore: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user