Files
WRNexusJS/packages/cli/src/deploy.ts
T
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

185 lines
5.6 KiB
TypeScript

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;
}