release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user