import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { dirname, join, posix, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import test from "node:test"; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const packageJson = JSON.parse(readFileSync(join(repositoryRoot, "package.json"), "utf8")) as { dependencies: Record; pi: { extensions: string[] }; }; const packOutput = execFileSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], { cwd: repositoryRoot, encoding: "utf8", }); const packResult = JSON.parse(packOutput.slice(packOutput.indexOf("["))) as Array<{ files: Array<{ path: string }>; }>; const packedFiles = new Set(packResult[0].files.map((entry) => entry.path)); test("the packed bundle ships no nested node_modules or build output", () => { assert.ok(packedFiles.size > 0, "npm pack --dry-run must report a non-empty file list"); for (const filePath of packedFiles) { const segments = filePath.split("/"); assert.equal(segments.includes("node_modules"), false, `${filePath} must not ship a nested node_modules tree`); assert.equal(segments.includes("dist"), false, `${filePath} must not ship build output`); } }); test("every declared extension entry and file: dependency manifest is inside the packed bundle", () => { for (const entry of packageJson.pi.extensions) { const bundled = entry.replace(/^\.\//u, ""); assert.ok(packedFiles.has(bundled), `extension entry ${entry} must be inside the packed bundle`); } for (const [name, specifier] of Object.entries(packageJson.dependencies)) { const match = /^file:\.\/(.+)$/u.exec(specifier); if (!match) continue; const manifest = `${match[1]}/package.json`; assert.ok(packedFiles.has(manifest), `${name} is a file: dependency — ${manifest} must be packed`); } }); test("every file: dependency ships an UPSTREAM.md source record", () => { for (const [name, specifier] of Object.entries(packageJson.dependencies)) { const match = /^file:\.\/(.+)$/u.exec(specifier); if (!match) continue; const upstream = `${match[1]}/UPSTREAM.md`; assert.ok( readFileSync(join(repositoryRoot, upstream), "utf8").trim().length > 0, `${name} is a file: dependency — ${upstream} must exist and be non-empty`, ); assert.ok(packedFiles.has(upstream), `${name} is a file: dependency — ${upstream} must be packed`); } }); test("relative import specifiers inside packed sources resolve to packed files", () => { const specifierPatterns = [ /\bfrom\s+["'](\.{1,2}\/[^"']+)["']/gu, /(? { if (packedFiles.has(resolved)) return true; const candidates = [`${resolved}.ts`, `${resolved}.js`, `${resolved}.mjs`, `${resolved}/index.ts`]; if (candidates.some((candidate) => packedFiles.has(candidate))) return true; // TypeScript sources import sibling ".js"/".mjs" specifiers that map to ".ts" files. const swapped = resolved.replace(/\.(js|mjs|cjs)$/u, ".ts"); if (swapped !== resolved && packedFiles.has(swapped)) return true; // new URL("../config/prompt/", import.meta.url) style directory references. for (const filePath of packedFiles) { if (filePath.startsWith(`${resolved}/`)) return true; } return false; }; const unresolved: string[] = []; for (const filePath of packedFiles) { if (!/\.(ts|js|mjs)$/u.test(filePath)) continue; const segments = filePath.split("/"); if (/\.test\.(ts|js|mjs)$/u.test(filePath) || segments.includes("test") || segments.includes("tests")) continue; const source = readFileSync(join(repositoryRoot, filePath), "utf8"); for (const pattern of specifierPatterns) { pattern.lastIndex = 0; for (const match of source.matchAll(pattern)) { const resolved = posix.normalize(posix.join(posix.dirname(filePath), match[1])).replace(/\/$/u, ""); if (resolved === ".." || resolved.startsWith("../")) continue; if (resolved.split("/").includes("node_modules")) continue; if (!isPacked(resolved)) unresolved.push(`${filePath} -> ${match[1]}`); } } } assert.deepEqual(unresolved, [], "packed sources reference files missing from the bundle"); }); test("prompt config and lifecycle scripts are inside the packed bundle", () => { const tracked = execFileSync("git", ["ls-files", "config/prompt"], { cwd: repositoryRoot, encoding: "utf8", }) .split("\n") .filter(Boolean); for (const filePath of [...tracked, "install.sh", "update.sh", "uninstall.sh", "configure.sh", "scripts/lib.sh"]) { assert.ok(packedFiles.has(filePath), `${filePath} must be inside the packed bundle`); } });