Files
Clintchiz 64ab20cc95
Quality / quality (ubuntu-latest) (push) Failing after 22s
Quality / quality (windows-latest) (push) Canceled after 0s
feat: add application productivity foundations
2026-08-23 11:13:03 +05:30

168 lines
5.0 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;
}
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 <h1>${pascal}</h1>\n <p>Generated resource page. Connect it to /api/${clean}.</p>\n }\n}\n`,
},
];
}
export function runGenerateResource(appRoot: string, name: string | undefined): void {
if (!name) throw new TypeError("Usage: wrnexus generate resource <name>");
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 {
<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="wrn-${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}`);
}