Add TypeScript ports of the perfectionist rules that the Rust workspace enforces through dylint.toml to @pnpm/eslint-config, with the same limits, and fix every finding that needs no refactor. The size and shape rules (function length, cognitive complexity, nesting, local bindings, file length) are enforced for packages that already meet them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR in eslint.config.mjs and are refactored in follow-up changes. The custom rules mirror perfectionist's semantics rather than reusing sonarjs or max-lines-per-function, so that one limit means the same thing in both codebases: a closure is a level of the body that contains it, a nested function declaration is measured on its own, and a test-framework callback plays the role of a #[test] function. Bugs found and fixed along the way: - a registry error body cut at exactly 64 KiB lost its truncation marker - removing a dependency could leave dangling .bin shims - constructor/__proto__ names crashed the projects graph, were skipped by the injected-deps syncer, and were dropped from writeWantedLockfile's returned lockfile The CI chunk runner learns to run the node:test suite of the ESLint config package.
343 lines
13 KiB
TypeScript
343 lines
13 KiB
TypeScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
|
|
import { expect, test } from '@jest/globals'
|
|
import type { FetchFromRegistry } from '@pnpm/fetching.types'
|
|
import type { PlatformAssetResolution } from '@pnpm/resolving.resolver-base'
|
|
|
|
import { DEFAULT_NODE_MIRROR_BASE_URL, resolveNodeRuntime, resolveNodeVersion, UNOFFICIAL_NODE_MIRROR_BASE_URL } from '../lib/index.js'
|
|
|
|
const MIRROR = 'https://node.example/download/rc/'
|
|
|
|
const fetch: FetchFromRegistry = async (url) => {
|
|
switch (url) {
|
|
case `${MIRROR}index.json`:
|
|
return new Response(JSON.stringify([
|
|
{ version: 'v22.11.0', lts: false },
|
|
{ version: 'v22.10.0', lts: false },
|
|
]))
|
|
case `${MIRROR}v22.11.0/SHASUMS256.txt`:
|
|
return new Response('ed52239294ad517fbe91a268146d5d2aa8a17d2d62d64873e43219078ba71c4e node-v22.11.0-linux-x64.tar.gz\n')
|
|
default:
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
}
|
|
|
|
test.each([
|
|
['runtime:rc/22', undefined, 'runtime:22.11.0'],
|
|
['runtime:rc/^22', undefined, 'runtime:^22.11.0'],
|
|
['runtime:rc/22', 'runtime:~22.0.0', 'runtime:~22.11.0'],
|
|
['runtime:rc/^22', 'runtime:22.0.0', 'runtime:22.11.0'],
|
|
])('resolveNodeRuntime() preserves runtime version prefix (%s, previous %s)', async (bareSpecifier, prevSpecifier, expected) => {
|
|
const resolution = await resolveNodeRuntime({
|
|
fetchFromRegistry: fetch,
|
|
nodeDownloadMirrors: {
|
|
rc: MIRROR,
|
|
},
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier,
|
|
prevSpecifier,
|
|
})
|
|
|
|
expect(resolution?.normalizedBareSpecifier).toBe(expected)
|
|
})
|
|
|
|
test('resolveNodeRuntime() authenticates release index and SHASUMS requests without sharing cached metadata', async () => {
|
|
const requests: Array<{ url: string, authHeaderValue?: string }> = []
|
|
const authenticatedFetch: FetchFromRegistry = async (url, opts) => {
|
|
requests.push({ url, authHeaderValue: opts?.authHeaderValue })
|
|
return fetch(url)
|
|
}
|
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pnpm-node-resolver-auth-'))
|
|
const runResolve = async () => resolveNodeRuntime({
|
|
fetchFromRegistry: authenticatedFetch,
|
|
getAuthHeader: url => url.startsWith(MIRROR) ? 'Bearer mirror-token' : undefined,
|
|
nodeDownloadMirrors: { rc: MIRROR },
|
|
cacheDir,
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/22',
|
|
})
|
|
try {
|
|
await runResolve()
|
|
await runResolve()
|
|
|
|
expect(requests).toEqual([
|
|
{ url: `${MIRROR}index.json`, authHeaderValue: 'Bearer mirror-token' },
|
|
{ url: `${MIRROR}v22.11.0/SHASUMS256.txt`, authHeaderValue: 'Bearer mirror-token' },
|
|
{ url: `${MIRROR}index.json`, authHeaderValue: 'Bearer mirror-token' },
|
|
{ url: `${MIRROR}v22.11.0/SHASUMS256.txt`, authHeaderValue: 'Bearer mirror-token' },
|
|
])
|
|
} finally {
|
|
await fs.promises.rm(cacheDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test.each([
|
|
'http://node.example/download/rc/',
|
|
'http://127.attacker.example/download/rc/',
|
|
])('resolveNodeRuntime() omits credentials for remote HTTP mirror %s', async (mirror) => {
|
|
const requests: Array<{ url: string, authHeaderValue?: string }> = []
|
|
const httpFetch: FetchFromRegistry = async (url, opts) => {
|
|
requests.push({ url, authHeaderValue: opts?.authHeaderValue })
|
|
if (url === `${mirror}index.json`) {
|
|
return new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }]))
|
|
}
|
|
if (url === `${mirror}v22.11.0/SHASUMS256.txt`) {
|
|
return new Response('ed52239294ad517fbe91a268146d5d2aa8a17d2d62d64873e43219078ba71c4e node-v22.11.0-linux-x64.tar.gz\n')
|
|
}
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
|
|
await resolveNodeRuntime({
|
|
fetchFromRegistry: httpFetch,
|
|
getAuthHeader: () => 'Bearer mirror-token',
|
|
nodeDownloadMirrors: { rc: mirror },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/22',
|
|
})
|
|
|
|
expect(requests).toEqual([
|
|
{ url: `${mirror}index.json`, authHeaderValue: undefined },
|
|
{ url: `${mirror}v22.11.0/SHASUMS256.txt`, authHeaderValue: undefined },
|
|
])
|
|
})
|
|
|
|
test('resolveNodeVersion() selects credentials again after a redirect', async () => {
|
|
const startUrl = `${MIRROR}index.json`
|
|
const redirectedUrl = 'http://node.example/public/index.json'
|
|
const requests: Array<{ url: string, authHeaderValue?: string }> = []
|
|
const redirectingFetch: FetchFromRegistry = async (url, opts) => {
|
|
requests.push({ url, authHeaderValue: opts?.authHeaderValue })
|
|
if (url === startUrl) {
|
|
return new Response(null, { status: 302, headers: { location: redirectedUrl } })
|
|
}
|
|
if (url === redirectedUrl) {
|
|
return new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }]))
|
|
}
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
|
|
const version = await resolveNodeVersion(redirectingFetch, 'latest', {
|
|
nodeMirrorBaseUrl: MIRROR,
|
|
getAuthHeader: url => url.startsWith(MIRROR) ? 'Bearer mirror-token' : undefined,
|
|
})
|
|
|
|
expect(version).toBe('22.11.0')
|
|
expect(requests).toEqual([
|
|
{ url: startUrl, authHeaderValue: 'Bearer mirror-token' },
|
|
{ url: redirectedUrl, authHeaderValue: undefined },
|
|
])
|
|
})
|
|
|
|
const RELEASE_MIRROR = 'https://node.example/download/release/'
|
|
|
|
// An exact-specifier resolve skips the release index, so a nonexistent
|
|
// version first fails its asset fetch; the resolver must then consult the
|
|
// index and raise the canonical not-found error rather than the raw fetch
|
|
// failure.
|
|
test('resolveNodeRuntime() raises NODEJS_VERSION_NOT_FOUND for a nonexistent exact version', async () => {
|
|
const { fetch: countedFetch, calls } = countingFetch({
|
|
[`${RELEASE_MIRROR}index.json`]: () => new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }])),
|
|
})
|
|
|
|
await expect(resolveNodeRuntime({
|
|
fetchFromRegistry: countedFetch,
|
|
nodeDownloadMirrors: { release: RELEASE_MIRROR },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:22.99.0',
|
|
})).rejects.toThrow(/Could not find a Node.js version that satisfies 22.99.0/)
|
|
// The asset fetch runs first; the index is only consulted to classify the
|
|
// failure.
|
|
expect(calls[0]).toBe(`${RELEASE_MIRROR}v22.99.0/SHASUMS256.txt`)
|
|
expect(calls).toContain(`${RELEASE_MIRROR}index.json`)
|
|
})
|
|
|
|
// When the index confirms the exact version exists, the asset-fetch failure
|
|
// is the real error and must surface unchanged.
|
|
test('resolveNodeRuntime() keeps the asset error when the exact version exists', async () => {
|
|
const { fetch: countedFetch } = countingFetch({
|
|
[`${RELEASE_MIRROR}index.json`]: () => new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }])),
|
|
[`${RELEASE_MIRROR}v22.11.0/SHASUMS256.txt`]: () => new Response(null, { status: 500 }),
|
|
})
|
|
|
|
await expect(resolveNodeRuntime({
|
|
fetchFromRegistry: countedFetch,
|
|
nodeDownloadMirrors: { release: RELEASE_MIRROR },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:22.11.0',
|
|
})).rejects.toThrow(/SHASUMS256.txt/)
|
|
})
|
|
|
|
// A SHASUMS body cached by an earlier resolve serves the next one without
|
|
// refetching it; only the (mutable) release index is fetched again.
|
|
test('resolveNodeRuntime() serves repeat asset reads from the cache', async () => {
|
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pnpm-node-resolver-'))
|
|
try {
|
|
const { fetch: countedFetch, calls } = countingFetch({
|
|
[`${MIRROR}index.json`]: () => new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }])),
|
|
[`${MIRROR}v22.11.0/SHASUMS256.txt`]: () => new Response('ed52239294ad517fbe91a268146d5d2aa8a17d2d62d64873e43219078ba71c4e node-v22.11.0-linux-x64.tar.gz\n'),
|
|
})
|
|
|
|
for (let run = 0; run < 2; run++) {
|
|
// eslint-disable-next-line no-await-in-loop -- the second run must see the cache written by the first
|
|
const resolution = await resolveNodeRuntime({
|
|
fetchFromRegistry: countedFetch,
|
|
nodeDownloadMirrors: { rc: MIRROR },
|
|
cacheDir,
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/22',
|
|
})
|
|
expect(resolution?.resolution.variants).toHaveLength(1)
|
|
}
|
|
|
|
expect(calls.filter((url) => url === `${MIRROR}v22.11.0/SHASUMS256.txt`)).toHaveLength(1)
|
|
expect(calls.filter((url) => url === `${MIRROR}index.json`)).toHaveLength(2)
|
|
} finally {
|
|
await fs.promises.rm(cacheDir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
function countingFetch (responses: Record<string, () => Response>): { fetch: FetchFromRegistry, calls: string[] } {
|
|
const calls: string[] = []
|
|
const countedFetch = (async (url: string) => {
|
|
calls.push(url)
|
|
const response = responses[url]
|
|
if (!response) return new Response(null, { status: 404 })
|
|
return response()
|
|
}) as unknown as FetchFromRegistry
|
|
return { fetch: countedFetch, calls }
|
|
}
|
|
|
|
const OFFICIAL_INDEX_URL = `${DEFAULT_NODE_MIRROR_BASE_URL}index.json`
|
|
const OFFICIAL_SHASUMS_URL = `${DEFAULT_NODE_MIRROR_BASE_URL}v22.11.0/SHASUMS256.txt`
|
|
const UNOFFICIAL_SHASUMS_URL = `${UNOFFICIAL_NODE_MIRROR_BASE_URL}v22.11.0/SHASUMS256.txt`
|
|
|
|
test('resolveNodeRuntime() skips the musl assets of a release unofficial-builds never built', async () => {
|
|
const variants = await resolveMuslVariants(async () => new Response(null, { status: 404 }))
|
|
|
|
expect(variants.map(({ targets }) => targets[0].libc)).toStrictEqual([undefined])
|
|
})
|
|
|
|
test('resolveNodeRuntime() reads the musl assets unofficial-builds publishes', async () => {
|
|
const variants = await resolveMuslVariants(async () => new Response(MUSL_SHASUMS))
|
|
|
|
expect(variants.map(({ targets }) => targets[0].libc)).toStrictEqual([undefined, 'musl'])
|
|
})
|
|
|
|
test.each([
|
|
403,
|
|
500,
|
|
])('resolveNodeRuntime() fails when unofficial-builds answers %i', async (status) => {
|
|
await expect(resolveMuslVariants(async () => new Response(null, { status })))
|
|
.rejects.toThrow(`Failed to fetch integrity file: ${UNOFFICIAL_SHASUMS_URL} (status: ${status})`)
|
|
})
|
|
|
|
test('resolveNodeRuntime() fails when unofficial-builds cannot be reached', async () => {
|
|
await expect(resolveMuslVariants(() => Promise.reject(new Error('getaddrinfo ENOTFOUND unofficial-builds.nodejs.org'))))
|
|
.rejects.toThrow('getaddrinfo ENOTFOUND unofficial-builds.nodejs.org')
|
|
})
|
|
|
|
/**
|
|
* The musl request is only made when the picked mirror is the default one, and
|
|
* pointing the `rc` channel at it keeps that mirror's own SHASUMS file
|
|
* unsigned, so no release signature has to be minted for the official half.
|
|
*/
|
|
async function resolveMuslVariants (respondToMuslRequest: () => Promise<Response>): Promise<PlatformAssetResolution[]> {
|
|
const fetch: FetchFromRegistry = async (url) => {
|
|
switch (url) {
|
|
case OFFICIAL_INDEX_URL:
|
|
return new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }]))
|
|
case OFFICIAL_SHASUMS_URL:
|
|
return new Response(GLIBC_SHASUMS)
|
|
case UNOFFICIAL_SHASUMS_URL:
|
|
return respondToMuslRequest()
|
|
default:
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
}
|
|
const resolution = await resolveNodeRuntime({
|
|
fetchFromRegistry: fetch,
|
|
nodeDownloadMirrors: { rc: DEFAULT_NODE_MIRROR_BASE_URL },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/22.11.0',
|
|
})
|
|
return resolution!.resolution.variants
|
|
}
|
|
|
|
const GLIBC_SHASUMS = 'ed52239294ad517fbe91a268146d5d2aa8a17d2d62d64873e43219078ba71c4e node-v22.11.0-linux-x64.tar.gz\n'
|
|
const MUSL_SHASUMS = '696cb00a4b9d0e4dd2eb95e5fe32e8ff1ac2c3dfe54c7a2a5f03f7f9e6f0b1c2 node-v22.11.0-linux-x64-musl.tar.gz\n'
|
|
|
|
test('resolveNodeRuntime() resolves native win-arm64 variant for Node 20+', async () => {
|
|
const fetch: FetchFromRegistry = async (url) => {
|
|
if (url === `${MIRROR}index.json`) {
|
|
return new Response(JSON.stringify([{ version: 'v22.11.0', lts: false }]))
|
|
}
|
|
if (url === `${MIRROR}v22.11.0/SHASUMS256.txt`) {
|
|
return new Response(
|
|
'b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2 node-v22.11.0-win-x64.zip\n' +
|
|
'a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 node-v22.11.0-win-arm64.zip\n'
|
|
)
|
|
}
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
|
|
const resolution = await resolveNodeRuntime({
|
|
fetchFromRegistry: fetch,
|
|
nodeDownloadMirrors: { rc: MIRROR },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/22.11.0',
|
|
})
|
|
|
|
const variants = resolution!.resolution.variants
|
|
const winArm64 = variants.find(variant => variant.targets.some(target => target.os === 'win32' && target.cpu === 'arm64'))
|
|
expect(winArm64).toBeDefined()
|
|
expect(winArm64!.targets).toStrictEqual([{ os: 'win32', cpu: 'arm64' }])
|
|
expect(winArm64!.resolution).toMatchObject({
|
|
url: `${MIRROR}v22.11.0/node-v22.11.0-win-arm64.zip`,
|
|
archive: 'zip',
|
|
prefix: 'node-v22.11.0-win-arm64',
|
|
})
|
|
})
|
|
|
|
test('resolveNodeRuntime() includes win32-arm64 target on win-x64 variant for Node < 20', async () => {
|
|
const fetch: FetchFromRegistry = async (url) => {
|
|
if (url === `${MIRROR}index.json`) {
|
|
return new Response(JSON.stringify([{ version: 'v18.20.0', lts: false }]))
|
|
}
|
|
if (url === `${MIRROR}v18.20.0/SHASUMS256.txt`) {
|
|
return new Response('c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3 node-v18.20.0-win-x64.zip\n')
|
|
}
|
|
throw new Error(`Unexpected URL: ${url}`)
|
|
}
|
|
|
|
const resolution = await resolveNodeRuntime({
|
|
fetchFromRegistry: fetch,
|
|
nodeDownloadMirrors: { rc: MIRROR },
|
|
}, {
|
|
alias: 'node',
|
|
bareSpecifier: 'runtime:rc/18.20.0',
|
|
})
|
|
|
|
const variants = resolution!.resolution.variants
|
|
expect(variants).toHaveLength(1)
|
|
expect(variants[0].targets).toStrictEqual([
|
|
{ os: 'win32', cpu: 'x64' },
|
|
{ os: 'win32', cpu: 'arm64' },
|
|
])
|
|
expect(variants[0].resolution).toMatchObject({
|
|
url: `${MIRROR}v18.20.0/node-v18.20.0-win-x64.zip`,
|
|
archive: 'zip',
|
|
prefix: 'node-v18.20.0-win-x64',
|
|
})
|
|
})
|
|
|