#!/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 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); 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[0] ?? ""); 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] === "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 "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 "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); });