feat(cli): add production workspace command
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
* cross-process). `wrnexus.workspace.ts` maps each app to the domains it serves.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { scaffoldApp } from "./create.ts";
|
||||
@@ -68,7 +68,8 @@ export const workspaceFiles = (name: string): Record<string, string> => ({
|
||||
"workspaces": ["apps/*", "packages/*"],
|
||||
"scripts": {
|
||||
"dev": "wrnexus gateway",
|
||||
"gateway": "wrnexus gateway"
|
||||
"gateway": "wrnexus gateway",
|
||||
"production": "wrnexus production"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wrnexus/cli": "${frameworkVersion}"
|
||||
@@ -394,8 +395,7 @@ export async function runGateway(root: string, args: string[]): Promise<void> {
|
||||
profileArg?.split("=")[1] ||
|
||||
process.env.WRNEXUS_ENV ||
|
||||
process.env.WRNEXUS_PROFILE ||
|
||||
config.defaultEnvironment ||
|
||||
(args.includes("--prod") ? "production" : "development");
|
||||
(args.includes("--prod") ? "production" : config.defaultEnvironment || "development");
|
||||
|
||||
const resolved = resolveWorkspaceConfig(config, environment);
|
||||
|
||||
@@ -426,6 +426,91 @@ export async function runGateway(root: string, args: string[]): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Return default/named database targets that contain SQL migrations. */
|
||||
export function workspaceMigrationTargets(appRoot: string): Array<string | null> {
|
||||
const dbRoot = join(resolve(appRoot), "app", "db");
|
||||
if (!existsSync(dbRoot)) return [];
|
||||
const targets: Array<string | null> = [];
|
||||
const hasSql = (dir: string) =>
|
||||
existsSync(dir) &&
|
||||
readdirSync(dir, { withFileTypes: true }).some(
|
||||
(entry) => entry.isFile() && entry.name.endsWith(".sql"),
|
||||
);
|
||||
if (hasSql(join(dbRoot, "migrations"))) targets.push(null);
|
||||
for (const entry of readdirSync(dbRoot, { withFileTypes: true })) {
|
||||
if (
|
||||
entry.isDirectory() &&
|
||||
entry.name !== "migrations" &&
|
||||
hasSql(join(dbRoot, entry.name, "migrations"))
|
||||
) {
|
||||
targets.push(entry.name);
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
/** Build, migrate, then serve every registered workspace app in production mode. */
|
||||
export async function runProduction(root: string, args: string[]): Promise<void> {
|
||||
const workspaceRoot = resolve(root);
|
||||
const config = await loadWorkspaceConfig(workspaceRoot);
|
||||
const shouldBuild = !args.includes("--no-build");
|
||||
const shouldMigrate = !args.includes("--no-migrate");
|
||||
const prepareOnly = args.includes("--prepare-only");
|
||||
const environmentArg = args.find((arg) => arg.startsWith("--environment="));
|
||||
const profileArg = args.find((arg) => arg.startsWith("--profile="));
|
||||
const environment = environmentArg?.split("=")[1] || profileArg?.split("=")[1] || "production";
|
||||
|
||||
process.env.NODE_ENV = "production";
|
||||
process.env.WRNEXUS_ENV = environment;
|
||||
process.env.WRNEXUS_PROFILE = environment;
|
||||
console.log(`\n ⚡ Preparing WRNexus workspace (${environment})\n`);
|
||||
|
||||
if (shouldBuild) {
|
||||
const { runBuild } = await import("./build.ts");
|
||||
for (const app of config.apps) {
|
||||
const appRoot = resolve(workspaceRoot, app.dir);
|
||||
if (!existsSync(join(appRoot, "package.json"))) {
|
||||
throw new Error(`Workspace app '${app.name}' has no package.json at ${appRoot}.`);
|
||||
}
|
||||
console.log(`\n ▸ Building ${app.name}`);
|
||||
await runBuild(appRoot);
|
||||
}
|
||||
} else {
|
||||
console.log(" = builds skipped (--no-build)");
|
||||
}
|
||||
|
||||
if (shouldMigrate) {
|
||||
const { runDbCommand } = await import("./db.ts");
|
||||
for (const app of config.apps) {
|
||||
const appRoot = resolve(workspaceRoot, app.dir);
|
||||
const targets = workspaceMigrationTargets(appRoot);
|
||||
if (!targets.length) {
|
||||
console.log(` = ${app.name}: no SQL migrations`);
|
||||
continue;
|
||||
}
|
||||
for (const target of targets) {
|
||||
console.log(`\n ▸ Migrating ${app.name}${target ? ` (${target})` : ""}`);
|
||||
await runDbCommand(appRoot, "migrate", target ? [`--db=${target}`] : []);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(" = migrations skipped (--no-migrate)");
|
||||
}
|
||||
|
||||
console.log("\n ✓ Workspace builds and migrations are ready.\n");
|
||||
if (prepareOnly) return;
|
||||
|
||||
const gatewayArgs = args.filter(
|
||||
(arg) => arg !== "--prepare-only" && arg !== "--no-build" && arg !== "--no-migrate",
|
||||
);
|
||||
if (!gatewayArgs.some((arg) => arg.startsWith("--environment="))) {
|
||||
gatewayArgs.push(`--environment=${environment}`);
|
||||
}
|
||||
if (!gatewayArgs.some((arg) => arg.startsWith("--host="))) gatewayArgs.push("--host=0.0.0.0");
|
||||
if (!gatewayArgs.some((arg) => arg.startsWith("--port="))) gatewayArgs.push("--port=3000");
|
||||
await runGateway(workspaceRoot, gatewayArgs);
|
||||
}
|
||||
|
||||
function environmentVariableName(appName: string): string {
|
||||
return `WRNEXUS_APP_${appName.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}_URL`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user