#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { CI_RUNNER_LABEL, dockerImageTag, HOST_OVERRIDE_VALIDATION_PATTERN as INFRA_HOST_OVERRIDE_VALIDATION_PATTERN, HOST_OVERRIDE_WRITE_PATTERN as INFRA_HOST_OVERRIDE_WRITE_PATTERN, } from "./infra-config.mjs"; const WORKFLOW_PATH = ".gitea/workflows/ci.yml"; const SENTRY_REUSABLE_WORKFLOW_PATH = ".gitea/workflows/_sentry-release.yml"; const REQUIRED_PNPM_VERSION = "10.33.0"; const WEB_SOURCEMAPS_ARTIFACT_NAME = "web-client-sourcemaps"; const WEB_SOURCEMAPS_PATH = "apps/web/build/client"; const WEB_SOURCEMAPS_VALIDATE_COMMAND = "test -n \"$(find apps/web/build/client -type f -name '*.map' -print -quit)\""; const REQUIRED_COMMANDS_IN_ORDER = [ "pnpm run verify:github-ci-workflow", "pnpm run verify:gitea-ci-workflow", "pnpm run verify:deploy-workflow", "pnpm run verify:deploy-readiness", "pnpm run verify:ci-baseline", "pnpm run verify:source-health", "pnpm run verify:eslint-baseline", "pnpm run verify:env-drift", "pnpm run verify:health-baseline", "pnpm run verify:api-inventory", "pnpm run verify:tenant-isolation-report", "pnpm run verify:sidebar-routes", "pnpm run verify:i18n", "pnpm run verify:tenant-auth-baseline", "pnpm run verify:onboarding-flow", "pnpm run verify:phase-c-boundaries", "pnpm run verify:core-build-without-plugins-smoke", "pnpm run verify:removed-modules", "pnpm run verify:ui-visual", "pnpm run test:hot-swap", "pnpm run verify:a11y-coverage", "pnpm run test:a11y", "pnpm run verify:porting-docs-baseline", "pnpm run verify:production-docs", "pnpm run verify:release-docs", "pnpm run verify:docs-links", "pnpm typecheck", "pnpm lint", "pnpm run test:foundation", "pnpm run test:packages", "pnpm run test:mobile", "pnpm test", "pnpm run build:ci", "pnpm run verify:bundle-size", "pnpm run verify:production-seed-artifact", ]; const SERVICE_JOB_CONTRACTS = { api: { label: "api", commands: [ "pnpm run verify:openapi-security", "pnpm --filter @toolctrl/api typecheck", "pnpm exec vitest run apps/api/src", "pnpm --filter @toolctrl/api build", ], }, worker: { label: "worker", commands: [ "pnpm --filter @toolctrl/worker typecheck", "pnpm --filter @toolctrl/worker exec vitest run", "pnpm run verify:workflow-outbox-native", "pnpm --filter @toolctrl/worker build", "pnpm --filter @toolctrl/worker health", ], }, }; const DATABASE_JOB = "database"; const DATABASE_STATUS_COMMAND = "pnpm run verify:db-migrate-status:staging"; const DATABASE_GENERATE_COMMAND = "pnpm run db:generate"; const STAGING_SSH_HOST_VAR = "${{ vars.COOLIFY_STAGING_SSH_HOST }}"; const STAGING_SSH_USER_VAR = "${{ vars.COOLIFY_STAGING_SSH_USER }}"; const STAGING_WEB_RESOURCE_UUID_VAR = "${{ vars.COOLIFY_STAGING_WEB_RESOURCE_UUID }}"; const STAGING_SSH_KEY_SECRET = "${{ secrets.COOLIFY_STAGING_SSH_KEY }}"; const FORBIDDEN_DATABASE_MUTATION_PATTERN = /\b(?:prisma\s+migrate\s+deploy|migrate:deploy|db:deploy|db:push|prisma\s+db\s+push|seed)\b/i; const DOCKER_SMOKE_JOB = "docker-smoke"; const DOCKER_SMOKE_NEEDS = ["quality", "api", "worker", "database"]; const DOCKER_SMOKE_TARGETS = ["web", "api", "worker"]; const DOCKER_WEB_TAG = dockerImageTag("web"); const DOCKER_API_TAG = dockerImageTag("api"); const DOCKER_WORKER_TAG = dockerImageTag("worker"); const DOCKER_WEB_ARTIFACT_COMMAND = `docker run --rm --entrypoint /bin/sh ${DOCKER_WEB_TAG} -lc 'test -f /myapp/apps/web/build/server/index.js && test -d /myapp/apps/web/build/client && test -f /myapp/build/server-runtime/server.js'`; const DOCKER_WEB_RUNTIME_COMMAND = `bash scripts/docker-web-runtime-smoke.sh ${DOCKER_WEB_TAG}`; const DOCKER_API_RUNTIME_COMMAND = `bash scripts/docker-api-runtime-smoke.sh ${DOCKER_API_TAG}`; const DOCKER_WORKER_RUNTIME_COMMAND = `bash scripts/docker-worker-runtime-smoke.sh ${DOCKER_WORKER_TAG}`; const DOCKER_SMOKE_REVISION_ARG_PATTERN = /(?:^|\s)--build-arg(?:=|\s+)APP_REVISION=ci-smoke-revision(?:\s|$)/; const REQUIRED_RUNNER = CI_RUNNER_LABEL; const FORBIDDEN_E2E_PATTERN = /\b(playwright|cypress|browser|test:e2e|e2e)\b/i; const FORBIDDEN_DOCKER_PUBLISH_PATTERN = /\b(?:docker\s+(?:login|push)|docker\s+buildx\s+build[^\n]*--push|docker\s+build[^\n]*--push|publish)\b/i; const FORBIDDEN_GITEA_HOST_OVERRIDE_PATTERN = /\bGITEA_HOST_IP\b|\bvars\.GITEA_HOST_IP\b/; const HOST_OVERRIDE_WRITE_PATTERN = INFRA_HOST_OVERRIDE_WRITE_PATTERN; const HOST_OVERRIDE_VALIDATION_PATTERN = INFRA_HOST_OVERRIDE_VALIDATION_PATTERN; function readWorkflow(rootDir) { return fs.readFileSync(path.join(rootDir, WORKFLOW_PATH), "utf8"); } function readOptionalFile(rootDir, relativePath) { const absolutePath = path.join(rootDir, relativePath); if (!fs.existsSync(absolutePath)) { return null; } return fs.readFileSync(absolutePath, "utf8"); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function includesLineMatching(content, pattern) { return content.split(/\r?\n/).some((line) => pattern.test(line)); } function checkWorkflowExists(rootDir) { return fs.existsSync(path.join(rootDir, WORKFLOW_PATH)); } function getJobSection(content, jobName) { const lines = content.split(/\r?\n/); const jobStartPattern = new RegExp(`^ ${jobName}:\\s*$`); const startIndex = lines.findIndex((line) => jobStartPattern.test(line)); if (startIndex === -1) { return null; } const sectionLines = [lines[startIndex]]; for (let index = startIndex + 1; index < lines.length; index += 1) { const line = lines[index]; if (/^ [A-Za-z0-9_-]+:\s*$/.test(line)) { break; } sectionLines.push(line); } return sectionLines.join("\n"); } function hasCheckoutStep(section) { return /uses:\s*actions\/checkout@v\d+/.test(section); } function hasCorepackSetup(section) { return /\bcorepack\s+enable\b/i.test(section); } function hasPinnedPnpm(section) { return new RegExp(`\\bcorepack\\s+prepare\\s+pnpm@${REQUIRED_PNPM_VERSION.replaceAll(".", "\\.")}\\s+--activate\\b`).test(section); } function hasFrozenInstall(section) { return /pnpm\s+install\s+--frozen-lockfile\b/.test(section); } function hasPnpmStoreSetup(section) { return /pnpm\s+config\s+set\s+store-dir\b/.test(section) || /path:\s*\.pnpm-store\b/.test(section); } function checkWorkflowConcurrency(content) { if ( /^concurrency:\s*$/m.test(content) && /^\s+group:\s*\$\{\{\s*github\.workflow\s*\}\}-\$\{\{\s*github\.event\.pull_request\.number\s*\|\|\s*github\.ref\s*\}\}\s*$/m.test(content) && /^\s+cancel-in-progress:\s*true\s*$/m.test(content) ) { return { passes: [`${WORKFLOW_PATH}: CI concurrency cancels superseded pull request and branch runs`], failures: [], observations: [] }; } return { passes: [], failures: [`${WORKFLOW_PATH}: CI workflow must define concurrency by workflow plus PR/ref with cancel-in-progress: true`], observations: [], }; } function hasNeedsQuality(section) { return getNeedsValues(section).includes("quality"); } function getNeedsValues(section) { const inlineNeeds = section.match(/^ needs:\s*\[([^\]]+)\]\s*$/m); if (inlineNeeds) { return inlineNeeds[1] .split(",") .map((value) => value.trim().replace(/^['\"]|['\"]$/g, "")) .filter(Boolean); } const singleNeeds = section.match(/^ needs:\s*([A-Za-z0-9_-]+)\s*$/m); if (singleNeeds) { return [singleNeeds[1]]; } const blockNeeds = section.match(/^ needs:\s*\n((?: -\s*[^\n]+\n?)+)/m); if (blockNeeds) { return blockNeeds[1] .split(/\r?\n/) .map((line) => line.match(/^ -\s*([^\s#]+)/)?.[1]?.replace(/^['\"]|['\"]$/g, "")) .filter(Boolean); } return []; } function hasDockerBuildxSetup(section) { return /uses:\s*docker\/setup-buildx-action@v\d+/.test(section); } function getRunCommands(section) { const lines = section.split(/\r?\n/); const commands = []; for (let index = 0; index < lines.length; index += 1) { const runMatch = lines[index].match(/^(\s*)run:\s*(.*)$/); if (!runMatch) { continue; } const runIndent = runMatch[1].length; const value = runMatch[2].trim(); const lineNumber = index + 1; if (/^[|>][+-]?$/.test(value)) { const blockLines = []; for (let blockIndex = index + 1; blockIndex < lines.length; blockIndex += 1) { const blockLine = lines[blockIndex]; const blockIndent = blockLine.match(/^(\s*)/)?.[1].length ?? 0; if (blockLine.trim() !== "" && blockIndent <= runIndent) { break; } blockLines.push(blockLine.trim()); index = blockIndex; } commands.push({ lineNumber, command: blockLines.join("\n") }); continue; } commands.push({ lineNumber, command: value }); } return commands; } function stripExecutableShellLine(line) { return line.replace(/\s+#.*$/, "").trim(); } function getExecutableRunCommandEntries(section) { return getRunCommands(section).flatMap(({ lineNumber, command }) => command .split(/\r?\n/) .map((line, offset) => ({ lineNumber: lineNumber + offset, command: stripExecutableShellLine(line) })) .filter(({ command }) => command.length > 0 && !command.startsWith("#")), ); } function getExecutableRunCommandBlocks(section) { return getRunCommands(section) .map(({ command }) => command .split(/\r?\n/) .map((line) => stripExecutableShellLine(line)) .filter((line) => line.length > 0 && !line.startsWith("#")) .join("\n"), ) .filter(Boolean); } function getAllStepBlocks(section) { const lines = section.split(/\r?\n/); const steps = []; for (let index = 0; index < lines.length; index += 1) { const stepMatch = lines[index].match(/^(\s*)-\s+/); if (!stepMatch) { continue; } const stepIndent = stepMatch[1].length; const stepLines = [lines[index]]; for (let inner = index + 1; inner < lines.length; inner += 1) { const candidate = lines[inner]; const indent = candidate.match(/^(\s*)/)?.[1].length ?? 0; if (candidate.trim().length > 0 && indent <= stepIndent && /^\s*-\s+/.test(candidate)) { break; } stepLines.push(candidate); index = inner; } const text = stepLines.join("\n"); steps.push(text); } return steps; } function getStepBlocks(section) { return getAllStepBlocks(section).filter((step) => /^\s*run:\s*/m.test(step)); } function getRunTextFromStep(step) { const lines = step.split(/\r?\n/); for (let index = 0; index < lines.length; index += 1) { const runMatch = lines[index].match(/^(\s*)run:\s*(.*)$/); if (!runMatch) { continue; } const runIndent = runMatch[1].length; const value = runMatch[2].trim(); if (!/^[|>][+-]?$/.test(value)) { return value; } const commandLines = []; for (let inner = index + 1; inner < lines.length; inner += 1) { const candidate = lines[inner]; const indent = candidate.match(/^(\s*)/)?.[1].length ?? 0; if (candidate.trim().length > 0 && indent <= runIndent) { break; } commandLines.push(candidate); } return commandLines.join("\n"); } return ""; } function getExecutableShellLines(runText) { return runText .split(/\r?\n/) .map((line) => stripExecutableShellLine(line)) .filter(Boolean); } function getStepForRunCommand(section, predicate) { return ( getStepBlocks(section).find((step) => { const lines = getExecutableShellLines(getRunTextFromStep(step)); const block = lines.join("\n"); return lines.some((line) => predicate(line)) || predicate(block); }) ?? "" ); } function hasUploadSourcemapsArtifactStep(section) { return getAllStepBlocks(section).some( (step) => /uses:\s*actions\/upload-artifact@v4/i.test(step) && new RegExp(`^\\s*name:\\s*${escapeRegExp(WEB_SOURCEMAPS_ARTIFACT_NAME)}\\s*$`, "m").test(step) && new RegExp(`^\\s*path:\\s*${escapeRegExp(WEB_SOURCEMAPS_PATH)}\\s*$`, "m").test(step) && /^\s*if-no-files-found:\s*error\s*$/m.test(step) && /^\s*retention-days:\s*7\s*$/m.test(step), ); } function hasReusableSourcemapsArtifactContract(content) { return ( /^\s+sourcemaps-artifact:\s*$/m.test(content) && /uses:\s*actions\/download-artifact@v4/i.test(content) && /^\s*if:\s*\$\{\{\s*inputs\.sourcemaps-artifact\s*!=\s*'none'\s*\}\}\s*$/m.test(content) && /^\s*name:\s*\$\{\{\s*inputs\.sourcemaps-artifact\s*\}\}\s*$/m.test(content) && /^\s*path:\s*\$\{\{\s*inputs\.sourcemaps-path\s*\}\}\s*$/m.test(content) ); } function matchesExecutableCommand(executableCommand, expectedCommand) { return executableCommand === expectedCommand; } function countExecutableCommandOccurrences(section, expectedCommand) { return getExecutableRunCommandEntries(section).filter(({ command }) => command === expectedCommand).length; } function hasDockerTargetBuild(section, target) { return getExecutableRunCommandEntries(section).some( ({ command }) => /\bdocker\s+(?:buildx\s+build|build)\b/.test(command) && new RegExp(`(?:^|\\s)--target(?:=|\\s+)${target}(?:\\s|$)`).test(command), ); } function getDockerTargetBuild(section, target) { return getExecutableRunCommandEntries(section).find( ({ command }) => /\bdocker\s+(?:buildx\s+build|build)\b/.test(command) && new RegExp(`(?:^|\\s)--target(?:=|\\s+)${target}(?:\\s|$)`).test(command), )?.command; } function hasDockerComposeConfig(section) { return getExecutableRunCommandEntries(section).some(({ command }) => /\bdocker\s+compose\s+config\b/.test(command)); } function hasWebDockerArtifactValidation(section) { return getExecutableRunCommandEntries(section).some(({ command }) => command === DOCKER_WEB_ARTIFACT_COMMAND); } function hasWebDockerRuntimeValidation(section) { return getExecutableRunCommandEntries(section).some(({ command }) => command === DOCKER_WEB_RUNTIME_COMMAND); } function hasApiDockerRuntimeValidation(section) { return getExecutableRunCommandEntries(section).some(({ command }) => command === DOCKER_API_RUNTIME_COMMAND); } function hasWorkerDockerRuntimeValidation(section) { return getExecutableRunCommandEntries(section).some(({ command }) => command === DOCKER_WORKER_RUNTIME_COMMAND); } function hasParallelDockerTargetBuilds(section) { return getRunCommands(section).some(({ command }) => { const hasEveryTarget = DOCKER_SMOKE_TARGETS.every((target) => new RegExp(`\\bdocker\\s+(?:buildx\\s+build|build)\\b[^\\n]*--target(?:=|\\s+)${target}(?:\\s|$)[^\\n]*&`, "u").test(command), ); const capturesEveryPid = DOCKER_SMOKE_TARGETS.every((target) => new RegExp(`\\b${target}_pid=\\$!`, "u").test(command)); const waitsForEveryPid = DOCKER_SMOKE_TARGETS.every((target) => new RegExp(`\\bwait\\s+"\\$${target}_pid"`, "u").test(command)); return hasEveryTarget && capturesEveryPid && waitsForEveryPid; }); } function includesCommand(section, command) { return getExecutableRunCommandEntries(section).some(({ command: executableCommand }) => matchesExecutableCommand(executableCommand, command)); } function getRequiredStepSofteningReason(step) { if (!step) { return ""; } if (/^\s*continue-on-error:\s*true\s*$/im.test(step) || /^\s*if:\s*(?:always\(\)|\$\{\{\s*always\(\)\s*\}\})\s*$/im.test(step)) { return "continue-on-error or always()"; } const lines = getExecutableShellLines(getRunTextFromStep(step)); const block = lines.join("\n"); const lastLine = lines.at(-1) ?? ""; if (/\bset\s+(?:\+e|\+o\s+errexit)\b/.test(block)) { return "shell success masking"; } if (/(^|[^|])\|\|\s*(?:true|:|exit\s+0)(?:\s|$)/.test(block)) { return "shell success masking"; } if (/;\s*(?:true|:|exit\s+0)\s*$/.test(lastLine)) { return "shell success masking"; } if (/^(?:true|:|exit\s+0)$/.test(lastLine)) { return "shell success masking"; } return ""; } function getRequiredCommandSofteningReason(section, command) { const step = getStepForRunCommand(section, (line) => line.includes(command)); return getRequiredStepSofteningReason(step); } function getRunCommandLines(section) { return getExecutableRunCommandBlocks(section).map((command) => `run: ${command}`); } function hasStepLevelEnvValue(section, command, envName, envValue) { const lines = section.split(/\r?\n/); const commandIndex = lines.findIndex((line) => /^\s*run:\s*/.test(line) && line.includes(command)); if (commandIndex === -1) { return false; } const commandIndent = lines[commandIndex].match(/^(\s*)/)?.[1].length ?? 0; const stepIndent = Math.max(commandIndent - 2, 0); let nextStepIndex = lines.length; for (let index = commandIndex + 1; index < lines.length; index += 1) { const line = lines[index]; if (new RegExp(`^\\s{${stepIndent}}-\\s+`).test(line)) { nextStepIndex = index; break; } } const stepRemainder = lines.slice(commandIndex + 1, nextStepIndex).join("\n"); const escapedEnvValue = envValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return new RegExp(`^\\s*${envName}:\\s*${escapedEnvValue}\\s*$`, "m").test(stepRemainder); } function hasJobLevelEnvValue(section, envName) { const firstStepIndex = section.search(/^ steps:\s*$/m); const beforeSteps = firstStepIndex === -1 ? section : section.slice(0, firstStepIndex); return new RegExp(`^\\s*${envName}:\\s*`, "m").test(beforeSteps); } export function checkWorkflowTriggers(content) { const passes = []; const failures = []; if (includesLineMatching(content, /^\s*push\s*:/m)) { failures.push(`${WORKFLOW_PATH}: trigger "push" is forbidden; main pushes are reserved for the deploy workflow`); } else { passes.push(`${WORKFLOW_PATH}: trigger "push" is disabled so deploy is not queued behind CI on main`); } if (includesLineMatching(content, /^\s*pull_request\s*:/m)) { passes.push(`${WORKFLOW_PATH}: trigger "pull_request" is defined`); } else { failures.push(`${WORKFLOW_PATH}: missing required trigger "pull_request"`); } if (includesLineMatching(content, /^\s*workflow_dispatch\s*:/m)) { passes.push(`${WORKFLOW_PATH}: trigger "workflow_dispatch" is defined`); } else { failures.push(`${WORKFLOW_PATH}: missing required trigger "workflow_dispatch" for manual CI runs`); } return { passes, failures, observations: [] }; } export function checkPnpmSetup(content) { const passes = []; const failures = []; const qualitySection = getJobSection(content, "quality"); if (!qualitySection) { failures.push(`${WORKFLOW_PATH}: missing required quality job "quality"`); return { passes, failures, observations: [] }; } if (hasCorepackSetup(qualitySection)) { passes.push(`${WORKFLOW_PATH}: Corepack setup is present`); } else { failures.push(`${WORKFLOW_PATH}: missing Corepack setup`); } if (hasPinnedPnpm(qualitySection)) { passes.push(`${WORKFLOW_PATH}: pnpm@${REQUIRED_PNPM_VERSION} is pinned`); } else { failures.push(`${WORKFLOW_PATH}: pnpm must be pinned to pnpm@${REQUIRED_PNPM_VERSION}`); } if (hasFrozenInstall(qualitySection)) { passes.push(`${WORKFLOW_PATH}: command "pnpm install --frozen-lockfile" is present`); } else { failures.push(`${WORKFLOW_PATH}: missing required command "pnpm install --frozen-lockfile"`); } return { passes, failures, observations: [] }; } export function checkOrderedQualityGates(content) { const passes = []; const failures = []; const qualitySection = getJobSection(content, "quality") ?? content; const executableCommands = getExecutableRunCommandEntries(qualitySection); let previousIndex = -1; for (const command of REQUIRED_COMMANDS_IN_ORDER) { const commandIndex = executableCommands.findIndex((entry) => matchesExecutableCommand(entry.command, command)); const commandOccurrences = countExecutableCommandOccurrences(qualitySection, command); const softeningReason = getRequiredCommandSofteningReason(qualitySection, command); if (commandIndex === -1) { if (softeningReason) { failures.push(`${WORKFLOW_PATH}: command "${command}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: missing required command "${command}"`); } continue; } if (commandOccurrences > 1) { failures.push(`${WORKFLOW_PATH}: command "${command}" must run exactly once to avoid redundant CI gates`); continue; } if (commandIndex < previousIndex) { failures.push(`${WORKFLOW_PATH}: command "${command}" must run after previous quality gates`); continue; } if (softeningReason) { failures.push(`${WORKFLOW_PATH}: command "${command}" must not use ${softeningReason}`); continue; } passes.push(`${WORKFLOW_PATH}: command "${command}" is ordered`); previousIndex = commandIndex; } const sourcemapsValidationStep = getStepForRunCommand(qualitySection, (line) => line === WEB_SOURCEMAPS_VALIDATE_COMMAND); if (sourcemapsValidationStep) { const softeningReason = getRequiredStepSofteningReason(sourcemapsValidationStep); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: web source-map validation must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: web source-map files are validated before Sentry artifact upload`); } } else { failures.push(`${WORKFLOW_PATH}: missing web source-map validation before Sentry artifact upload`); } if (hasUploadSourcemapsArtifactStep(qualitySection)) { passes.push(`${WORKFLOW_PATH}: web source maps are uploaded as artifact "${WEB_SOURCEMAPS_ARTIFACT_NAME}"`); } else { failures.push(`${WORKFLOW_PATH}: must upload ${WEB_SOURCEMAPS_PATH} as artifact "${WEB_SOURCEMAPS_ARTIFACT_NAME}" with if-no-files-found: error`); } return { passes, failures, observations: [] }; } export function checkSentrySourcemapWorkflow(rootDir, content) { const passes = []; const failures = []; const sentryReusableContent = readOptionalFile(rootDir, SENTRY_REUSABLE_WORKFLOW_PATH); if (!sentryReusableContent) { failures.push(`${SENTRY_REUSABLE_WORKFLOW_PATH}: missing reusable Sentry release workflow`); } else if (hasReusableSourcemapsArtifactContract(sentryReusableContent)) { passes.push(`${SENTRY_REUSABLE_WORKFLOW_PATH}: reusable workflow downloads requested source-map artifacts before upload`); } else { failures.push(`${SENTRY_REUSABLE_WORKFLOW_PATH}: reusable workflow must accept sourcemaps-artifact and download it to inputs.sourcemaps-path`); } const sentryWebSection = getJobSection(content, "sentry-release-web"); if (!sentryWebSection) { failures.push(`${WORKFLOW_PATH}: missing Sentry web release job "sentry-release-web"`); } else if ( new RegExp(`^\\s*sourcemaps-path:\\s*${escapeRegExp(WEB_SOURCEMAPS_PATH)}\\s*$`, "m").test(sentryWebSection) && new RegExp(`^\\s*sourcemaps-artifact:\\s*${escapeRegExp(WEB_SOURCEMAPS_ARTIFACT_NAME)}\\s*$`, "m").test(sentryWebSection) ) { passes.push(`${WORKFLOW_PATH}: Sentry web release consumes artifact "${WEB_SOURCEMAPS_ARTIFACT_NAME}"`); } else { failures.push(`${WORKFLOW_PATH}: Sentry web release must pass sourcemaps-path ${WEB_SOURCEMAPS_PATH} and sourcemaps-artifact ${WEB_SOURCEMAPS_ARTIFACT_NAME}`); } return { passes, failures, observations: [] }; } export function checkServiceJobs(content) { const passes = []; const failures = []; for (const [jobName, contract] of Object.entries(SERVICE_JOB_CONTRACTS)) { const section = getJobSection(content, jobName); if (!section) { failures.push(`${WORKFLOW_PATH}: missing required service job "${jobName}"`); continue; } passes.push(`${WORKFLOW_PATH}: service job "${jobName}" is defined`); if (hasNeedsQuality(section)) { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" must not wait for "quality"; scoped service checks should run in parallel`); } else { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" runs independently of "quality"`); } if (hasCheckoutStep(section)) { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" checks out the repository`); } else { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" missing checkout step`); } if (hasCorepackSetup(section)) { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" enables Corepack`); } else { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" missing Corepack setup`); } if (hasPinnedPnpm(section)) { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" pins pnpm@${REQUIRED_PNPM_VERSION}`); } else { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" must pin pnpm@${REQUIRED_PNPM_VERSION}`); } if (hasFrozenInstall(section)) { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" installs with frozen lockfile`); } else { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" missing command "pnpm install --frozen-lockfile"`); } for (const command of contract.commands) { if (includesCommand(section, command)) { const softeningReason = getRequiredCommandSofteningReason(section, command); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" command "${command}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: service job "${jobName}" includes scoped command "${command}"`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, command); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" command "${command}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: service job "${jobName}" missing scoped command "${command}"`); } } } } return { passes, failures, observations: [] }; } export function checkDatabaseJob(content) { const passes = []; const failures = []; const section = getJobSection(content, DATABASE_JOB); if (!section) { return { passes, failures: [`${WORKFLOW_PATH}: missing required database job "${DATABASE_JOB}"`], observations: [], }; } passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" is defined`); if (hasNeedsQuality(section)) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must not wait for "quality"; database verification should run in parallel`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" runs independently of "quality"`); } if (hasCheckoutStep(section)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" checks out the repository`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" missing checkout step`); } if (hasCorepackSetup(section)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" enables Corepack`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" missing Corepack setup`); } if (hasPinnedPnpm(section)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" pins pnpm@${REQUIRED_PNPM_VERSION}`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must pin pnpm@${REQUIRED_PNPM_VERSION}`); } if (hasFrozenInstall(section)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" installs with frozen lockfile`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" missing command "pnpm install --frozen-lockfile"`); } if (includesCommand(section, DATABASE_GENERATE_COMMAND)) { const softeningReason = getRequiredCommandSofteningReason(section, DATABASE_GENERATE_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" command "${DATABASE_GENERATE_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" includes command "${DATABASE_GENERATE_COMMAND}"`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DATABASE_GENERATE_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" command "${DATABASE_GENERATE_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" missing command "${DATABASE_GENERATE_COMMAND}"`); } } if (includesCommand(section, DATABASE_STATUS_COMMAND)) { const softeningReason = getRequiredCommandSofteningReason(section, DATABASE_STATUS_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" command "${DATABASE_STATUS_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" includes command "${DATABASE_STATUS_COMMAND}"`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DATABASE_STATUS_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" command "${DATABASE_STATUS_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" missing command "${DATABASE_STATUS_COMMAND}"`); } } if (hasStepLevelEnvValue(section, DATABASE_STATUS_COMMAND, "COOLIFY_STAGING_SSH_HOST", STAGING_SSH_HOST_VAR)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" scopes staging SSH host to the migrate-status step`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must set COOLIFY_STAGING_SSH_HOST: \${{ vars.COOLIFY_STAGING_SSH_HOST }} on the migrate-status step`); } if (hasStepLevelEnvValue(section, DATABASE_STATUS_COMMAND, "COOLIFY_STAGING_SSH_USER", STAGING_SSH_USER_VAR)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" scopes staging SSH user to the migrate-status step`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must set COOLIFY_STAGING_SSH_USER: \${{ vars.COOLIFY_STAGING_SSH_USER }} on the migrate-status step`); } if (hasStepLevelEnvValue(section, DATABASE_STATUS_COMMAND, "COOLIFY_STAGING_WEB_RESOURCE_UUID", STAGING_WEB_RESOURCE_UUID_VAR)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" scopes staging web resource UUID to the migrate-status step`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must set COOLIFY_STAGING_WEB_RESOURCE_UUID: \${{ vars.COOLIFY_STAGING_WEB_RESOURCE_UUID }} on the migrate-status step`); } if (hasStepLevelEnvValue(section, DATABASE_STATUS_COMMAND, "COOLIFY_STAGING_SSH_KEY", STAGING_SSH_KEY_SECRET)) { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" scopes staging SSH key to the migrate-status step`); } else { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must set COOLIFY_STAGING_SSH_KEY: \${{ secrets.COOLIFY_STAGING_SSH_KEY }} on the migrate-status step`); } if (section.includes("secrets.DATABASE_URL")) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must not require direct DATABASE_URL access to private Coolify databases`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" verifies private Coolify databases without exposing DATABASE_URL`); } if (hasJobLevelEnvValue(section, "DATABASE_URL")) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" must not set DATABASE_URL at job scope`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" does not expose DATABASE_URL at job scope`); } const forbiddenMutationLine = getRunCommandLines(section).find((line) => FORBIDDEN_DATABASE_MUTATION_PATTERN.test(line)); if (forbiddenMutationLine) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" contains forbidden deploy/seed/schema mutation command: ${forbiddenMutationLine.trim()}`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" contains no deploy/seed/schema mutation commands`); } const forbiddenE2eLine = getRunCommandLines(section).find((line) => FORBIDDEN_E2E_PATTERN.test(line)); if (forbiddenE2eLine) { failures.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" contains forbidden E2E/browser command: ${forbiddenE2eLine.trim()}`); } else { passes.push(`${WORKFLOW_PATH}: database job "${DATABASE_JOB}" contains no E2E/browser commands`); } return { passes, failures, observations: [] }; } export function checkDockerSmokeJob(content) { const passes = []; const failures = []; const section = getJobSection(content, DOCKER_SMOKE_JOB); if (!section) { return { passes, failures: [`${WORKFLOW_PATH}: missing required Docker smoke job "${DOCKER_SMOKE_JOB}"`], observations: [], }; } passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" is defined`); const needs = getNeedsValues(section); for (const requiredNeed of DOCKER_SMOKE_NEEDS) { if (needs.includes(requiredNeed)) { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" needs "${requiredNeed}"`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must declare needs including "${requiredNeed}"`); } } if (hasCheckoutStep(section)) { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" checks out the repository`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing checkout step`); } if (hasCorepackSetup(section)) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must not run Corepack setup`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" does not run Corepack setup`); } if (hasPinnedPnpm(section)) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must not run "corepack prepare pnpm@${REQUIRED_PNPM_VERSION}"`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" does not run "corepack prepare pnpm@${REQUIRED_PNPM_VERSION}"`); } if (hasPnpmStoreSetup(section)) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must not configure or cache the pnpm store`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" does not configure or cache the pnpm store`); } if (hasFrozenInstall(section)) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must not run "pnpm install --frozen-lockfile"`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" does not run "pnpm install --frozen-lockfile"`); } if (hasDockerBuildxSetup(section)) { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" sets up Docker Buildx`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing Docker Buildx setup`); } for (const target of DOCKER_SMOKE_TARGETS) { if (hasDockerTargetBuild(section, target)) { const targetBuild = getDockerTargetBuild(section, target) ?? ""; const targetBuildPattern = (line) => /\bdocker\s+(?:buildx\s+build|build)\b/.test(line) && new RegExp(`(?:^|\\s)--target(?:=|\\s+)${target}(?:\\s|$)`).test(line); const softeningReason = getRequiredStepSofteningReason(getStepForRunCommand(section, targetBuildPattern)); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" build for target "${target}" must not use ${softeningReason}`); } else if (!DOCKER_SMOKE_REVISION_ARG_PATTERN.test(targetBuild)) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" build for target "${target}" must pass --build-arg APP_REVISION=ci-smoke-revision`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" builds Docker target "${target}"`); } } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing Docker build for target "${target}"`); } } if (hasParallelDockerTargetBuilds(section)) { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" builds Docker targets in parallel`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" must build web, api, and worker Docker targets in one parallel bash step`); } if (hasDockerComposeConfig(section)) { const softeningReason = getRequiredCommandSofteningReason(section, "docker compose config"); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "docker compose config" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" validates Docker Compose config`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, "docker compose config"); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "docker compose config" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing command "docker compose config"`); } } if (hasWebDockerArtifactValidation(section)) { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WEB_ARTIFACT_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WEB_ARTIFACT_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" validates web Docker artifacts`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WEB_ARTIFACT_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WEB_ARTIFACT_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing command "${DOCKER_WEB_ARTIFACT_COMMAND}"`); } } if (hasWebDockerRuntimeValidation(section)) { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WEB_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WEB_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" validates web Docker runtime`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WEB_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WEB_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing command "${DOCKER_WEB_RUNTIME_COMMAND}"`); } } if (hasApiDockerRuntimeValidation(section)) { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_API_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_API_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" validates API Docker runtime`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_API_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_API_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing command "${DOCKER_API_RUNTIME_COMMAND}"`); } } if (hasWorkerDockerRuntimeValidation(section)) { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WORKER_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WORKER_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" validates worker Docker runtime`); } } else { const softeningReason = getRequiredCommandSofteningReason(section, DOCKER_WORKER_RUNTIME_COMMAND); if (softeningReason) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" command "${DOCKER_WORKER_RUNTIME_COMMAND}" must not use ${softeningReason}`); } else { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" missing command "${DOCKER_WORKER_RUNTIME_COMMAND}"`); } } const forbiddenLine = getRunCommandLines(section).find((line) => FORBIDDEN_DOCKER_PUBLISH_PATTERN.test(line)); if (forbiddenLine) { failures.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" contains forbidden publish command: ${forbiddenLine.trim()}`); } else { passes.push(`${WORKFLOW_PATH}: Docker smoke job "${DOCKER_SMOKE_JOB}" contains no registry login/push/publish commands`); } return { passes, failures, observations: [] }; } export function checkWorkflowRunners(content) { const passes = []; const failures = []; const runnerLines = content .split(/\r?\n/) .map((line, index) => ({ line, lineNumber: index + 1 })) .filter(({ line }) => /^\s*runs-on:\s*/.test(line)); if (runnerLines.length === 0) { failures.push(`${WORKFLOW_PATH}: missing required runs-on declarations`); return { passes, failures, observations: [] }; } for (const { line, lineNumber } of runnerLines) { const runner = line.replace(/^\s*runs-on:\s*/, "").trim().replace(/^['\"]|['\"]$/g, ""); if (runner === REQUIRED_RUNNER) { passes.push(`${WORKFLOW_PATH}:${lineNumber}: runner is "${REQUIRED_RUNNER}"`); } else { failures.push(`${WORKFLOW_PATH}:${lineNumber}: runs-on must be "${REQUIRED_RUNNER}" but found "${runner}"`); } } return { passes, failures, observations: [] }; } export function checkForbiddenE2eCommands(content) { const passes = []; const failures = []; const forbiddenLine = getRunCommandLines(content).find((line) => FORBIDDEN_E2E_PATTERN.test(line)); if (forbiddenLine) { failures.push(`${WORKFLOW_PATH}: forbidden E2E/browser command found: ${forbiddenLine.trim()}`); } else { passes.push(`${WORKFLOW_PATH}: no forbidden E2E/browser commands are present`); } return { passes, failures, observations: [] }; } export function checkGiteaVariableNames(content) { const passes = []; const failures = []; if (FORBIDDEN_GITEA_HOST_OVERRIDE_PATTERN.test(content)) { failures.push(`${WORKFLOW_PATH}: GITEA_HOST_IP is forbidden because Gitea reserves the GITEA_ variable prefix; use TOOLCTRL_GITEA_HOST_IP`); } else { passes.push(`${WORKFLOW_PATH}: internal host override avoids the reserved GITEA_ variable prefix`); } const hostWrites = [...content.matchAll(HOST_OVERRIDE_WRITE_PATTERN)].length; const validations = [...content.matchAll(HOST_OVERRIDE_VALIDATION_PATTERN)].length; if (hostWrites > validations) { failures.push(`${WORKFLOW_PATH}: TOOLCTRL_GITEA_HOST_IP must be validated before writing to /etc/hosts`); } else { passes.push(`${WORKFLOW_PATH}: internal host override validates TOOLCTRL_GITEA_HOST_IP before /etc/hosts writes`); } return { passes, failures, observations: [] }; } export function verifyGiteaCiWorkflow(rootDir = process.cwd()) { if (!checkWorkflowExists(rootDir)) { return { passes: [], failures: [`${WORKFLOW_PATH}: missing required Gitea CI workflow file`], observations: [] }; } let content; try { content = readWorkflow(rootDir); } catch (error) { return { passes: [], failures: [`${WORKFLOW_PATH}: failed to read workflow: ${error.message}`], observations: [] }; } const checks = [ checkWorkflowTriggers(content), checkWorkflowConcurrency(content), checkPnpmSetup(content), checkWorkflowRunners(content), checkOrderedQualityGates(content), checkServiceJobs(content), checkDatabaseJob(content), checkDockerSmokeJob(content), checkSentrySourcemapWorkflow(rootDir, content), checkForbiddenE2eCommands(content), checkGiteaVariableNames(content), ]; 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 formatGiteaCiWorkflowResult(result) { const status = result.failures.length > 0 ? "FAIL" : "PASS"; return [ `${status} Gitea CI workflow 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 = verifyGiteaCiWorkflow(process.cwd()); const output = formatGiteaCiWorkflowResult(result); if (result.failures.length > 0) { process.stderr.write(output); process.exitCode = 1; } else { process.stdout.write(output); } }