feat: close application architecture gaps
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.55",
|
||||
"version": "0.8.57",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -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")}`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { checkReachability } from "../src/reachability.ts";
|
||||
|
||||
test("reports convention modules and workers that cannot be reached", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrn-reach-"));
|
||||
mkdirSync(join(root, "app", "middleware"), { recursive: true });
|
||||
mkdirSync(join(root, "app", "queues"), { recursive: true });
|
||||
writeFileSync(join(root, "app", "middleware", "audit.ts"), "export const audit = () => {};\n");
|
||||
writeFileSync(
|
||||
join(root, "app", "queues", "mail.ts"),
|
||||
"export const mail = defineQueue({}); export const worker = defineWorker({});\n",
|
||||
);
|
||||
|
||||
expect(
|
||||
checkReachability(root)
|
||||
.map((issue) => issue.code)
|
||||
.sort(),
|
||||
).toEqual(["WRN-REACH-MIDDLEWARE", "WRN-REACH-QUEUE", "WRN-REACH-WORKER"]);
|
||||
});
|
||||
|
||||
test("accepts discovered middleware and queues", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrn-reach-"));
|
||||
mkdirSync(join(root, "app", "middleware"), { recursive: true });
|
||||
mkdirSync(join(root, "app", "queues"), { recursive: true });
|
||||
writeFileSync(join(root, "app", "middleware", "audit.ts"), "export default () => {};\n");
|
||||
writeFileSync(join(root, "app", "queues", "mail.ts"), "export default defineQueue({});\n");
|
||||
expect(checkReachability(root)).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user