Files
WRNexusJS/packages/cli/src/generate.ts
T

128 lines
3.1 KiB
TypeScript

/**
* `wrnexus generate <type> <name>` — 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<string, GenerateType> = {
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;
}
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 {
<h1>${pascal}</h1>
<p>Edit app/pages/${clean}.wrn to build this page.</p>
}
}
`,
};
case "component":
return {
path: `components/${clean}.wrn`,
content: `component ${pascal} {
props {
label = "${pascal}"
}
view {
<div class="wire-${baseName(clean)}">{label}</div>
}
}
`,
};
case "api":
return {
path: `api/${clean}.ts`,
content: `import type { Context } from "@wrnexus/core";
export async function GET(ctx: Context): Promise<Response> {
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 `<appRoot>/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 <page|component|layout|api|schema> <name>");
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}`);
}