/** * `wrnexus generate ` — scaffold a page, component, API route, or * schema from a template. Keeps new files consistent and gets users moving fast. * * wrnexus generate page about * wrnexus generate component user-card * wrnexus generate api users/list * wrnexus generate schema signup */ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; export type GenerateType = "page" | "component" | "api" | "schema"; const ALIASES: Record = { page: "page", p: "page", component: "component", c: "component", api: "api", a: "api", schema: "schema", s: "schema", }; export interface GeneratedFile { /** Path relative to the `app/` directory. */ path: string; content: string; } export function scaffoldResource(name: string): GeneratedFile[] { const clean = name.replace(/[^A-Za-z0-9_-]/g, "").toLowerCase(); if (!clean) throw new TypeError("resource name is required"); const singular = clean.replace(/s$/, ""); const pascal = toPascalCase(singular); return [ { path: `schemas/${singular}.ts`, content: `import { v } from "@wrnexus/validation";\n\nexport default v.object({\n name: v.string().min(1, "Required"),\n});\n`, }, { path: `resources/${clean}.ts`, content: `import { getDb } from "@wrnexus/db";\nimport { defineResource } from "@wrnexus/helpers";\n\nexport default defineResource({\n name: "${clean}",\n db: getDb,\n table: "${clean}",\n owner: "owner_id",\n fields: ["name"],\n permissions: { read: "${singular}:read", write: "${singular}:write" },\n});\n`, }, { path: `api/${clean}.ts`, content: `import resource from "../resources/${clean}.ts";\nexport const GET = resource.list;\nexport const POST = resource.create;\n`, }, { path: `api/${clean}/[id].ts`, content: `import resource from "../../resources/${clean}.ts";\nexport const GET = resource.get;\nexport const PUT = resource.update;\nexport const DELETE = resource.remove;\n`, }, { path: `pages/${clean}.wrn`, content: `page ${pascal}List {\n view {\n

${pascal}

\n

Generated resource page. Connect it to /api/${clean}.

\n }\n}\n`, }, ]; } export function runGenerateResource(appRoot: string, name: string | undefined): void { if (!name) throw new TypeError("Usage: wrnexus generate resource "); for (const file of scaffoldResource(name)) { const target = join(resolve(appRoot), "app", file.path); if (existsSync(target)) continue; mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, file.content, "utf8"); console.log(`✓ Generated app/${file.path}`); } } function toPascalCase(name: string): string { return name .split(/[^A-Za-z0-9]+/) .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(""); } function baseName(name: string): string { const parts = name.split("/"); return parts[parts.length - 1] ?? name; } /** Produce the file (relative path + content) for a generate request. */ export function scaffold(type: GenerateType, name: string): GeneratedFile { const clean = name.replace(/\.(wrn|ts)$/, "").replace(/^\/+|\/+$/g, ""); const pascal = toPascalCase(baseName(clean)); switch (type) { case "page": return { path: `pages/${clean}.wrn`, content: `page ${pascal} { layout = "public" seo { title = "${pascal}" } view {

${pascal}

Edit app/pages/${clean}.wrn to build this page.

} } `, }; case "component": return { path: `components/${clean}.wrn`, content: `component ${pascal} { props { label = "${pascal}" } view {
{label}
} } `, }; case "api": return { path: `api/${clean}.ts`, content: `import type { Context } from "@wrnexus/core"; export async function GET(ctx: Context): Promise { return Response.json({ ok: true, route: ctx.url.pathname }); } `, }; case "schema": return { path: `schemas/${clean}.ts`, content: `import { v } from "@wrnexus/validation"; export default v.object({ name: v.string().min(1, "Required"), }); `, }; } } /** Write a scaffolded file under `/app`, refusing to overwrite. */ export function runGenerate( appRoot: string, typeArg: string | undefined, name: string | undefined, ): void { const type = typeArg ? ALIASES[typeArg] : undefined; if (!type || !name) { console.error("Usage: wrnexus generate "); process.exit(1); } const file = scaffold(type, name); const target = join(resolve(appRoot), "app", file.path); if (existsSync(target)) { console.error(`Refusing to overwrite existing file: app/${file.path}`); process.exit(1); } mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, file.content, "utf8"); console.log(`✓ Created app/${file.path}`); }