import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { generateDocker } from "./docker.ts"; export const DEPLOY_TARGETS = [ "docker", "kubernetes", "systemd", "railway", "render", "fly", ] as const; export type DeployTarget = (typeof DEPLOY_TARGETS)[number]; const ENVIRONMENT = `# Copy to .env.production and replace every required value. NODE_ENV=production PORT=3000 HOST=0.0.0.0 DATABASE_URL=postgres://USER:PASSWORD@HOST:5432/DB SESSION_SECRET=REPLACE_WITH_AT_LEAST_32_RANDOM_BYTES # OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com `; const OPERATIONS = `# WRNexus deployment operations - Liveness: \`GET /healthz\` - Readiness: \`GET /readyz\` (includes registered dependency checks) - Migrations: run \`bunx wrnexus db migrate --profile=production\` once per release before scaling. - Shutdown: the Bun production server drains on SIGTERM/SIGINT. - Assets: \`dist/public\` files are content-addressed and may be cached immutably by a CDN. - Secrets: provide \`DATABASE_URL\` and \`SESSION_SECRET\` through the platform secret store; never commit production env files. - Logs: stdout/stderr are structured for platform collection. Configure OTLP for centralized telemetry. - Scaling: start with 250m CPU/256Mi memory, use readiness probes, and scale horizontally from request latency and CPU. `; const KUBERNETES = `apiVersion: v1 kind: Service metadata: name: wrnexus spec: selector: { app: wrnexus } ports: [{ name: http, port: 80, targetPort: 3000 }] --- apiVersion: apps/v1 kind: Deployment metadata: name: wrnexus spec: replicas: 2 selector: { matchLabels: { app: wrnexus } } template: metadata: { labels: { app: wrnexus } } spec: containers: - name: app image: ghcr.io/OWNER/APP:latest ports: [{ containerPort: 3000 }] envFrom: [{ secretRef: { name: wrnexus-secrets } }] livenessProbe: { httpGet: { path: /healthz, port: 3000 }, initialDelaySeconds: 5 } readinessProbe: { httpGet: { path: /readyz, port: 3000 }, initialDelaySeconds: 5 } resources: requests: { cpu: 250m, memory: 256Mi } limits: { cpu: "1", memory: 512Mi } lifecycle: { preStop: { exec: { command: ["sh", "-c", "sleep 5"] } } } terminationGracePeriodSeconds: 30 --- apiVersion: batch/v1 kind: Job metadata: name: wrnexus-migrate spec: template: spec: restartPolicy: Never containers: - name: migrate image: ghcr.io/OWNER/APP:latest command: ["bunx", "wrnexus", "db", "migrate", "--profile=production"] envFrom: [{ secretRef: { name: wrnexus-secrets } }] `; const SYSTEMD = `[Unit] Description=WRNexus application After=network-online.target [Service] Type=simple WorkingDirectory=/srv/wrnexus EnvironmentFile=/etc/wrnexus/wrnexus.env ExecStartPre=/usr/bin/bunx wrnexus db migrate --profile=production ExecStart=/usr/bin/bun dist/server.js Restart=on-failure RestartSec=5 TimeoutStopSec=30 User=wrnexus Group=wrnexus NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ReadWritePaths=/srv/wrnexus [Install] WantedBy=multi-user.target `; const NGINX = `server { listen 80; server_name example.com; location /assets/ { root /srv/wrnexus/dist/public; expires 1y; add_header Cache-Control "public, immutable"; } location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Request-ID $request_id; } } `; const RAILWAY = `[build] builder = "DOCKERFILE" [deploy] startCommand = "bun dist/server.js" healthcheckPath = "/readyz" restartPolicyType = "ON_FAILURE" preDeployCommand = ["bunx wrnexus db migrate --profile=production"] `; const RENDER = `services: - type: web name: wrnexus runtime: docker healthCheckPath: /readyz preDeployCommand: bunx wrnexus db migrate --profile=production envVars: - key: DATABASE_URL sync: false - key: SESSION_SECRET sync: false `; const FLY = `app = "wrnexus-app" primary_region = "bom" [build] dockerfile = "Dockerfile" [env] PORT = "3000" [http_service] internal_port = 3000 force_https = true auto_stop_machines = "stop" auto_start_machines = true min_machines_running = 1 [[http_service.checks]] path = "/readyz" interval = "15s" timeout = "2s" [deploy] release_command = "bunx wrnexus db migrate --profile=production" `; function write(root: string, relative: string, content: string, files: string[]): void { const path = join(root, relative); if (existsSync(path)) return; mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, content, "utf8"); files.push(relative); } export function generateDeployment(appRoot: string, target: string): string[] { if (!DEPLOY_TARGETS.includes(target as DeployTarget)) throw new Error(`WRN-DEPLOY-TARGET: use ${DEPLOY_TARGETS.join(" | ")}.`); const root = resolve(appRoot); const files: string[] = []; if (target === "docker" || ["kubernetes", "railway", "render", "fly"].includes(target)) generateDocker(root); write(root, ".env.production.example", ENVIRONMENT, files); write(root, "deploy/README.md", OPERATIONS, files); if (target === "kubernetes") write(root, "deploy/kubernetes.yaml", KUBERNETES, files); if (target === "systemd") { write(root, "deploy/wrnexus.service", SYSTEMD, files); write(root, "deploy/nginx.conf", NGINX, files); } if (target === "railway") write(root, "railway.toml", RAILWAY, files); if (target === "render") write(root, "render.yaml", RENDER, files); if (target === "fly") write(root, "fly.toml", FLY, files); return files; }