113 lines
4.2 KiB
TypeScript
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");
|
|
});
|
|
});
|