156 lines
5.4 KiB
TypeScript
156 lines
5.4 KiB
TypeScript
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
|
|
const PKG = resolve(__dirname, "..");
|
|
const SRC = resolve(PKG, "src");
|
|
|
|
interface PackageJson {
|
|
readonly exports: Record<string, unknown>;
|
|
readonly dependencies?: Record<string, string>;
|
|
readonly peerDependencies?: Record<string, string>;
|
|
readonly devDependencies?: Record<string, string>;
|
|
}
|
|
|
|
const pkg = JSON.parse(
|
|
readFileSync(resolve(PKG, "package.json"), "utf8")
|
|
) as PackageJson;
|
|
|
|
const EXEMPT_NO_MD = new Set(["index.ts"]);
|
|
|
|
function walk(dir: string): string[] {
|
|
const out: string[] = [];
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
const s = statSync(full);
|
|
if (s.isDirectory()) out.push(...walk(full));
|
|
else out.push(full);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
describe("dep-graph", () => {
|
|
it("every subpath export points to a real file (post-build) and has a matching source", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const subpaths = Object.keys(pkg.exports).filter(
|
|
(k) => k !== "./package.json"
|
|
);
|
|
for (const subpath of subpaths) {
|
|
const target = (
|
|
pkg.exports[subpath] as {
|
|
import?: { default: string };
|
|
}
|
|
)?.import?.default;
|
|
expect(target, `${subpath} has import.default`).toBeDefined();
|
|
}
|
|
expect(subpaths.length).toBeGreaterThanOrEqual(12);
|
|
});
|
|
|
|
it("every impl .ts has a sibling .md", { timeout: 5000 }, () => {
|
|
const files = walk(SRC);
|
|
const tsFiles = files.filter(
|
|
(f) =>
|
|
f.endsWith(".ts") &&
|
|
!f.endsWith(".test.ts") &&
|
|
!f.endsWith(".bench.ts") &&
|
|
!f.endsWith(".types.ts") &&
|
|
!f.endsWith("/index.ts") &&
|
|
!f.endsWith(".spec-conformance.test.ts")
|
|
);
|
|
for (const file of tsFiles) {
|
|
const base = file.slice(0, -3);
|
|
const md = `${base}.md`;
|
|
if (EXEMPT_NO_MD.has(file.split("/").pop() ?? "")) continue;
|
|
expect(existsSync(md), `${file} missing sibling .md`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("dependencies match the allowlist", { timeout: 5000 }, () => {
|
|
const allowed = new Set([
|
|
"@opentelemetry/api",
|
|
"@suaveplan/error",
|
|
"@suaveplan/surrealdb",
|
|
"@suaveplan/types",
|
|
"surrealdb",
|
|
"tslib",
|
|
"zod",
|
|
]);
|
|
for (const dep of Object.keys(pkg.dependencies ?? {})) {
|
|
expect(allowed.has(dep), `unexpected runtime dep: ${dep}`).toBe(
|
|
true
|
|
);
|
|
}
|
|
expect(Object.keys(pkg.peerDependencies ?? {})).toEqual([
|
|
"better-auth",
|
|
]);
|
|
});
|
|
|
|
it("no source file exceeds 300 lines", { timeout: 5000 }, () => {
|
|
const files = walk(SRC).filter(
|
|
(f) =>
|
|
f.endsWith(".ts") &&
|
|
!f.endsWith(".test.ts") &&
|
|
!f.endsWith(".bench.ts")
|
|
);
|
|
for (const file of files) {
|
|
const lines = readFileSync(file, "utf8").split("\n").length;
|
|
expect(
|
|
lines,
|
|
`${file} has ${lines} lines (>300)`
|
|
).toBeLessThanOrEqual(300);
|
|
}
|
|
});
|
|
|
|
it("no source file uses export *", { timeout: 5000 }, () => {
|
|
const files = walk(SRC).filter((f) => f.endsWith(".ts"));
|
|
for (const file of files) {
|
|
const content = readFileSync(file, "utf8");
|
|
expect(
|
|
/\bexport \* from\b/.test(content),
|
|
`${file} uses export *`
|
|
).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("no source file imports from vitest or bun:test", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const files = walk(SRC).filter(
|
|
(f) => f.endsWith(".ts") || f.endsWith(".test.ts")
|
|
);
|
|
for (const file of files) {
|
|
const content = readFileSync(file, "utf8");
|
|
expect(
|
|
/from\s+["'](vitest|bun:test)["']/.test(content),
|
|
`${file} imports from vitest/bun:test directly`
|
|
).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("README quick-start uses betterAuthSurrealAdapter v1.6 factory", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const readme = readFileSync(resolve(PKG, "README.md"), "utf8");
|
|
const quickStartIdx = readme.indexOf("## Quick start");
|
|
expect(quickStartIdx).toBeGreaterThan(-1);
|
|
// First non-install code block after `## Quick start` should use
|
|
// `betterAuthSurrealAdapter`.
|
|
const after = readme.slice(quickStartIdx);
|
|
const firstFence = after.indexOf("```ts");
|
|
const firstBlockEnd = after.indexOf("```", firstFence + 5);
|
|
const firstBlock = after.slice(firstFence, firstBlockEnd);
|
|
expect(firstBlock).toContain("betterAuthSurrealAdapter");
|
|
expect(firstBlock).toContain("/adapter/v1.6");
|
|
});
|
|
|
|
it("./adapter/v1.6 subpath export resolves", { timeout: 5000 }, () => {
|
|
const subpath = pkg.exports["./adapter/v1.6"] as
|
|
| { import?: { default: string; types: string } }
|
|
| undefined;
|
|
expect(subpath).toBeDefined();
|
|
expect(subpath?.import?.default).toBe("./dist/adapter/v1.6.js");
|
|
expect(subpath?.import?.types).toBe("./dist/adapter/v1.6.d.ts");
|
|
});
|
|
});
|