From b3ef19c0c07ca2afeb8c3bc000699c3b404a7e85 Mon Sep 17 00:00:00 2001 From: reggi Date: Fri, 10 Jul 2026 14:38:59 -0400 Subject: [PATCH] test(smoke): assert bundled production deps resolve in the packed npm Adds a smoke test that packs npm, installs it globally into an isolated testdir, then walks the installed bundle and require.resolve()s every non-optional production dependency from its owning package. This catches deps that get dropped from the hoisted bundle at pack time (e.g. a dev-only version shadowing the real production copy), which unit tests miss because they run against the source tree where a nested copy still resolves. See #9722/#9740 where sigstore went missing from the packed bundle and shipped a broken `npm publish --provenance`. Runs against the packed/installed bundle (not the dev tree) to avoid dev-only @types/peer noise, and skips optionalDependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 716952b1-3f66-4a3e-a48b-ca9cff285466 --- smoke-tests/test/bundled-deps-resolve.js | 95 ++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 smoke-tests/test/bundled-deps-resolve.js diff --git a/smoke-tests/test/bundled-deps-resolve.js b/smoke-tests/test/bundled-deps-resolve.js new file mode 100644 index 0000000000000..145a40753a253 --- /dev/null +++ b/smoke-tests/test/bundled-deps-resolve.js @@ -0,0 +1,95 @@ +const t = require('tap') +const { join } = require('node:path') +const fs = require('node:fs') +const { createRequire } = require('node:module') +const setup = require('./fixtures/setup.js') + +// Walk an installed package tree and assert that every non-optional +// production dependency of every bundled package resolves from that +// package's own directory. This catches deps that were dropped from the +// hoisted bundle at pack time (e.g. a dev-only copy shadowing the real +// production version), which unit tests miss because they run against the +// source tree where a nested copy still resolves. See PR #9740 / #9722 +// where `sigstore` went missing from the packed bundle. +const findMissingDeps = (npmRoot) => { + const req = createRequire(join(npmRoot, 'package.json')) + const missing = [] + const seen = new Set() + const stack = [npmRoot] + + while (stack.length) { + const dir = stack.pop() + if (seen.has(dir)) { + continue + } + seen.add(dir) + + let pkg + try { + pkg = JSON.parse(fs.readFileSync(join(dir, 'package.json'), 'utf8')) + } catch { + continue + } + + const optional = new Set(Object.keys(pkg.optionalDependencies || {})) + for (const dep of Object.keys(pkg.dependencies || {})) { + if (optional.has(dep)) { + continue + } + try { + req.resolve(dep, { paths: [dir] }) + } catch { + missing.push(`${pkg.name || dir} -> ${dep}`) + } + } + + const nodeModules = join(dir, 'node_modules') + let entries + try { + entries = fs.readdirSync(nodeModules, { withFileTypes: true }) + } catch { + continue + } + for (const entry of entries) { + if (entry.name === '.bin') { + continue + } + const entryPath = join(nodeModules, entry.name) + if (entry.name.startsWith('@')) { + for (const scoped of fs.readdirSync(entryPath, { withFileTypes: true })) { + stack.push(join(entryPath, scoped.name)) + } + } else { + stack.push(entryPath) + } + } + } + + return missing.sort() +} + +t.test('bundled production deps all resolve from the packed bundle', async t => { + const { + npm, + npmLocalTarball, + paths: { globalNodeModules }, + } = await setup(t, { + testdir: { + project: { + 'package.json': { name: 'npm', version: '999.999.999' }, + }, + }, + }) + + const tarball = await npmLocalTarball() + await npm('install', tarball, '--global') + + const npmRoot = join(globalNodeModules, 'npm') + const missing = findMissingDeps(npmRoot) + + t.strictSame( + missing, + [], + 'every non-optional production dependency resolves in the packed bundle' + ) +})