Files
2026-05-27 23:29:53 +01:00

113 lines
4.2 KiB
TypeScript

import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import { describe, expect, it } from "@suaveplan/testing/runner";
const pkgRoot = resolve(__dirname, "..");
const srcRoot = join(pkgRoot, "src");
function walk(dir: string): string[] {
const out: string[] = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
const st = statSync(full);
if (st.isDirectory()) out.push(...walk(full));
else out.push(full);
}
return out;
}
describe("@suaveplan/api-plugin-vault dep graph + layout", () => {
it("package.json carries required dependencies", { timeout: 5000 }, () => {
const pkg = JSON.parse(
readFileSync(join(pkgRoot, "package.json"), "utf8")
) as {
dependencies: Record<string, string>;
};
const deps = pkg.dependencies;
expect(deps["@suaveplan/api-plugin"]).toBe("workspace:^");
expect(deps["@suaveplan/api-plugin-auth"]).toBe("workspace:^");
expect(deps["@suaveplan/api-openapi"]).toBe("workspace:^");
expect(deps["@suaveplan/api-migrations"]).toBe("workspace:^");
expect(deps["@suaveplan/vault"]).toBe("workspace:^");
expect(deps["@suaveplan/crypto-vault"]).toBe("workspace:^");
expect(deps["@suaveplan/error"]).toBe("workspace:^");
expect(deps["@suaveplan/surrealdb"]).toBe("workspace:^");
expect(deps["@suaveplan/types"]).toBe("workspace:^");
expect(deps["hono"]).toBe("catalog:");
expect(deps["@hono/zod-openapi"]).toBe("catalog:server");
expect(deps["zod"]).toBe("catalog:forms");
expect(deps["@opentelemetry/api"]).toBe("catalog:observability");
});
it("package.json exposes the 10 documented subpath exports", {
timeout: 5000,
}, () => {
const pkg = JSON.parse(
readFileSync(join(pkgRoot, "package.json"), "utf8")
) as { exports: Record<string, unknown> };
const subs = Object.keys(pkg.exports).filter(
(k) => k.startsWith("./") && k !== "./package.json"
);
// 9 named feature subpaths (root `.` and `./package.json` are
// filtered separately).
expect(subs.length).toBe(9);
});
it("every subpath export resolves to a file within src/", {
timeout: 5000,
}, () => {
const pkg = JSON.parse(
readFileSync(join(pkgRoot, "package.json"), "utf8")
) as {
exports: Record<string, { import?: { default?: string } } | string>;
};
const missing: string[] = [];
for (const [sub, val] of Object.entries(pkg.exports)) {
if (sub === "./package.json") continue;
const defaultPath =
typeof val === "string"
? val
: (val?.import?.default ?? undefined);
if (defaultPath === undefined) continue;
const rel = defaultPath
.replace(/^\.\/dist\//, "")
.replace(/\.js$/, ".ts");
const srcPath = join(srcRoot, rel);
try {
statSync(srcPath);
} catch {
missing.push(`${sub} → ${srcPath}`);
}
}
expect(missing).toEqual([]);
});
it("every impl .ts has a sibling .md", { timeout: 5000 }, () => {
const missing: string[] = [];
for (const file of walk(srcRoot)) {
if (!file.endsWith(".ts")) continue;
if (
file.endsWith(".test.ts") ||
file.endsWith(".spec.ts") ||
file.endsWith(".types.ts") ||
file.endsWith("/index.ts")
) {
continue;
}
const md = file.replace(/\.ts$/, ".md");
try {
statSync(md);
} catch {
missing.push(md);
}
}
expect(missing).toEqual([]);
});
it("README.md is present at package root", { timeout: 5000 }, () => {
const readme = readFileSync(join(pkgRoot, "README.md"), "utf8");
expect(readme.length).toBeGreaterThan(200);
expect(readme).toContain("@suaveplan/api-plugin-vault");
});
});