From 81f942b15dc98abaab02cf199bf5b371537fffc5 Mon Sep 17 00:00:00 2001 From: quality Date: Fri, 2 Oct 2026 04:27:31 -0400 Subject: [PATCH] test(bundle): assert dist/index.js requires only node built-ins and inlines every package src/ imports action.yml runs dist/index.js on a runner without node_modules, so every package has to be inlined by ncc. bundle.test.ts only checks node --check and the webpackMissingModule marker; a module left external emits a literal require() that passes both, and runBundle.ts spawns from the repository root where node_modules resolves it anyway. Signed-off-by: quality --- __tests__/bundle/selfContained.test.ts | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 __tests__/bundle/selfContained.test.ts diff --git a/__tests__/bundle/selfContained.test.ts b/__tests__/bundle/selfContained.test.ts new file mode 100644 index 0000000..3608073 --- /dev/null +++ b/__tests__/bundle/selfContained.test.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs' +import { isBuiltin } from 'node:module' +import { describe, expect, it } from 'vitest' + +import { bundlePath } from './runBundle' + +// action.yml runs dist/index.js on a runner that never installs node_modules, so every package the +// action imports has to be inlined by ncc. The bundle suite cannot catch a leak: it spawns the bundle +// from the repository root, where node_modules resolves the import anyway. +describe('dist/index.js is self-contained', () => { + const source = fs.readFileSync(bundlePath, 'utf8') + const specifiers = [...source.matchAll(/\brequire\((["'])([^"']+)\1\)/g)].map(match => match[2]) + + it('requires only node built-in modules', () => { + expect(specifiers.length).toBeGreaterThan(0) + expect(specifiers.filter(specifier => !isBuiltin(specifier))).toEqual([]) + }) + + it('inlines every package src/ imports', () => { + const packages = new Set() + for (const file of fs.readdirSync('src', { recursive: true, encoding: 'utf8' }).filter(name => name.endsWith('.ts'))) { + for (const match of fs.readFileSync(`src/${file}`, 'utf8').matchAll(/^import .* from '([^'.][^']*)'/gm)) { + if (!isBuiltin(match[1])) { + packages.add(match[1]) + } + } + } + expect([...packages]).toEqual(expect.arrayContaining(['@actions/core', '@actions/github', 'js-yaml'])) + for (const name of packages) { + expect(source, name).toContain(`node_modules/${name}/`) + } + }) +})