feat: configure workspace environment runtimes

This commit is contained in:
2026-07-20 09:22:26 +05:30
parent 3888453e0d
commit b3e5e5b999
58 changed files with 183 additions and 103 deletions
+15 -1
View File
@@ -65,6 +65,11 @@ Usage:
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
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.
`);
}
@@ -210,7 +215,16 @@ async function main(): Promise<void> {
if (!knownEnvironments.has(command)) throw new Error("unknown-environment-command");
const environmentArgs = rest.filter((arg) => arg !== workspaceRoot);
environmentArgs.push(`--environment=${command}`);
if (command === "development") {
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);
+9
View File
@@ -664,6 +664,15 @@ const MIGRATIONS: Migration[] = [
// Existing workspaces gain the CLI command aliases and optional UI props automatically.
},
},
{
version: "0.2.67",
id: "configurable-workspace-environment-runtime",
description:
"Adds per-environment runtime, HMR, build, migration, profile, hostname, and port controls to workspace commands.",
apply() {
// Existing workspace configurations remain compatible; new controls are opt-in.
},
},
];
/** Release tooling uses this to require an explicit migration entry per version. */
+36 -16
View File
@@ -25,6 +25,12 @@ export interface WorkspaceEnvironmentConfig {
protocol?: "http" | "https";
rootDomain?: string;
port?: number;
hostname?: string;
runtime?: "development" | "production";
hmr?: boolean;
build?: boolean;
migrate?: boolean;
profile?: string;
}
export interface WorkspaceApp {
@@ -54,7 +60,7 @@ export interface WorkspaceApp {
export interface WorkspaceConfig {
defaultEnvironment?: WorkspaceEnvironment;
environments?: Record<WorkspaceEnvironment, WorkspaceEnvironmentConfig>;
environments?: Partial<Record<WorkspaceEnvironment, WorkspaceEnvironmentConfig>>;
apps: WorkspaceApp[];
security?: GatewaySecurity;
@@ -81,6 +87,12 @@ export const workspaceFiles = (name: string): Record<string, string> => ({
// Map each app to the domains it serves. \`wrnexus gateway\` runs them all behind
// one port and routes by Host header (add these hosts to your /etc/hosts).
const config: WorkspaceConfig = {
defaultEnvironment: "development",
environments: {
development: { protocol: "http", rootDomain: "localhost", port: 3000, runtime: "development", hmr: true, build: false, migrate: false },
staging: { protocol: "https", rootDomain: "staging.example.com", port: 443, runtime: "production", hmr: false, build: true, migrate: true },
production: { protocol: "https", rootDomain: "example.com", port: 443, runtime: "production", hmr: false, build: true, migrate: true },
},
// Gateway-wide security (all optional):
security: {
trustedHostsOnly: true, // reject requests for unknown domains
@@ -398,26 +410,23 @@ export async function runGateway(root: string, args: string[]): Promise<void> {
(args.includes("--prod") ? "production" : config.defaultEnvironment || "development");
const resolved = resolveWorkspaceConfig(config, environment);
const environmentConfig = resolved.config;
const runtime =
environmentConfig.runtime ?? (environment === "development" ? "development" : "production");
const port = portArg
? Number(portArg.split("=")[1])
: resolved.environment === "development"
? 3000
: 80;
: (environmentConfig.port ?? (runtime === "development" ? 3000 : 80));
const hostname = hostArg?.split("=")[1];
const hostname = hostArg?.split("=")[1] ?? environmentConfig.hostname;
const { startGateway } = await import("@wrnexus/dev-server");
await startGateway({
port,
hostname,
mode:
environment === "production" ||
args.includes("--prod") ||
process.env.NODE_ENV === "production"
? "production"
: "development",
mode: args.includes("--prod") || process.env.NODE_ENV === "production" ? "production" : runtime,
hmr: runtime === "development" ? (environmentConfig.hmr ?? true) : false,
environment,
security: resolved.security,
apps: resolved.apps.map((app) => ({
@@ -458,16 +467,21 @@ export function workspaceMigrationTargets(appRoot: string): Array<string | null>
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";
const environmentConfig = config.environments?.[environment] ?? {};
const profile = environmentConfig.profile ?? environment;
const shouldBuild =
!args.includes("--no-build") && (args.includes("--build") || environmentConfig.build !== false);
const shouldMigrate =
!args.includes("--no-migrate") &&
(args.includes("--migrate") || environmentConfig.migrate !== false);
process.env.NODE_ENV = "production";
process.env.WRNEXUS_ENV = environment;
process.env.WRNEXUS_PROFILE = environment;
process.env.WRNEXUS_PROFILE = profile;
console.log(`\n ⚡ Preparing WRNexus workspace (${environment})\n`);
if (shouldBuild) {
@@ -512,8 +526,12 @@ export async function runProduction(root: string, args: string[]): Promise<void>
gatewayArgs.push(`--environment=${environment}`);
}
if (!gatewayArgs.includes("--prod")) gatewayArgs.push("--prod");
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");
if (
!gatewayArgs.some((arg) => arg.startsWith("--host=")) &&
environmentConfig.hostname === undefined
) {
gatewayArgs.push("--host=0.0.0.0");
}
await runGateway(workspaceRoot, gatewayArgs);
}
@@ -555,6 +573,7 @@ export interface ResolvedWorkspaceApp extends WorkspaceApp {
export interface ResolvedWorkspaceConfig {
environment: WorkspaceEnvironment;
config: WorkspaceEnvironmentConfig;
apps: ResolvedWorkspaceApp[];
security?: GatewaySecurity;
}
@@ -635,6 +654,7 @@ export function resolveWorkspaceConfig(
return {
environment,
config: environmentConfig,
apps,
security: config.security,
};