feat: add application productivity foundations
Quality / quality (ubuntu-latest) (push) Failing after 22s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-23 11:13:03 +05:30
parent 46195462c3
commit 64ab20cc95
42 changed files with 1533 additions and 40 deletions
+35
View File
@@ -0,0 +1,35 @@
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { runGenerateResource } from "./generate.ts";
const FEATURES: Record<string, Record<string, string>> = {
queue: {
"app/queues/example.ts": `import { defineQueue } from "@wrnexus/queue";\n\nexport default defineQueue({\n name: "example",\n jobs: {\n run: {\n run: async (data: { id: string }) => { console.log("job", data.id); },\n success: async (data) => { console.log("completed", data.id); },\n failed: async (data, error) => { console.error("failed", data.id, error); },\n },\n },\n});\n`,
},
mail: {
"app/lib/mail.ts": `import { defineMail } from "@wrnexus/mail";\n\nexport const mail = defineMail({\n from: process.env.MAIL_FROM,\n driver: { send: async () => { throw new Error("Configure a mail driver"); } },\n sandbox: { enabled: process.env.NODE_ENV !== "production", allowlist: [] },\n});\n`,
},
seed: {
"app/db/seed.ts": `import { addSeedData, defineSeed } from "@wrnexus/db";\n\nexport default defineSeed(async (db) => {\n await addSeedData(db, { name: "Example" }, "examples", { conflict: "ignore" });\n});\n`,
},
testing: {
"test/app.test.ts": `import { expect, test } from "bun:test";\nimport { createTestApp } from "@wrnexus/test";\n\ntest("app boots", async () => {\n const app = await createTestApp(import.meta.dir + "/..");\n expect((await app.fetch("/healthz")).ok).toBe(true);\n app.close();\n});\n`,
},
};
export function runAdd(root: string, feature: string | undefined, name?: string) {
if (feature === "resource") return runGenerateResource(root, name);
const files = feature ? FEATURES[feature] : undefined;
if (!files)
throw new Error(`Use wrnexus add ${Object.keys(FEATURES).join(" | ")} | resource <name>`);
for (const [relative, content] of Object.entries(files)) {
const target = join(resolve(root), relative);
if (existsSync(target)) {
console.log(`${relative} exists — skipped`);
continue;
}
mkdirSync(dirname(target), { recursive: true });
writeFileSync(target, content, "utf8");
console.log(`✓ Added ${relative}`);
}
}
+16 -2
View File
@@ -7,6 +7,7 @@
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { runAdd } from "./add.ts";
import { AI_GUIDE, CLAUDE_MD } from "./ai-guide.ts";
import { currentCliVersion } from "./update-notifier.ts";
@@ -672,13 +673,26 @@ export function scaffoldApp(root: string, appName: string): void {
}
}
export function createApp(name: string): void {
export function createApp(name: string, options: readonly string[] = []): void {
if (!name) {
console.error("Usage: wrnexus create <app-name>");
process.exit(1);
}
scaffoldApp(resolve(process.cwd(), name), name);
const root = resolve(process.cwd(), name);
scaffoldApp(root, name);
const preset = options.find((value) => value.startsWith("--preset="))?.split("=")[1];
const requested =
options
.find((value) => value.startsWith("--features="))
?.split("=")[1]
?.split(",") ?? [];
const features = new Set([
...(preset === "saas" ? ["queue", "mail", "seed", "testing"] : []),
...(preset === "email" ? ["queue", "mail", "testing"] : []),
...requested,
]);
for (const feature of features) runAdd(root, feature);
console.log(`✓ Created ${name}`);
console.log(`\nNext steps:`);
+40
View File
@@ -30,6 +30,46 @@ export interface GeneratedFile {
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]+/)
+21 -1
View File
@@ -167,7 +167,7 @@ async function main(): Promise<void> {
break;
}
case "create":
createApp(rest[0] ?? "");
createApp(rest.find((value) => !value.startsWith("--")) ?? "", rest);
break;
case "workspace": {
const { addWorkspaceApp, createWorkspace } = await import("./workspace.ts");
@@ -192,6 +192,11 @@ async function main(): Promise<void> {
}
case "generate":
case "g": {
if (rest[0] === "resource") {
const { runGenerateResource } = await import("./generate.ts");
runGenerateResource(".", rest[1]);
break;
}
if (rest[0] === "routes") {
const { regenerateRoutes } = await import("./routes.ts");
const n = regenerateRoutes(join(resolve(rest[1] ?? "."), "app"));
@@ -307,6 +312,21 @@ async function main(): Promise<void> {
if (!result.ok) process.exitCode = 1;
break;
}
case "queue": {
const values = rest.filter((value) => !value.startsWith("--"));
const { runQueueCommand } = await import("./queue-command.ts");
await runQueueCommand(values[2] ?? ".", values[0] ?? "status", [
values[1] ?? "",
...rest.filter((value) => value.startsWith("--")),
]);
break;
}
case "add": {
const values = rest.filter((value) => !value.startsWith("--"));
const { runAdd } = await import("./add.ts");
runAdd(values[2] ?? ".", values[0], values[1]);
break;
}
case "security": {
const values = rest.filter((value) => !value.startsWith("--"));
const { runSecurityCommand } = await import("./security-command.ts");
+86
View File
@@ -0,0 +1,86 @@
import { resolve } from "node:path";
import { loadAppConfig } from "@wrnexus/styles";
import { connectFromConfig } from "@wrnexus/db/connect";
import { installSqliteQueueSchema } from "@wrnexus/queue";
export async function runQueueCommand(root: string, command: string, args: readonly string[]) {
const appRoot = resolve(root);
const config = await loadAppConfig(appRoot, process.env.WRNEXUS_PROFILE);
const selected = config.queue?.databaseName ?? "default";
const dbConfig = selected === "default" ? config.db : config.databases?.[selected];
const effective =
config.queue?.storage === "database"
? dbConfig
: { driver: "sqlite" as const, url: "file:./.wrnexus/queue.sqlite" };
if (!effective || effective.driver === "mongo")
throw new Error(`Queue database '${selected}' is not configured as SQL.`);
const db = connectFromConfig(effective, appRoot);
if (db.driver.dialect !== "sqlite")
throw new Error("Queue CLI currently requires SQLite configured storage.");
const table = config.queue?.table ?? "wrnexus_jobs";
await installSqliteQueueSchema(db, table);
try {
const id = args.find((value) => !value.startsWith("--"));
if (command === "list") {
console.table(
await db.all(
`SELECT id,name,run_at,priority,lease_owner,lease_until FROM ${table} ORDER BY run_at`,
),
);
return;
}
if (command === "status") {
const pending = await db.one<{ total: number }>(`SELECT COUNT(*) AS total FROM ${table}`);
const history = await db.one<{ total: number }>(
`SELECT COUNT(*) AS total FROM ${table}_history`,
);
console.log(
JSON.stringify(
{ pending: Number(pending?.total ?? 0), history: Number(history?.total ?? 0) },
null,
2,
),
);
return;
}
if (!id && command !== "clear") throw new Error(`wrnexus queue ${command} requires a job id`);
if (command === "inspect") {
console.log(
JSON.stringify(await db.one(`SELECT * FROM ${table} WHERE id = ?`, [id]), null, 2),
);
return;
}
if (command === "cancel") {
await db.exec(`DELETE FROM ${table} WHERE id = ?`, [id]);
return;
}
if (command === "retry") {
const row = await db.one<{ payload: string }>(
`SELECT payload FROM ${table}_history WHERE id = ? AND state = 'failed'`,
[id],
);
if (!row) throw new Error(`Failed job '${id}' was not found.`);
const job = JSON.parse(row.payload) as {
name: string;
runAt: number;
priority: number;
idempotencyKey?: string;
};
job.runAt = Date.now();
await db.exec(
`INSERT INTO ${table} (id,name,payload,run_at,priority,idempotency_key) VALUES (?,?,?,?,?,?)`,
[id, job.name, JSON.stringify(job), job.runAt, job.priority, job.idempotencyKey ?? null],
);
return;
}
if (command === "clear" && args.includes("--completed")) {
await db.exec(`DELETE FROM ${table}_history WHERE state = 'completed'`);
return;
}
throw new Error(
"Use queue list | status | inspect <id> | retry <id> | cancel <id> | clear --completed",
);
} finally {
await db.close();
}
}