#!/usr/bin/env bun /** * @wrnexus/cli — the `wrnexus` command line. * * wrnexus dev [app-dir] [--port=3000] start the dev server (live reload) * wrnexus build [app-dir] build a production server + assets * wrnexus create scaffold a new app * wrnexus eject copy a WrNexus UI component into your app * wrnexus db database migrations * wrnexus authz authorization catalog tooling */ import { join, resolve } from "node:path"; import { resolveProfile, loadEnv } from "@wrnexus/styles"; import { runDev } from "./dev.ts"; import { createApp } from "./create.ts"; import { currentCliVersion, notifyIfUpdateAvailable } from "./update-notifier.ts"; /** * Resolve the active profile from `--profile=` (or WRNEXUS_PROFILE / mode), * publish it as WRNEXUS_PROFILE (so config loaders + the dev child pick it up), * and load its `.env` cascade into process.env. Returns the profile name. */ function bootstrapProfile( appRoot: string, mode: "development" | "production", args: string[], ): string { const flag = args.find((a) => a.startsWith("--profile=")); const profile = resolveProfile({ explicit: flag?.split("=")[1], mode }); process.env.WRNEXUS_PROFILE = profile; const loaded = loadEnv(resolve(appRoot), profile); const count = Object.keys(loaded).length; console.log(` ▸ profile: ${profile}${count ? ` (${count} env vars loaded)` : ""}`); return profile; } function help(): void { console.log(`wrnexus — WrNexus CLI Usage: wrnexus dev [app-dir] [--port=3000] [--host=::] Start the development server (live reload) wrnexus dev [app-dir] --services Start local production-service simulators with the app wrnexus dev [app-dir] --production-runtime wrnexus dev [app-dir] --services [--services-port=3099] [--services-http] Rebuild and reload the exact production artifact wrnexus build [app-dir] Build a production server bundle + assets wrnexus preview [app-dir] Serve the existing exact production output wrnexus create Scaffold a new app wrnexus workspace Scaffold a monorepo (apps/* + shared packages/*) wrnexus workspace add [--domain=name.localhost] Add an app to the current workspace wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain wrnexus production [workspace-dir] Build, migrate, and serve the complete production workspace wrnexus [workspace-dir] Run a configured workspace environment (development/staging/custom) wrnexus generate Scaffold a page | component | api | schema wrnexus generate routes | docker | mobile Generate routes or scaffold deployment targets wrnexus generate types [app-dir] Generate application-wide route/component/key types wrnexus routes [app-dir] Generate typed named routes wrnexus typecheck [app-dir] Generate types and check TypeScript plus every .wrn file wrnexus check [app-dir] Build/generate, typecheck, lint, test, and format-check wrnexus mobile add Install Capacitor or Expo native packages wrnexus mobile compile Compile .wrn pages into native Expo routes wrnexus native list List cross-platform native capabilities wrnexus native add Install capability packages for the configured mobile mode wrnexus eject Copy a WrNexus UI component into app/components wrnexus update [dir] [--latest] Upgrade deps, migrate project files, and verify the app wrnexus db Migrations: migrate | rollback | status | seed | generate | new wrnexus authz Authorization: list | generate | init [--dialect=sqlite|postgres|mysql] wrnexus test [level] [app-dir] [--watch] Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] Audit ASVS controls, inspect headers, or run abuse tests wrnexus api [app-dir] Generate OpenAPI, docs, Postman, examples and SDKs wrnexus sdk generate [app-dir] Generate TypeScript, JavaScript, Java, Go, or Python SDK wrnexus deploy [app-dir] Generate docker | kubernetes | systemd | railway | render | fly wrnexus mcp [app-dir] Start the WRNexus MCP server over stdio wrnexus i18n [app-dir] Extract and audit translation keys wrnexus report [app-dir] [--file=app/pages/page.wrn] Create a sanitized reproduction bundle wrnexus playground [--port=4173] Start the shareable WRN compiler playground wrnexus config [app-dir] --explain Print the fully resolved profile configuration wrnexus analyze [app-dir] Inspect dist/build-report.json and performance budgets wrnexus explain [subject] [app-dir] Explain compiler and production build decisions wrnexus explain [app-dir] Explain route caching or permission enforcement wrnexus inspect [app-dir] Inspect packages | plugins | routes | assets | runtimes | styles | migrations | bundle wrnexus inspect component [app-dir] Inspect a component's typed public contract wrnexus generate system Scaffold a complete framework-native package Profiles: pass --profile= to dev/build/db (or set WRNEXUS_PROFILE) to load that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat Workspace environments: configure protocol, rootDomain, port, hostname, runtime (development|production), hmr, build, migrate, and profile in wrnexus.workspace.ts. CLI overrides: --port, --host, --build/--no-build, --migrate/--no-migrate, --profile, and --prepare-only. Update options: --dry-run previews changes; --no-verify skips post-update check/build. `); } async function main(): Promise { const [command, ...rest] = process.argv.slice(2); if (command === "--version" || command === "-v" || command === "version") { console.log(currentCliVersion()); return; } notifyIfUpdateAvailable(currentCliVersion(), command); switch (command) { case "dev": { const appRoot = rest.find((a) => !a.startsWith("--")) ?? "."; const portArg = rest.find((a) => a.startsWith("--port=")); const hostArg = rest.find((a) => a.startsWith("--host=")); const port = portArg ? Number(portArg.split("=")[1]) : 3000; const host = hostArg?.split("=")[1] || "::"; bootstrapProfile(appRoot, "development", rest); let developmentCertificate: | { certFile: string; keyFile: string; cert: string; key: string; reused: boolean } | undefined; if (rest.includes("--services") && !rest.includes("--services-http")) { const { ensureLocalCertificate } = await import("./services.ts"); developmentCertificate = await ensureLocalCertificate(appRoot); } if (rest.includes("--services")) { const { startLocalServices } = await import("./services.ts"); await startLocalServices({ appRoot, port: Number( rest.find((value) => value.startsWith("--services-port="))?.split("=")[1] ?? 3099, ), https: !rest.includes("--services-http"), origin: `${rest.includes("--services-http") ? "http" : "https"}://localhost:${port}`, certificate: developmentCertificate, }); } if (rest.includes("--production-runtime")) { const { runProductionDev } = await import("./dev.ts"); await runProductionDev(appRoot, port, host); } else { runDev(appRoot, port, host, developmentCertificate); } break; } case "build": { const appRoot = rest.find((a) => !a.startsWith("--")) ?? "."; bootstrapProfile(appRoot, "production", rest); const { runBuild } = await import("./build.ts"); await runBuild(appRoot); break; } case "preview": { const appRoot = rest.find((a) => !a.startsWith("--")) ?? "."; const port = Number(rest.find((a) => a.startsWith("--port="))?.split("=")[1] ?? 3000); const hostname = rest.find((a) => a.startsWith("--host="))?.split("=")[1] || "::"; bootstrapProfile(appRoot, "production", rest); const { runPreview } = await import("./preview.ts"); runPreview(appRoot, { port, hostname }); break; } case "create": createApp(rest.find((value) => !value.startsWith("--")) ?? "", rest); break; case "workspace": { const { addWorkspaceApp, createWorkspace } = await import("./workspace.ts"); if (rest[0] === "add") { await addWorkspaceApp(".", rest[1] ?? "", rest.slice(2)); } else { createWorkspace(rest.find((a) => !a.startsWith("--")) ?? ""); } break; } case "gateway": { const appRoot = rest.find((a) => !a.startsWith("--")) ?? "."; const { runGateway } = await import("./workspace.ts"); await runGateway(appRoot, rest); break; } case "production": { const workspaceRoot = rest.find((a) => !a.startsWith("--")) ?? "."; const { runProduction } = await import("./workspace.ts"); await runProduction(workspaceRoot, rest); break; } 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")); console.log(`✓ Generated app/routes.gen.ts (${n} routes)`); break; } if (rest[0] === "types") { const { generateApplicationTypesWithPlugins } = await import("./types.ts"); const result = await generateApplicationTypesWithPlugins(rest[1] ?? "."); console.log( `✓ Generated ${result.file} (${result.routes} routes, ${result.components} components)`, ); break; } if (rest[0] === "docker") { const { generateDocker } = await import("./docker.ts"); generateDocker(process.cwd()); break; } if (rest[0] === "system") { const { generateSystem } = await import("./system.ts"); const files = generateSystem(process.cwd(), rest[1] ?? ""); console.log(`✓ Created @wrnexus/${rest[1]} (${files.length} files)`); break; } if (rest[0] === "mobile") { const { generateMobile, mobileOptions } = await import("./mobile.ts"); await generateMobile(process.cwd(), mobileOptions(rest.slice(1))); break; } const { runGenerate } = await import("./generate.ts"); runGenerate(".", rest[0], rest[1]); break; } case "routes": { const appRoot = resolve(rest.find((arg) => !arg.startsWith("--")) ?? "."); const { regenerateRoutes } = await import("./routes.ts"); const count = regenerateRoutes(join(appRoot, "app")); console.log(`✓ Generated app/routes.gen.ts (${count} routes)`); break; } case "typecheck": { const { runTypecheck } = await import("./types.ts"); const healthy = await runTypecheck(rest.find((arg) => !arg.startsWith("--")) ?? "."); if (!healthy) process.exitCode = 1; else console.log("✓ Application types are valid"); break; } case "check": { const appRoot = rest.find((arg) => !arg.startsWith("--")) ?? "."; bootstrapProfile(appRoot, "production", rest); const { runCheck } = await import("./check.ts"); await runCheck(appRoot); console.log("✓ Application check passed"); break; } case "eject": { const { runEject } = await import("./eject.ts"); const args = rest.filter((a) => !a.startsWith("--")); // First arg may be an app dir; treat known component names as names. runEject(".", args); break; } case "mobile": { const { runMobileCommand } = await import("./mobile-command.ts"); await runMobileCommand(".", rest[0], rest.slice(1)); break; } case "native": { const { runNativeCommand } = await import("./native-command.ts"); await runNativeCommand(".", rest[0], rest.slice(1)); break; } case "update": case "upgrade": { const dir = rest.find((a) => !a.startsWith("--")) ?? "."; const { runUpdate } = await import("./update.ts"); await runUpdate(dir, rest); break; } case "db": { bootstrapProfile(".", "development", rest); const { runDbCommand } = await import("./db.ts"); const [sub, ...dbArgs] = rest.filter((a) => !a.startsWith("--profile=")); await runDbCommand(".", sub, dbArgs); break; } case "authz": { bootstrapProfile(".", "development", rest); const { runAuthzCommand } = await import("./authz.ts"); const [sub, ...authzArgs] = rest.filter((a) => !a.startsWith("--profile=")); await runAuthzCommand(".", sub, authzArgs); break; } case "profiles": { const { listProfiles } = await import("./profiles.ts"); await listProfiles(rest.find((a) => !a.startsWith("--")) ?? "."); break; } case "doctor": { const { runDoctor } = await import("./doctor.ts"); const healthy = await runDoctor(rest.find((a) => !a.startsWith("--")) ?? ".", { fix: rest.includes("--fix"), }); if (!healthy) process.exitCode = 1; break; } case "contracts": { const { runContractsCommand } = await import("./contracts-command.ts"); const subcommand = rest.find((value) => !value.startsWith("--")) ?? "check"; const appRoot = rest.filter((value) => !value.startsWith("--"))[1] ?? "."; const result = await runContractsCommand(appRoot, subcommand, rest); 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"); const healthy = await runSecurityCommand(values[1] ?? ".", values[0] ?? "audit", rest); if (!healthy) process.exitCode = 1; break; } case "api": { const values = rest.filter((value) => !value.startsWith("--")); if (!["generate", "docs"].includes(values[0] ?? "")) throw new Error("WRN-API-COMMAND: use api generate or api docs."); const { runApiCommand } = await import("./api-command.ts"); runApiCommand(values[1] ?? ".", "api", rest); break; } case "sdk": { const values = rest.filter((value) => !value.startsWith("--")); if (values[0] !== "generate") throw new Error("WRN-SDK-COMMAND: use sdk generate ."); const { runApiCommand } = await import("./api-command.ts"); runApiCommand(values[2] ?? ".", "sdk", rest); break; } case "deploy": { const values = rest.filter((value) => !value.startsWith("--")); const { generateDeployment } = await import("./deploy.ts"); const files = generateDeployment(values[1] ?? ".", values[0] ?? ""); console.log(`✓ Deployment preset '${values[0]}' ready (${files.length} new files)`); break; } case "mcp": { const { runMcpStdio } = await import("@wrnexus/mcp/stdio"); await runMcpStdio(resolve(rest.find((value) => !value.startsWith("--")) ?? ".")); break; } case "i18n": { const values = rest.filter((value) => !value.startsWith("--")); const { runI18nCommand } = await import("./i18n-command.ts"); if (!runI18nCommand(values[1] ?? ".", values[0] ?? "validate")) process.exitCode = 1; break; } case "report": { const { runReport } = await import("./report.ts"); runReport(rest.find((value) => !value.startsWith("--")) ?? ".", rest); break; } case "playground": { const { createPlaygroundHandler } = await import("@wrnexus/playground"); const port = Number(rest.find((value) => value.startsWith("--port="))?.split("=")[1] ?? 4173); const server = Bun.serve({ port, hostname: "127.0.0.1", fetch: createPlaygroundHandler(), }); console.log(`▶ WRNexus playground: http://localhost:${server.port}`); break; } case "config": { const { runConfigCommand } = await import("./config-command.ts"); await runConfigCommand(rest.find((a) => !a.startsWith("--")) ?? ".", rest); break; } case "inspect": { const target = rest.find((arg) => !arg.startsWith("--")); if (target === "component") { const values = rest.filter((arg) => !arg.startsWith("--")); const { runInspectComponent } = await import("./inspect.ts"); runInspectComponent(values[2] ?? ".", values[1] ?? "", rest); break; } const appRoot = rest.filter((arg) => !arg.startsWith("--"))[1] ?? "."; const { runInspect } = await import("./inspect.ts"); await runInspect(appRoot, target, rest); break; } case "analyze": { const { runAnalyze } = await import("./analyze.ts"); const healthy = runAnalyze(rest.find((a) => !a.startsWith("--")) ?? ".", rest); if (!healthy) process.exitCode = 1; break; } case "explain": { const values = rest.filter((value) => !value.startsWith("--")); const target = values[0] ?? "build"; const subject = target === "build" || target === "bundle" ? "" : (values[1] ?? ""); const appRoot = target === "build" || target === "bundle" ? (values[1] ?? ".") : (values[2] ?? "."); const { runExplain } = await import("./explain.ts"); runExplain(appRoot, target, subject, rest); break; } case "test": { const { TEST_LEVELS, runTests } = await import("./test.ts"); const values = rest.filter((value) => !value.startsWith("--")); const hasLevel = TEST_LEVELS.includes(values[0] as (typeof TEST_LEVELS)[number]); const appRoot = (hasLevel ? values[1] : values[0]) ?? "."; const flag = rest.find((a) => a.startsWith("--profile=")); // Tests default to the `test` profile (config + .env.test), unless overridden. process.env.WRNEXUS_PROFILE = resolveProfile({ explicit: flag?.split("=")[1] ?? "test" }); loadEnv(resolve(appRoot), process.env.WRNEXUS_PROFILE); runTests(appRoot, rest); break; } case undefined: case "help": case "--help": case "-h": help(); break; default: { const { loadWorkspaceConfig, runGateway, runProduction } = await import("./workspace.ts"); const workspaceRoot = rest.find((a) => !a.startsWith("--")) ?? "."; try { const workspace = await loadWorkspaceConfig(resolve(workspaceRoot)); const knownEnvironments = new Set([ "development", "production", workspace.defaultEnvironment, ...Object.keys(workspace.environments ?? {}), ]); if (!knownEnvironments.has(command)) throw new Error("unknown-environment-command"); const environmentArgs = rest.filter((arg) => arg !== workspaceRoot); environmentArgs.push(`--environment=${command}`); const environmentConfig = workspace.environments?.[command]; const runtime = environmentConfig?.runtime ?? (command === "development" ? "development" : "production"); if (runtime === "development") { if (environmentArgs.includes("--prepare-only")) { console.log( `\n ✓ Workspace environment "${command}" uses the development runtime (HMR ${environmentConfig?.hmr === false ? "disabled" : "enabled"}).\n`, ); break; } await runGateway(workspaceRoot, environmentArgs); } else { await runProduction(workspaceRoot, environmentArgs); } } catch (error) { if (error instanceof Error && error.message !== "unknown-environment-command") throw error; const { runPluginCliCommand } = await import("./plugin-command.ts"); if (await runPluginCliCommand(workspaceRoot, command, rest)) break; console.error(`Unknown command or workspace environment: ${command}\n`); help(); process.exit(1); } } } } main().catch((err) => { console.error(err); process.exit(1); });