Files
suaveplan/packages/api-stack/api/api-openapi/tests/dep-graph.test.ts
2026-05-27 23:29:53 +01:00

127 lines
3.8 KiB
TypeScript

import { readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
const PKG_ROOT = new URL("..", import.meta.url).pathname;
const PKG_JSON = JSON.parse(
readFileSync(join(PKG_ROOT, "package.json"), "utf8")
) as {
dependencies: Record<string, string>;
exports: Record<string, unknown>;
};
const RUNTIME_ALLOWLIST = new Set([
"@hono/zod-openapi",
"@opentelemetry/api",
"@suaveplan/api-plugin",
"@suaveplan/api-schemas",
"@suaveplan/error",
"@suaveplan/types",
"hono",
"tslib",
"yaml",
"zod",
]);
const SRC_ROOT = join(PKG_ROOT, "src");
function walk(dir: string): string[] {
const out: string[] = [];
for (const entry of readdirSync(dir)) {
const p = join(dir, entry);
const st = statSync(p);
if (st.isDirectory()) out.push(...walk(p));
else out.push(p);
}
return out;
}
const FEATURE_MODULES = [
"error-codes",
"types",
"registry",
"route-factory",
"schema-registry",
"merger",
"plugin-integration",
"emitter",
"namespacing",
"strictness",
"endpoints",
];
describe("dep-graph", () => {
it("dependencies match the allowlist", { timeout: 5000 }, () => {
for (const dep of Object.keys(PKG_JSON.dependencies)) {
expect(RUNTIME_ALLOWLIST.has(dep)).toBe(true);
}
});
it("subpath exports cover every feature module", { timeout: 5000 }, () => {
const exports = PKG_JSON.exports;
for (const m of FEATURE_MODULES) {
expect(exports[`./${m}`]).toBeDefined();
}
expect(exports["."]).toBeDefined();
});
it("every implementation .ts has a sibling .md", { timeout: 5000 }, () => {
const files = walk(SRC_ROOT).filter(
(f) =>
f.endsWith(".ts") &&
!f.endsWith(".test.ts") &&
!f.endsWith(".types.ts") &&
!f.endsWith(".bench.ts") &&
!f.endsWith(".spec-conformance.test.ts") &&
!/[/\\]index\.ts$/.test(f) &&
!/[/\\]src[/\\]index\.ts$/.test(f)
);
for (const f of files) {
const md = f.replace(/\.ts$/, ".md");
const has = (() => {
try {
statSync(md);
return true;
} catch {
return false;
}
})();
expect({ file: f, hasMd: has }).toEqual({
file: f,
hasMd: true,
});
}
});
it("no biome-ignore suppressions in src", { timeout: 5000 }, () => {
const files = walk(SRC_ROOT).filter((f) => f.endsWith(".ts"));
for (const f of files) {
const text = readFileSync(f, "utf8");
expect(text.includes("biome-ignore")).toBe(false);
}
});
it("no direct vitest/bun:test imports", { timeout: 5000 }, () => {
const files = walk(SRC_ROOT)
.concat(walk(join(PKG_ROOT, "tests")))
.filter((f) => f.endsWith(".ts"));
const banned =
/from\s+['"](vitest|bun:test)['"]|require\(\s*['"](vitest|bun:test)['"]\s*\)/;
for (const f of files) {
const text = readFileSync(f, "utf8");
if (banned.test(text)) {
throw new Error(`${f} imports vitest or bun:test directly`);
}
}
expect(true).toBe(true);
});
it("no `export *` in feature barrels", { timeout: 5000 }, () => {
for (const m of FEATURE_MODULES) {
const idx = join(SRC_ROOT, m, "index.ts");
const text = readFileSync(idx, "utf8");
expect(text.includes("export *")).toBe(false);
}
});
});