first commit
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
#!/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 <app-name> scaffold a new app
|
||||
* wrnexus eject <name...> copy a Wire UI component into your app
|
||||
* wrnexus db <migrate|rollback|status|new> database migrations
|
||||
*/
|
||||
|
||||
import { join, resolve } from "node:path";
|
||||
import { resolveProfile, loadEnv } from "@wrnexus/styles";
|
||||
import { runDev } from "./dev.ts";
|
||||
import { createApp } from "./create.ts";
|
||||
|
||||
/**
|
||||
* Resolve the active profile from `--profile=<name>` (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 build [app-dir] Build a production server bundle + assets
|
||||
wrnexus create <app-name> Scaffold a new app
|
||||
wrnexus workspace <name> Scaffold a monorepo (apps/* + shared packages/*)
|
||||
wrnexus gateway [--port=3000] Serve every workspace app behind one port, routed by domain
|
||||
wrnexus generate <type> <name> Scaffold a page | component | api | schema
|
||||
wrnexus generate routes | docker | mobile
|
||||
Generate routes or scaffold deployment targets
|
||||
wrnexus mobile add <package...> 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 <capability...> Install capability packages for the configured mobile mode
|
||||
wrnexus eject <name...> Copy a Wire UI component into app/components
|
||||
wrnexus update [dir] [--latest] Upgrade @wrnexus/* deps + apply config/file migrations
|
||||
wrnexus db <cmd> Migrations: migrate | rollback | status | seed | generate | new
|
||||
wrnexus test [app-dir] [--watch] Run the app's tests (bun test, 'test' profile)
|
||||
wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files
|
||||
wrnexus doctor [app-dir] Check project structure, runtime, mobile config, and next fixes
|
||||
|
||||
Profiles: pass --profile=<name> to dev/build/db (or set WRNEXUS_PROFILE) to load
|
||||
that profile's .env cascade and config overrides. e.g. wrnexus dev --profile=uat
|
||||
`);
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const [command, ...rest] = process.argv.slice(2);
|
||||
|
||||
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);
|
||||
runDev(appRoot, port, host);
|
||||
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 "create":
|
||||
createApp(rest[0] ?? "");
|
||||
break;
|
||||
case "workspace": {
|
||||
const { createWorkspace } = await import("./workspace.ts");
|
||||
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 "generate":
|
||||
case "g": {
|
||||
if (rest[0] === "routes") {
|
||||
const { regenerateRoutes } = await import("./routes.ts");
|
||||
const n = regenerateRoutes(join(process.cwd(), "app"));
|
||||
console.log(`✓ Generated app/routes.gen.ts (${n} routes)`);
|
||||
break;
|
||||
}
|
||||
if (rest[0] === "docker") {
|
||||
const { generateDocker } = await import("./docker.ts");
|
||||
generateDocker(process.cwd());
|
||||
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 "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 "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 = runDoctor(rest.find((a) => !a.startsWith("--")) ?? ".");
|
||||
if (!healthy) process.exitCode = 1;
|
||||
break;
|
||||
}
|
||||
case "test": {
|
||||
const appRoot = rest.find((a) => !a.startsWith("--")) ?? ".";
|
||||
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);
|
||||
const { runTests } = await import("./test.ts");
|
||||
runTests(appRoot, rest);
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case "help":
|
||||
case "--help":
|
||||
case "-h":
|
||||
help();
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown command: ${command}\n`);
|
||||
help();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user