Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/verify-deploy-readiness.mjs
T

267 lines
9.3 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { GROUPS, desiredWorkerEnv, getWorkerEnvDrift } from "./coolify-ensure-worker-groups.mjs";
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const REQUIRED_PACKAGE_SCRIPTS = [
"verify:deploy-workflow",
"verify:docker-baseline",
"verify:docker-non-root",
"verify:health-baseline",
"verify:api-inventory",
"verify:openapi-security",
"verify:i18n",
"verify:phase-c-boundaries",
"test:hot-swap",
"verify:ui-visual",
"verify:docker-runtime-smoke",
"verify:docker-api-runtime-smoke",
"verify:docker-worker-runtime-smoke",
"verify:production-seed-artifact",
"verify:workflow-outbox-native",
"verify:plugin-runtime-live",
"db:migrate:deploy",
];
const WORKFLOW_OUTBOX_NATIVE_SCRIPT = "verify:workflow-outbox-native";
const REQUIRED_WORKFLOW_OUTBOX_NATIVE_TEST_TARGETS = [
"apps/worker/src/outbox/event-router.test.ts",
"apps/worker/src/jobs/workflow-trigger.test.ts",
"apps/worker/src/jobs/workflow-trigger-bootstrap.test.ts",
"apps/worker/src/jobs/workflow-trigger-boundary.test.ts",
"apps/worker/src/jobs/workflow-trigger-store.test.ts",
"apps/worker/src/health.test.ts",
"apps/web/app/modules/workflowEngine/routes/workflow-engine/workflow-producer-inventory.test.ts",
];
const REQUIRED_DEPLOY_WORKFLOW_MARKERS = [
"deploy-preflight",
"stage-or-promote",
"backup_confirmed",
"workflow_dispatch",
"docker buildx build --push --target",
"COOLIFY_STAGING_API_URL",
"COOLIFY_API_URL",
"/healthz",
"payload.revision",
"payload.service",
];
const REQUIRED_CI_COMMANDS = [
"pnpm run verify:deploy-workflow",
"pnpm run verify:deploy-readiness",
"pnpm run verify:docker-non-root",
"pnpm run verify:health-baseline",
"pnpm run verify:api-inventory",
"pnpm run verify:openapi-security",
"pnpm run verify:i18n",
"pnpm run test:hot-swap",
"pnpm run verify:workflow-outbox-native",
"pnpm run build:ci",
];
const REQUIRED_DOC_MARKERS = [
"stage-and-promote",
"web`, `api`, and `worker`",
"GET /healthz",
"GET /readyz",
"workflow_dispatch",
"backup_confirmed=true",
"main branch protection",
"direct pushes to `main` must be disabled",
"pull-request CI workflow to pass",
"Do not set `SEED_DEMO_DATA=true`",
];
const REQUIRED_WORKER_MATRIX_DOCS = [
"docs/worker.md",
"docs/ops/worker-queues.md",
"docs/deployments/boiler-saas-ctrl-com.md",
];
const REQUIRED_WORKER_RUNBOOK_MARKERS = [
"OUTBOX_DISPATCHER_REQUIRED=true",
"WORKFLOW_TRIGGER_OUTBOX_REQUIRED=true",
"TRANSACTIONAL_MAIL_OUTBOX_REQUIRED",
"Coolify readiness should probe `/readyz`",
];
const REQUIRED_CANONICAL_WORKER_RUNBOOK_MARKERS = [
"scripts/coolify-ensure-worker-groups.mjs",
"OUTBOX_DISPATCHER_REQUIRED=true",
"WORKFLOW_TRIGGER_OUTBOX_REQUIRED=true",
"TRANSACTIONAL_MAIL_OUTBOX_REQUIRED=true",
"pnpm run verify:plugin-runtime-live",
];
const REQUIRED_OPERATOR_CHECKLIST_MARKERS = ["B-008 is closed", "B-012", "operator-owned", "A7/#51 is backlogged", "B2"];
const REQUIRED_GENERAL_WORKER_ENV = {
OUTBOX_DISPATCHER_ENABLED: "true",
OUTBOX_DISPATCHER_REQUIRED: "true",
WORKFLOW_TRIGGER_EXECUTOR_MODE: "http",
WORKFLOW_TRIGGER_OUTBOX_ENABLED: "true",
WORKFLOW_TRIGGER_OUTBOX_REQUIRED: "true",
};
const REQUIRED_GENERAL_WORKER_DRIFT_KEYS = ["DATABASE_URL", "BULLMQ_REDIS_URL", "WORKFLOW_TRIGGER_EXECUTOR_URL", "API_ACCESS_TOKEN"];
function read(rootDir, relativePath, failures) {
const absolutePath = path.join(rootDir, relativePath);
if (!fs.existsSync(absolutePath)) {
failures.push(`${relativePath} missing required file`);
return "";
}
return fs.readFileSync(absolutePath, "utf8");
}
function checkIncludes(content, markers, label, result) {
for (const marker of markers) {
if (!content.includes(marker)) {
result.failures.push(`${label} missing ${marker}`);
} else {
result.passes.push(`${label} contains ${marker}`);
}
}
}
function checkWorkflowOutboxNativeScript(packageJson, result) {
const command = packageJson.scripts?.[WORKFLOW_OUTBOX_NATIVE_SCRIPT];
if (!command) {
return;
}
for (const target of REQUIRED_WORKFLOW_OUTBOX_NATIVE_TEST_TARGETS) {
if (command.includes(target)) {
result.passes.push(`package.json ${WORKFLOW_OUTBOX_NATIVE_SCRIPT} includes ${target}`);
} else {
result.failures.push(`package.json ${WORKFLOW_OUTBOX_NATIVE_SCRIPT} missing ${target}`);
}
}
}
function hasWorkerMatrixRow(content, group) {
const appName = `worker-${group.name}`;
const groupEnv = `WORKER_QUEUE_GROUP=${group.name}`;
return content.split(/\r?\n/).some((line) => {
if (!line.includes("|") || !line.includes(`\`${appName}\``) || !line.includes(`\`${groupEnv}\``)) {
return false;
}
return line.includes(`\`${group.healthPort}\``) && line.includes(`\`${group.adminPort}\``);
});
}
function checkWorkerMatrixDocs(rootDir, result) {
for (const relativePath of REQUIRED_WORKER_MATRIX_DOCS) {
const content = read(rootDir, relativePath, result.failures);
if (!content) continue;
for (const group of GROUPS) {
if (hasWorkerMatrixRow(content, group)) {
result.passes.push(`${relativePath} documents worker-${group.name} as ${group.name} on health ${group.healthPort} and admin ${group.adminPort}`);
} else {
result.failures.push(
`${relativePath} missing worker-${group.name} row with WORKER_QUEUE_GROUP=${group.name}, health port ${group.healthPort}, and admin port ${group.adminPort}`
);
}
}
}
const workerRunbook = read(rootDir, "docs/worker.md", result.failures);
if (workerRunbook) {
checkIncludes(workerRunbook, REQUIRED_WORKER_RUNBOOK_MARKERS, "worker docs", result);
}
const canonicalWorkerRunbook = read(rootDir, "docs/ops/worker-queues.md", result.failures);
if (canonicalWorkerRunbook) {
checkIncludes(canonicalWorkerRunbook, REQUIRED_CANONICAL_WORKER_RUNBOOK_MARKERS, "canonical worker queue docs", result);
}
}
function entriesFromRecord(record) {
return Object.entries(record).map(([key, value]) => ({ key, value }));
}
function checkCoolifyWorkerProvisionerContract(result) {
const general = GROUPS.find((group) => group.name === "general");
if (!general) {
result.failures.push("Coolify worker provisioner missing worker-general group");
return;
}
const desiredGeneralEnv = desiredWorkerEnv(general);
for (const [key, expected] of Object.entries(REQUIRED_GENERAL_WORKER_ENV)) {
if (desiredGeneralEnv[key] === expected) {
result.passes.push(`Coolify worker provisioner sets worker-general ${key}=${expected}`);
} else {
result.failures.push(`Coolify worker provisioner must set worker-general ${key}=${expected}`);
}
}
const runtimeDrift = getWorkerEnvDrift({ uuid: "worker-general", name: "worker-general" }, general, entriesFromRecord(desiredGeneralEnv));
const driftKeys = new Set(runtimeDrift.map((item) => item.key));
for (const key of REQUIRED_GENERAL_WORKER_DRIFT_KEYS) {
if (driftKeys.has(key)) {
result.passes.push(`Coolify worker provisioner detects missing worker-general ${key}`);
} else {
result.failures.push(`Coolify worker provisioner must detect missing worker-general ${key}`);
}
}
}
export function verifyDeployReadiness(rootDir = ROOT_DIR) {
const result = { passes: [], failures: [] };
const packageJsonContent = read(rootDir, "package.json", result.failures);
if (packageJsonContent) {
const packageJson = JSON.parse(packageJsonContent);
for (const scriptName of REQUIRED_PACKAGE_SCRIPTS) {
if (!packageJson.scripts?.[scriptName]) {
result.failures.push(`package.json missing script ${scriptName}`);
} else {
result.passes.push(`package.json script ${scriptName}`);
}
}
checkWorkflowOutboxNativeScript(packageJson, result);
}
checkIncludes(read(rootDir, ".gitea/workflows/deploy.yml", result.failures), REQUIRED_DEPLOY_WORKFLOW_MARKERS, ".gitea deploy workflow", result);
checkIncludes(read(rootDir, ".gitea/workflows/ci.yml", result.failures), REQUIRED_CI_COMMANDS, ".gitea CI workflow", result);
checkIncludes(read(rootDir, "docs/deployment.md", result.failures), REQUIRED_DOC_MARKERS, "deployment docs", result);
checkIncludes(
read(rootDir, "docs/improvement-plan/OPERATOR-CHECKLIST.md", result.failures),
REQUIRED_OPERATOR_CHECKLIST_MARKERS,
"operator checklist",
result
);
checkWorkerMatrixDocs(rootDir, result);
checkCoolifyWorkerProvisionerContract(result);
return result;
}
function printResult(result) {
for (const message of result.passes) {
console.log(`PASS ${message}`);
}
if (result.failures.length > 0) {
for (const message of result.failures) {
console.error(`FAIL ${message}`);
}
process.exitCode = 1;
} else {
console.log("Deploy readiness contract verified.");
}
}
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
if (invokedPath && fileURLToPath(import.meta.url) === invokedPath) {
try {
printResult(verifyDeployReadiness());
} catch (error) {
if (error instanceof SyntaxError) {
console.error(`FAIL package.json could not be parsed: ${error.message}`);
} else {
console.error(`FAIL deploy readiness verifier crashed: ${error instanceof Error ? error.message : String(error)}`);
}
process.exitCode = 1;
}
}