mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test(cli): catch undeclared runtime dependencies on clean installs
This commit is contained in:
1 parent
4a4fccfa76
commit
6a1d7bde9a
2 files changed
+75
-1
No files matched your search
@@ -49,7 +49,8 @@ COPY ${OPENRIG_TARBALL} /tmp/openrig.tgz
|
||||
RUN set -eux; \
|
||||
npm install -g /tmp/openrig.tgz; \
|
||||
rm -f /tmp/openrig.tgz; \
|
||||
rig --version
|
||||
rig --version; \
|
||||
rig --help
|
||||
|
||||
# --- layer 4: stub runtime assets (zero-token by construction; staged by the build verb into the
|
||||
# build context as ./stub-assets/, hashed into the manifest via deriveStubAssetsHash) ---
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { isBuiltin } from "node:module";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
import ts from "typescript";
|
||||
|
||||
const root = fileURLToPath(new URL("../", import.meta.url));
|
||||
|
||||
function runtimeImports(text, file) {
|
||||
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
|
||||
const imports = [];
|
||||
const visit = (node) => {
|
||||
let literal;
|
||||
if (ts.isImportDeclaration(node)) {
|
||||
const clause = node.importClause;
|
||||
if (clause?.isTypeOnly) return;
|
||||
const bindings = clause?.namedBindings;
|
||||
if (!clause?.name && bindings && ts.isNamedImports(bindings)
|
||||
&& bindings.elements.length && bindings.elements.every((e) => e.isTypeOnly)) return;
|
||||
literal = node.moduleSpecifier;
|
||||
} else if (ts.isExportDeclaration(node) && !node.isTypeOnly) {
|
||||
if (node.exportClause && ts.isNamedExports(node.exportClause)
|
||||
&& node.exportClause.elements.every((e) => e.isTypeOnly)) return;
|
||||
literal = node.moduleSpecifier;
|
||||
} else if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
|
||||
|| (ts.isIdentifier(node.expression) && node.expression.text === "require"))) {
|
||||
literal = node.arguments[0];
|
||||
}
|
||||
if (literal && ts.isStringLiteralLike(literal)) imports.push(literal.text);
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(source);
|
||||
return imports;
|
||||
}
|
||||
|
||||
test("runtime import audit includes lazy loads and re-exports, excludes types and prose", () => {
|
||||
assert.deepEqual(runtimeImports(`
|
||||
import type { A } from "types-only";
|
||||
import { type B } from "inline-types-only";
|
||||
export type { C } from "exported-types";
|
||||
const prose = 'import("not-an-import")';
|
||||
import "side-effect";
|
||||
export { value } from "re-export";
|
||||
const lazy = () => import("lazy");
|
||||
const cjs = require("required");
|
||||
`, "control.ts"), ["side-effect", "re-export", "lazy", "required"]);
|
||||
});
|
||||
|
||||
test("the CLI entry points declare every reachable external runtime dependency", () => {
|
||||
const manifest = JSON.parse(readFileSync(resolve(root, "packages/cli/package.json"), "utf8"));
|
||||
const pending = ["bin-wrapper.ts", "index.ts"].map((f) => resolve(root, "packages/cli/src", f));
|
||||
const visited = new Set();
|
||||
const missing = new Set();
|
||||
while (pending.length) {
|
||||
const file = pending.pop();
|
||||
if (visited.has(file)) continue;
|
||||
visited.add(file);
|
||||
for (const specifier of runtimeImports(readFileSync(file, "utf8"), file)) {
|
||||
if (specifier.startsWith(".")) {
|
||||
pending.push(resolve(dirname(file), specifier.replace(/\.js$/, ".ts")));
|
||||
continue;
|
||||
}
|
||||
if (isBuiltin(specifier)) continue;
|
||||
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
||||
// build-package rewrites these to the daemon files shipped inside the CLI.
|
||||
if (name === "@openrig/daemon") continue;
|
||||
if (!Object.hasOwn(manifest.dependencies, name)) missing.add(`${name} (${file.slice(root.length)})`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual([...missing].sort(), [], "Runtime imports must survive a production-only install");
|
||||
});
|
||||
Reference in new issue
Block a user