feat: close application architecture gaps
This commit is contained in:
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { runBuild } from "./build.ts";
|
||||
import { runTypecheck } from "./types.ts";
|
||||
import { assertReachability } from "./reachability.ts";
|
||||
|
||||
async function runScript(root: string, name: string): Promise<void> {
|
||||
const child = Bun.spawn(["bun", "run", name], {
|
||||
@@ -20,6 +21,7 @@ export async function runCheck(appRoot: string): Promise<void> {
|
||||
const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")) as {
|
||||
scripts?: Record<string, string>;
|
||||
};
|
||||
assertReachability(root);
|
||||
await runBuild(root);
|
||||
if (manifest.scripts?.typecheck) await runScript(root, "typecheck");
|
||||
else if (!(await runTypecheck(root))) throw new Error("WRN-CHECK: application typecheck failed");
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { dirname, extname, join, relative, resolve } from "node:path";
|
||||
|
||||
export interface ReachabilityIssue {
|
||||
code: "WRN-REACH-MIDDLEWARE" | "WRN-REACH-QUEUE" | "WRN-REACH-WORKER";
|
||||
file: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
function sourceFiles(directory: string): string[] {
|
||||
if (!existsSync(directory)) return [];
|
||||
const output: string[] = [];
|
||||
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
||||
const path = join(directory, entry.name);
|
||||
if (entry.isDirectory()) output.push(...sourceFiles(path));
|
||||
else if ([".ts", ".js", ".mts", ".mjs"].includes(extname(entry.name))) output.push(path);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/** Static registration audit for convention-discovered application modules. */
|
||||
export function checkReachability(appRoot: string): ReachabilityIssue[] {
|
||||
const root = resolve(appRoot);
|
||||
const app = join(root, "app");
|
||||
const all = sourceFiles(app).map((file) => ({ file, source: readFileSync(file, "utf8") }));
|
||||
const issues: ReachabilityIssue[] = [];
|
||||
|
||||
for (const entry of all) {
|
||||
const path = relative(root, entry.file).replace(/\\/g, "/");
|
||||
if (path.startsWith("app/middleware/") && !/\bexport\s+default\b/.test(entry.source)) {
|
||||
issues.push({
|
||||
code: "WRN-REACH-MIDDLEWARE",
|
||||
file: path,
|
||||
message: "Middleware discovery requires a default export.",
|
||||
});
|
||||
}
|
||||
if (
|
||||
path.startsWith("app/queues/") &&
|
||||
/\bdefineQueue\s*\(/.test(entry.source) &&
|
||||
!/\bexport\s+default\b/.test(entry.source)
|
||||
) {
|
||||
issues.push({
|
||||
code: "WRN-REACH-QUEUE",
|
||||
file: path,
|
||||
message: "Queue discovery requires the defined queue to be exported as default.",
|
||||
});
|
||||
}
|
||||
if (
|
||||
/\bdefineWorker\s*(?:<[^>]+>)?\s*\(/.test(entry.source) &&
|
||||
!/\brunWorker\s*\(/.test(entry.source)
|
||||
) {
|
||||
const stem = entry.file.replace(/\.[^.]+$/, "");
|
||||
const imported = all.some(
|
||||
(candidate) =>
|
||||
candidate.file !== entry.file &&
|
||||
candidate.source
|
||||
.replace(/\\/g, "/")
|
||||
.includes(relative(dirname(candidate.file), stem).replace(/\\/g, "/")),
|
||||
);
|
||||
if (!imported) {
|
||||
issues.push({
|
||||
code: "WRN-REACH-WORKER",
|
||||
file: path,
|
||||
message: "Worker is defined but no application module imports it or calls runWorker().",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function assertReachability(appRoot: string): void {
|
||||
const issues = checkReachability(appRoot);
|
||||
if (!issues.length) return;
|
||||
throw new Error(
|
||||
`WRN-REACHABILITY: unreachable application exports\n${issues
|
||||
.map((issue) => `- ${issue.code} ${issue.file}: ${issue.message}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user