128 lines
3.1 KiB
TypeScript
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}`);
|
|
}
|