diff --git a/packages/agent-bundle/tests/cli.test.ts b/packages/agent-bundle/tests/cli.test.ts index 0d3b36410..1e828be26 100644 --- a/packages/agent-bundle/tests/cli.test.ts +++ b/packages/agent-bundle/tests/cli.test.ts @@ -7,6 +7,7 @@ import { promisify } from 'node:util'; import { expect, it } from '@rstest/core'; import { runCli as runSourceCli } from '../src/cli.ts'; +import { cachedNpmInstallArguments } from './support/shared-pack.ts'; const execFile = promisify(executeFile); const workspaceRoot = process.cwd(); @@ -128,7 +129,7 @@ const createPackedConsumer = async (): Promise<{ readonly cli: string; readonly const [packed] = JSON.parse(stdout) as Array<{ readonly filename: string }>; await writeFile(join(root, 'package.json'), '{"type":"module"}\n'); await execFile( - 'npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', join(root, packed.filename)], + 'npm', ['install', ...cachedNpmInstallArguments, join(root, packed.filename)], { cwd: root }, ); return { cli: join(root, 'node_modules', '.bin', 'agent-bundle'), root }; diff --git a/packages/agent-bundle/tests/dev-workbench-packaging.test.ts b/packages/agent-bundle/tests/dev-workbench-packaging.test.ts index 326ffde08..6d12f3b70 100644 --- a/packages/agent-bundle/tests/dev-workbench-packaging.test.ts +++ b/packages/agent-bundle/tests/dev-workbench-packaging.test.ts @@ -7,7 +7,7 @@ import { promisify } from 'node:util'; import { describe, expect, it } from '@rstest/core'; -import { installedEnvironment, npmInstallArguments, sharedPackedTarball } from './support/shared-pack.ts'; +import { cachedNpmInstallArguments, installedEnvironment, sharedPackedTarball } from './support/shared-pack.ts'; const execFile = promisify(executeFile); const workspaceRoot = process.cwd(); @@ -83,7 +83,7 @@ it('serves prebuilt workbench assets from an installed tarball without the repos expect(listing.stdout).not.toMatch(/package\/dist\/workbench\/.*-[a-f0-9]{8,}/iu); await writeFile(join(consumer, 'package.json'), '{"type":"module"}\n'); - await execFile('npm', ['install', ...npmInstallArguments, tarball], { cwd: consumer }); + await execFile('npm', ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumer }); await mkdir(join(project, 'skills', 'review'), { recursive: true }); await Promise.all([ writeFile(join(project, 'package.json'), '{"type":"module"}\n'), @@ -115,7 +115,7 @@ it('runs the Agent API from an omit-dev installed tarball with its runtime MCP d const project = join(consumer, 'project'); try { await writeFile(join(consumer, 'package.json'), '{"type":"module"}\n'); - await execFile('npm', ['install', '--omit=dev', ...npmInstallArguments, tarball], { cwd: consumer }); + await execFile('npm', ['install', '--omit=dev', ...cachedNpmInstallArguments, tarball], { cwd: consumer }); await mkdir(join(project, 'skills', 'review'), { recursive: true }); await Promise.all([ writeFile(join(project, 'package.json'), '{"type":"module"}\n'), diff --git a/packages/agent-bundle/tests/packed-consumer.test.ts b/packages/agent-bundle/tests/packed-consumer.test.ts index 10b639724..aec4d5f95 100644 --- a/packages/agent-bundle/tests/packed-consumer.test.ts +++ b/packages/agent-bundle/tests/packed-consumer.test.ts @@ -20,7 +20,7 @@ import { promisify } from 'node:util'; import { expect, it } from '@rstest/core'; import { sha256Hex } from '../src/core/digest.ts'; -import { installedEnvironment, npmInstallArguments } from './support/shared-pack.ts'; +import { cachedNpmInstallArguments, installedEnvironment } from './support/shared-pack.ts'; const execFile = promisify(executeFile); const workspaceRoot = process.cwd(); @@ -110,7 +110,7 @@ it('uses only an installed tarball after source deletion', async () => { // The two consumers are disjoint directories; npm's cache handles the // concurrent installs (the scaffolder e2e relies on the same property). await Promise.all([projectRoot, scriptProjectRoot].map(async (consumer) => - execFile('npm', ['install', ...npmInstallArguments, tarball], { + execFile('npm', ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumer, env: installedEnvironment(), }))); @@ -331,7 +331,7 @@ it('uses only an installed tarball after source deletion', async () => { '', ].join('\n')), ]); - await execFile('npm', ['install', ...npmInstallArguments, tarball], { + await execFile('npm', ['install', ...cachedNpmInstallArguments, tarball], { cwd: frameworkRoot, env: installedEnvironment(), }); diff --git a/packages/agent-bundle/tests/public-api-packed.test.ts b/packages/agent-bundle/tests/public-api-packed.test.ts index 5db4ae6bf..b8a68b99f 100644 --- a/packages/agent-bundle/tests/public-api-packed.test.ts +++ b/packages/agent-bundle/tests/public-api-packed.test.ts @@ -7,7 +7,7 @@ import { promisify } from 'node:util'; import { expect, it } from '@rstest/core'; import { writeFixtureManifest } from './support/manifest.ts'; -import { npmInstallArguments, sharedPackedTarball } from './support/shared-pack.ts'; +import { cachedNpmInstallArguments, sharedPackedTarball } from './support/shared-pack.ts'; interface PackageManifest { bin: { @@ -58,7 +58,7 @@ it('writes the package version as the producer of a packed CLI manifest', async try { await writeFile(join(consumerRoot, 'package.json'), '{"type":"module"}\n'); await execFile( - 'npm', ['install', ...npmInstallArguments, tarball], + 'npm', ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumerRoot }, ); @@ -90,7 +90,7 @@ it('imports the externalized config entry from a packed npm consumer', async () await writeFile(join(consumerRoot, 'package.json'), '{"type":"module"}\n'); await execFile( 'npm', - ['install', ...npmInstallArguments, tarball], + ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumerRoot }, ); @@ -198,7 +198,7 @@ it('invokes a prebuilt MCP server from a clean packed consumer', async () => { await writeFile(join(consumerRoot, 'package.json'), '{"type":"module"}\n'); await execFile( 'npm', - ['install', ...npmInstallArguments, tarball], + ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumerRoot }, ); const { stdout } = await execFile(process.execPath, [ diff --git a/packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts b/packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts index ee8477592..d0a996933 100644 --- a/packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts +++ b/packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts @@ -6,7 +6,7 @@ import { promisify } from 'node:util'; import { describe, expect, it } from '@rstest/core'; -import { npmInstallArguments, sharedPackedTarball } from './support/shared-pack.ts'; +import { cachedNpmInstallArguments, sharedPackedTarball } from './support/shared-pack.ts'; const execFile = promisify(executeFile); const workspaceRoot = process.cwd(); @@ -45,7 +45,7 @@ describe.sequential('optional RSC runtime package boundary', () => { const tarListing = (await execFile('tar', ['-tf', tarball])).stdout; expect(tarListing).not.toMatch(/examples\/rsc-agent-runtime|react-server-dom-rspack|rsbuild-plugin-rsc/u); - await execFile('npm', ['install', ...npmInstallArguments, tarball], { cwd: consumer }); + await execFile('npm', ['install', ...cachedNpmInstallArguments, tarball], { cwd: consumer }); const dependencyTree = JSON.parse((await execFile('npm', ['ls', '--all', '--json'], { cwd: consumer })).stdout) as InstalledDependencyTree; const installedNames = installedDependencyNames(dependencyTree); for (const name of ['react', 'react-dom', 'react-server-dom-rspack', 'rsbuild-plugin-rsc']) { diff --git a/packages/agent-bundle/tests/support/packed-native-smoke.ts b/packages/agent-bundle/tests/support/packed-native-smoke.ts index 99f38793c..00e244612 100644 --- a/packages/agent-bundle/tests/support/packed-native-smoke.ts +++ b/packages/agent-bundle/tests/support/packed-native-smoke.ts @@ -15,6 +15,10 @@ import { homedir, tmpdir } from 'node:os'; import { join } from 'node:path'; import { promisify } from 'node:util'; +// The native smoke installs the production closure a real consumer would get, +// so it stays on npm's default metadata staleness checks. +import { npmInstallArguments } from './shared-pack.ts'; + const execFile = promisify(executeFile); const workspaceRoot = process.cwd(); const packageRoot = join(workspaceRoot, 'packages', 'agent-bundle'); @@ -236,9 +240,7 @@ export const runPackedNativeSmoke = async (options: { const installed = await runNodeEntrypoint(npmEntrypoint, [ 'install', '--omit=dev', - '--ignore-scripts', - '--no-audit', - '--no-fund', + ...npmInstallArguments, join(tarballs, listing[0].filename), ], { cwd: consumer, environment }); if (installed.exitCode !== 0) throw new Error('Packed native smoke could not install the release tarball.'); diff --git a/packages/agent-bundle/tests/support/shared-pack.ts b/packages/agent-bundle/tests/support/shared-pack.ts index 124584daf..131a1959f 100644 --- a/packages/agent-bundle/tests/support/shared-pack.ts +++ b/packages/agent-bundle/tests/support/shared-pack.ts @@ -26,9 +26,28 @@ export const installedEnvironment = (): NodeJS.ProcessEnv => { return environment; }; -/** Canonical flags for installing a packed tarball into a consumer fixture. */ +/** + * Canonical flags for installing a packed tarball into a consumer fixture. + * They keep npm's default metadata staleness checks, so the install resolves + * the tree a consumer would get today. That is the point of the proofs that + * stand in for a real consumer — release-audit's production entrypoint walk, + * the scaffolder template matrix, the native host smoke — and exact direct + * pins do not make it free: transitive ranges still move underneath them, and + * scripts/audit-packed-release.mjs audits the installed tree without ever + * exercising it. + */ export const npmInstallArguments = ['--ignore-scripts', '--no-audit', '--no-fund'] as const; +/** + * The same flags for suites that only prove the packed tarball resolves, + * imports, and runs, where the dependency tree is a means rather than the + * subject. `--prefer-offline` serves cached registry metadata without + * revalidating it: the tarball under test is always read from disk and + * uncached dependencies are still fetched, so only the staleness round-trips + * are skipped. + */ +export const cachedNpmInstallArguments = [...npmInstallArguments, '--prefer-offline'] as const; + const packs = new Map>(); let fallbackBuild: Promise | undefined; diff --git a/packages/create-agent-bundle/tests/support/scaffold-fixture.ts b/packages/create-agent-bundle/tests/support/scaffold-fixture.ts index e321895a1..0ebcce5a8 100644 --- a/packages/create-agent-bundle/tests/support/scaffold-fixture.ts +++ b/packages/create-agent-bundle/tests/support/scaffold-fixture.ts @@ -6,7 +6,7 @@ import { promisify } from 'node:util'; import { expect } from '@rstest/core'; -import { installedEnvironment, npmInstallArguments, sharedPackedTarball } from '../../../agent-bundle/tests/support/shared-pack.ts'; +import { cachedNpmInstallArguments, installedEnvironment, sharedPackedTarball } from '../../../agent-bundle/tests/support/shared-pack.ts'; const execFile = promisify(executeFile); @@ -36,7 +36,7 @@ const packFixture = async (): Promise => { const runnerRoot = join(root, 'runner'); await mkdir(runnerRoot, { recursive: true }); await writeFile(join(runnerRoot, 'package.json'), '{"name":"scaffold-runner","type":"module","private":true}\n'); - await execFile('npm', ['install', ...npmInstallArguments, scaffolderTarball], { + await execFile('npm', ['install', ...cachedNpmInstallArguments, scaffolderTarball], { cwd: runnerRoot, env: installedEnvironment(), }); diff --git a/packages/workbench/tests/packed-release.e2e.test.ts b/packages/workbench/tests/packed-release.e2e.test.ts index dfe352f39..83e0c8840 100644 --- a/packages/workbench/tests/packed-release.e2e.test.ts +++ b/packages/workbench/tests/packed-release.e2e.test.ts @@ -14,7 +14,7 @@ import { validateOutageLedger, type ConsoleErrorRecord, } from './support/packed-outage-ledger.ts'; -import { npmInstallArguments, sharedPackedTarball } from '../../agent-bundle/tests/support/shared-pack.ts'; +import { cachedNpmInstallArguments, sharedPackedTarball } from '../../agent-bundle/tests/support/shared-pack.ts'; import { timeScale } from '../../agent-bundle/tests/support/time-scale.ts'; import { availablePort, @@ -83,7 +83,7 @@ e2e('runs every Agent API tool from the installed tarball', { timeout: 360_000 * let primaryFailure: Error | undefined; try { await writeFile(join(consumer, 'package.json'), '{"type":"module"}\n'); - await execFile('npm', ['install', '--omit=dev', ...npmInstallArguments, tarball], { + await execFile('npm', ['install', '--omit=dev', ...cachedNpmInstallArguments, tarball], { cwd: consumer, env: installedEnvironment(), }); diff --git a/scripts/audit-packed-release.mjs b/scripts/audit-packed-release.mjs index 744bd8eeb..4ae1ea5a9 100644 --- a/scripts/audit-packed-release.mjs +++ b/scripts/audit-packed-release.mjs @@ -157,6 +157,11 @@ const auditPackedRelease = async () => { '--pack-destination', tarballs, ], { cwd: packageRoot, env: productionEnvironment })).stdout); const tarball = join(tarballs, asString(filename, 'npm pack did not produce a tarball filename')); + // No `--prefer-offline` here (unlike the packed pool's + // cachedNpmInstallArguments): the tree this install produces is what + // `npm audit`, `npm audit signatures`, and `npm sbom` below report on, so + // it has to resolve against live registry metadata rather than whatever + // the cache last saw. await execNpm([ 'install', '--omit=dev',