Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/verify-health-baseline.mjs
T

422 lines
15 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const WEB_HEALTHCHECK_PATH = "apps/web/app/routes/healthcheck.tsx";
const WEB_HEALTHZ_PATH = "apps/web/app/routes/healthz.tsx";
const WEB_READYZ_PATH = "apps/web/app/routes/readyz.tsx";
const API_APP_PATH = "apps/api/src/app.ts";
const API_SYSTEM_ROUTES_PATH = "apps/api/src/routes/system-routes.ts";
const WORKER_HEALTH_PATH = "apps/worker/src/health.ts";
const WORKER_MANIFEST_PATH = "apps/worker/package.json";
const DOCKERFILE_PATH = "Dockerfile";
function manifestPath(relativePath) {
return relativePath.split(path.sep).join("/");
}
function readTextFile(rootDir, relativePath) {
return fs.readFileSync(path.join(rootDir, relativePath), "utf8");
}
function getDockerfileTargetBody(dockerfile, targetName) {
const targetPattern = new RegExp(`^FROM\\s+\\S+(?:\\s+AS\\s+${targetName})\\s*$`, "im");
const targetMatch = targetPattern.exec(dockerfile);
if (!targetMatch) {
return null;
}
const bodyStart = targetMatch.index + targetMatch[0].length;
const nextTargetMatch = /^FROM\s+/gim.exec(dockerfile.slice(bodyStart));
const bodyEnd = nextTargetMatch ? bodyStart + nextTargetMatch.index : dockerfile.length;
return dockerfile.slice(bodyStart, bodyEnd);
}
function checkFileExists(rootDir, relativePath) {
return fs.existsSync(path.join(rootDir, relativePath));
}
function getApiGetRouteSource(source, routePath) {
const escapedRoutePath = routePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const routeStartMatch = new RegExp(`app\\.get\\(\\s*["']${escapedRoutePath}["']\\s*,`).exec(source);
if (!routeStartMatch) {
return null;
}
const callSource = readCallExpression(source, routeStartMatch.index);
if (!callSource) {
return null;
}
const handlerReferenceMatch = callSource.match(/app\.get\(\s*["'][^"']+["']\s*,\s*([A-Za-z_$][\w$]*)\s*\);?\s*$/);
if (handlerReferenceMatch) {
const handlerName = handlerReferenceMatch[1];
const handlerSourceMatch = new RegExp(`function\\s+${handlerName}\\s*\\([^)]*\\)\\s*{[\\s\\S]*?\\n}`).exec(source);
return [callSource, handlerSourceMatch?.[0] ?? ""].filter(Boolean).join("\n");
}
return callSource;
}
function readCallExpression(source, startIndex) {
let depth = 0;
let quote = null;
let escaped = false;
for (let index = startIndex; index < source.length; index += 1) {
const char = source[index];
if (quote) {
if (escaped) {
escaped = false;
} else if (char === "\\") {
escaped = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === '"' || char === "'" || char === "`") {
quote = char;
continue;
}
if (char === "(") {
depth += 1;
continue;
}
if (char === ")") {
depth -= 1;
if (depth === 0) {
const semicolonOffset = source.slice(index).match(/^\)\s*;?/)?.[0]?.length ?? 1;
return source.slice(startIndex, index + semicolonOffset);
}
}
}
return null;
}
export function checkWebHealthcheck(rootDir) {
const relativePath = WEB_HEALTHCHECK_PATH;
const passes = [];
const failures = [];
const observations = [];
if (!checkFileExists(rootDir, relativePath)) {
failures.push(`${relativePath}: missing existing web healthcheck route`);
} else {
const source = readTextFile(rootDir, relativePath);
passes.push(`${relativePath}: exists`);
if (!/\bdb\b|utils\/db\.server|@toolctrl\/db/.test(source) && !/\bfetch\s*\(/.test(source)) {
passes.push(`${relativePath}: legacy liveness healthcheck avoids DB and self-fetch`);
} else {
failures.push(`${relativePath}: legacy healthcheck must not import/check DB or self-fetch the app`);
}
}
if (checkFileExists(rootDir, WEB_HEALTHZ_PATH)) {
const healthzSource = readTextFile(rootDir, WEB_HEALTHZ_PATH);
passes.push(`${WEB_HEALTHZ_PATH}: defines lightweight /healthz liveness route`);
if (!/\bdb\b|utils\/db\.server|@toolctrl\/db/.test(healthzSource)) {
passes.push(`${WEB_HEALTHZ_PATH}: liveness route does not require DB`);
} else {
failures.push(`${WEB_HEALTHZ_PATH}: liveness route must not import or check db`);
}
if (/revision\s*:\s*process\.env\.APP_REVISION\s*\|\|\s*["']unknown["']/.test(healthzSource)) {
passes.push(`${WEB_HEALTHZ_PATH}: liveness route reports APP_REVISION`);
} else {
failures.push(`${WEB_HEALTHZ_PATH}: liveness route must report APP_REVISION as revision`);
}
if (/status\s*:\s*["']ok["']/.test(healthzSource) && /service\s*:\s*["']web["']/.test(healthzSource)) {
passes.push(`${WEB_HEALTHZ_PATH}: liveness route reports ok web service identity`);
} else {
failures.push(`${WEB_HEALTHZ_PATH}: liveness route must report status ok and service web`);
}
} else {
failures.push(`${WEB_HEALTHZ_PATH}: missing web /healthz liveness route`);
}
if (checkFileExists(rootDir, WEB_READYZ_PATH)) {
const readyzSource = readTextFile(rootDir, WEB_READYZ_PATH);
passes.push(`${WEB_READYZ_PATH}: defines database-backed /readyz readiness route`);
if (/import\s+\{\s*db\s*\}\s+from\s+["']~\/utils\/db\.server["']/.test(readyzSource) && /db\.[\w$]+\.(?:count|findFirst|findMany)|db\.\$queryRaw|db\.\$executeRaw/.test(readyzSource)) {
passes.push(`${WEB_READYZ_PATH}: readiness imports and checks db`);
} else {
failures.push(`${WEB_READYZ_PATH}: readiness route must import and check db`);
}
} else {
failures.push(`${WEB_READYZ_PATH}: missing web /readyz readiness route`);
}
return { passes, failures, observations };
}
export function checkApiHealthContract(rootDir) {
const relativePath = API_APP_PATH;
const passes = [];
const failures = [];
const observations = [];
if (!checkFileExists(rootDir, relativePath)) {
failures.push(`${relativePath}: missing API app source with health routes`);
return { passes, failures, observations };
}
const source = [
readTextFile(rootDir, relativePath),
checkFileExists(rootDir, API_SYSTEM_ROUTES_PATH) ? readTextFile(rootDir, API_SYSTEM_ROUTES_PATH) : "",
].join("\n");
const v1HealthRouteSource = getApiGetRouteSource(source, "/v1/health");
if (v1HealthRouteSource) {
passes.push(`${relativePath}: defines GET /v1/health`);
const routeSource = v1HealthRouteSource;
if (/service\s*:\s*["']api["']/.test(routeSource)) {
passes.push(`${relativePath}: /v1/health reports service "api"`);
} else {
failures.push(`${relativePath}: /v1/health must report service "api"`);
}
if (/requestId\s*:\s*c\.get\(\s*["']requestId["']\s*\)/.test(routeSource)) {
passes.push(`${relativePath}: /v1/health includes requestId`);
} else {
failures.push(`${relativePath}: /v1/health must include requestId`);
}
if (
/function\s+apiHealthResponse[\s\S]*?revision\s*:\s*getApiRuntimeConfig\(\)\.revision/.test(source) ||
/revision\s*:\s*getApiRuntimeConfig\(\)\.revision/.test(routeSource)
) {
passes.push(`${relativePath}: /v1/health reports configured APP_REVISION`);
} else {
failures.push(`${relativePath}: /v1/health must report configured APP_REVISION as revision`);
}
} else {
failures.push(`${relativePath}: missing GET /v1/health route`);
}
const healthzRouteSource = getApiGetRouteSource(source, "/healthz");
if (healthzRouteSource) {
passes.push(`${relativePath}: defines GET /healthz`);
const routeSource = healthzRouteSource;
if (/service\s*:\s*["']api["']/.test(routeSource)) {
passes.push(`${relativePath}: /healthz reports service "api"`);
} else {
failures.push(`${relativePath}: /healthz must report service "api"`);
}
if (/requestId\s*:\s*c\.get\(\s*["']requestId["']\s*\)/.test(routeSource)) {
passes.push(`${relativePath}: /healthz includes requestId`);
} else {
failures.push(`${relativePath}: /healthz must include requestId`);
}
if (
/function\s+apiHealthResponse[\s\S]*?revision\s*:\s*getApiRuntimeConfig\(\)\.revision/.test(source) ||
/revision\s*:\s*getApiRuntimeConfig\(\)\.revision/.test(routeSource)
) {
passes.push(`${relativePath}: /healthz reports configured APP_REVISION`);
} else {
failures.push(`${relativePath}: /healthz must report configured APP_REVISION as revision`);
}
} else {
failures.push(`${relativePath}: missing GET /healthz route`);
}
const readyzRouteSource = getApiGetRouteSource(source, "/readyz");
if (readyzRouteSource) {
passes.push(`${relativePath}: defines GET /readyz`);
const routeSource = readyzRouteSource;
if (/service\s*:\s*["']api["']/.test(routeSource)) {
passes.push(`${relativePath}: /readyz reports service "api"`);
} else {
failures.push(`${relativePath}: /readyz must report service "api"`);
}
if (/requestId\s*:\s*c\.get\(\s*["']requestId["']\s*\)/.test(routeSource)) {
passes.push(`${relativePath}: /readyz includes requestId`);
} else {
failures.push(`${relativePath}: /readyz must include requestId`);
}
if (/status\s*:\s*["']not_ready["']/.test(routeSource) && /\b503\b/.test(routeSource) && /code\s*:\s*["']dependency_unavailable["']/.test(routeSource)) {
passes.push(`${relativePath}: /readyz returns 503 not-ready dependency diagnostics`);
} else {
failures.push(`${relativePath}: /readyz must return 503 not_ready dependency diagnostics`);
}
} else {
failures.push(`${relativePath}: missing GET /readyz route`);
}
return { passes, failures, observations };
}
export function checkWorkerManifest(rootDir) {
const relativePath = WORKER_MANIFEST_PATH;
const passes = [];
const failures = [];
if (!checkFileExists(rootDir, relativePath)) {
return { passes, failures: [`${relativePath}: missing worker manifest with health script`], observations: [] };
}
try {
const manifest = JSON.parse(readTextFile(rootDir, relativePath));
if (manifest.scripts?.health === "node dist/health.js") {
passes.push(`${relativePath}: script "health" is node dist/health.js`);
} else {
failures.push(`${relativePath}: script "health" must be exactly node dist/health.js`);
}
} catch (error) {
failures.push(`${manifestPath(relativePath)}: failed to read manifest JSON: ${error.message}`);
}
return { passes, failures, observations: [] };
}
export function checkWorkerHealthReport(rootDir) {
const relativePath = WORKER_HEALTH_PATH;
const passes = [];
const failures = [];
const observations = [];
if (!checkFileExists(rootDir, relativePath)) {
failures.push(`${relativePath}: missing worker health source`);
return { passes, failures, observations };
}
const source = readTextFile(rootDir, relativePath);
if (/JSON\.stringify\(report\)/.test(source)) {
passes.push(`${relativePath}: CLI emits JSON health report`);
} else {
failures.push(`${relativePath}: CLI must emit JSON health report`);
}
const reportReturn = source.match(/return\s+{[\s\S]*?};/)?.[0] ?? "";
if (/redisSource\s*:/.test(reportReturn) && /redisParseStatus\s*:/.test(reportReturn) && !/redisUrl\s*:|redisPassword\s*:|password\s*:/.test(reportReturn)) {
passes.push(`${relativePath}: health JSON omits Redis URL and password values`);
} else {
failures.push(`${relativePath}: health JSON must not expose Redis URL or password values`);
}
if (/from\s+["']@toolctrl\/db\/client\.server["']/.test(source) || /\.\$queryRaw|\.\$executeRaw|\.appConfiguration\.count\(/.test(source)) {
passes.push(`${relativePath}: worker readiness checks DB`);
} else {
failures.push(`${relativePath}: worker readiness must check DB`);
}
if (/revision\s*:\s*env\.APP_REVISION\s*\|\|\s*["']unknown["']/.test(source)) {
passes.push(`${relativePath}: health report includes APP_REVISION`);
} else {
failures.push(`${relativePath}: health report must include APP_REVISION as revision`);
}
return { passes, failures, observations };
}
export function checkDockerfileWorkerHealthcheck(rootDir) {
const relativePath = DOCKERFILE_PATH;
const passes = [];
const failures = [];
if (!checkFileExists(rootDir, relativePath)) {
return { passes, failures: [`${relativePath}: missing Dockerfile with worker healthcheck`], observations: [] };
}
const dockerfile = readTextFile(rootDir, relativePath);
const workerBody = getDockerfileTargetBody(dockerfile, "worker");
if (!workerBody) {
failures.push(`${relativePath}: missing worker target`);
} else if (/HEALTHCHECK[\s\S]*?\bnode\s+apps\/worker\/dist\/health\.js\b/.test(workerBody)) {
passes.push(`${relativePath}: worker target contains HEALTHCHECK using compiled worker health`);
} else {
failures.push(`${relativePath}: worker target HEALTHCHECK must invoke node apps/worker/dist/health.js`);
}
return { passes, failures, observations: [] };
}
export function checkDockerfileWebApiHealthchecks(rootDir) {
const relativePath = DOCKERFILE_PATH;
const passes = [];
const failures = [];
if (!checkFileExists(rootDir, relativePath)) {
return { passes, failures: [`${relativePath}: missing Dockerfile with web/API healthchecks`], observations: [] };
}
const dockerfile = readTextFile(rootDir, relativePath);
for (const target of ["web", "api"]) {
const body = getDockerfileTargetBody(dockerfile, target);
const expectedPattern =
target === "api"
? /HEALTHCHECK[\s\S]*?\bcurl\b[\s\S]*?127\.0\.0\.1:\$\{API_PORT:-\$\{PORT\}\}\/healthz/
: /HEALTHCHECK[\s\S]*?\bcurl\b[\s\S]*?127\.0\.0\.1:\$\{PORT\}\/healthz/;
if (!body) {
failures.push(`${relativePath}: missing ${target} target`);
} else if (expectedPattern.test(body)) {
passes.push(`${relativePath}: ${target} target contains HEALTHCHECK using /healthz`);
} else {
const expectedPort = target === "api" ? "\\${API_PORT:-\\${PORT}}" : "\\${PORT}";
failures.push(`${relativePath}: ${target} target HEALTHCHECK must probe http://127.0.0.1:${expectedPort}/healthz`);
}
}
return { passes, failures, observations: [] };
}
export function verifyHealthBaseline(rootDir = process.cwd()) {
const checks = [
checkWebHealthcheck(rootDir),
checkApiHealthContract(rootDir),
checkWorkerManifest(rootDir),
checkWorkerHealthReport(rootDir),
checkDockerfileWorkerHealthcheck(rootDir),
checkDockerfileWebApiHealthchecks(rootDir),
];
return checks.reduce(
(result, check) => ({
passes: [...result.passes, ...check.passes],
failures: [...result.failures, ...check.failures],
observations: [...result.observations, ...check.observations],
}),
{ passes: [], failures: [], observations: [] },
);
}
export function formatHealthBaselineResult(result) {
const status = result.failures.length > 0 ? "FAIL" : "PASS";
return [
`${status} Health baseline verification`,
...result.passes.map((pass) => `PASS ${pass}`),
...result.observations.map((observation) => `OBSERVE ${observation}`),
...result.failures.map((failure) => `FAIL ${failure}`),
`Summary: ${result.passes.length} passed, ${result.observations.length} observed, ${result.failures.length} failed`,
"",
].join("\n");
}
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
if (isCli) {
const result = verifyHealthBaseline(process.cwd());
const output = formatHealthBaselineResult(result);
if (result.failures.length > 0) {
process.stderr.write(output);
process.exitCode = 1;
} else {
process.stdout.write(output);
}
}