import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { existsSync } from 'node:fs'; import fs from 'node:fs/promises'; import { createRequire } from 'node:module'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; const npmRoot = [ path.resolve(path.dirname(process.execPath), 'node_modules/npm'), path.resolve(path.dirname(process.execPath), '../lib/node_modules/npm'), ...(process.env.npm_execpath ? [path.resolve(path.dirname(process.env.npm_execpath), '..')] : []), ].find((root) => existsSync(path.join(root, 'node_modules/cacache'))); assert.ok(npmRoot, 'tests require the cacache bundled with npm'); const cacache = createRequire(path.join(npmRoot, 'package.json'))('cacache'); const script = fileURLToPath( new URL('./export-ci-npm-download-cache.mjs', import.meta.url), ); async function fixture(t) { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ci-npm-snapshot-')); t.after(() => fs.rm(root, { recursive: true, force: true })); const source = path.join(root, 'source'); const destination = path.join(root, 'destination'); const lock = path.join(root, 'package-lock.json'); const key = 'make-fetch-happen:request-cache:https://registry.npmjs.org/example/-/example-1.0.0.tgz'; const metadata = { url: key.slice('make-fetch-happen:request-cache:'.length), }; const integrity = String( await cacache.put(source, key, 'current-package', { metadata }), ); await cacache.put(source, 'old-package', 'unused-old-version'); await fs.writeFile( lock, JSON.stringify({ packages: { 'node_modules/example': { integrity } } }), ); const run = () => spawnSync(process.execPath, [script, npmRoot, lock, source, destination], { encoding: 'utf8', }); return { source, destination, key, metadata, integrity, run }; } test('exports only current lock content, preserving npm request metadata for offline use', async (t) => { const f = await fixture(t); const result = f.run(); assert.equal(result.status, 0, result.stderr); assert.deepEqual(Object.keys(await cacache.ls(f.destination)), [f.key]); const output = await cacache.get(f.destination, f.key); assert.equal(output.data.toString(), 'current-package'); assert.deepEqual(output.metadata, f.metadata); // 输出是独立快照;移走持久缓存仍可使用,不依赖挂载、链接或旧 builder。 await fs.rm(f.source, { recursive: true }); assert.equal( (await cacache.get(f.destination, f.key)).data.toString(), 'current-package', ); }); test('rejects a corrupted cached package instead of publishing it', async (t) => { const f = await fixture(t); const digest = Buffer.from(f.integrity.split('-')[1], 'base64').toString( 'hex', ); const contentPath = path.join( f.source, 'content-v2', 'sha512', digest.slice(0, 2), digest.slice(2, 4), digest.slice(4), ); await fs.writeFile(contentPath, 'corrupted-package'); const result = f.run(); assert.notEqual(result.status, 0); assert.match(result.stderr, /EINTEGRITY|EBADSIZE/); });