81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
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")}`,
|
|
);
|
|
}
|