first commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* `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|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}`);
|
||||
}
|
||||
Reference in New Issue
Block a user