From 5d76f5481db94a297eca31d6ae777e8fb34207a9 Mon Sep 17 00:00:00 2001 From: Jonathan Baldie Date: Thu, 23 Jul 2026 15:13:17 +0100 Subject: [PATCH] ci: enforce aggregate production quality gate --- .github/workflows/ci.yml | 18 ++++++++++++++++++ scripts/messcript.mjs | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa8fc5c..3ba4156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,3 +93,21 @@ jobs: touch persist.dat npx --no-install stryker run mutation/stryker.config.json node mutation/stryker_check.js + + production-quality: + name: Production quality + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + + - name: Run aggregate production quality gate + run: npm run quality:production diff --git a/scripts/messcript.mjs b/scripts/messcript.mjs index 45fb6d2..12c01f6 100644 --- a/scripts/messcript.mjs +++ b/scripts/messcript.mjs @@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import process from "node:process"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; const projectRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const messcriptRepository = "https://github.com/quality-gates/messcript.git"; @@ -21,6 +21,10 @@ const productionUnits = new Map([ ["http-handler", ["src/handler.ts"]], ["entrypoint", ["main.ts"]], ]); +const requiredComplexityRules = [ + "CyclomaticComplexity", + "NPathComplexity", +]; function run(command, args, cwd) { const result = spawnSync(command, args, { @@ -133,6 +137,34 @@ function requestedPaths(unitName) { return paths; } +async function reportAggregatePolicy() { + const rulesets = await import( + pathToFileURL(join(toolRoot, "dist", "rulesets.js")).href + ); + const selectedRules = new Set( + rulesets.loadRulesets(["typescript"]).selections.map( + (selection) => selection.name, + ), + ); + const missingRules = requiredComplexityRules.filter( + (ruleName) => !selectedRules.has(ruleName), + ); + if (missingRules.length > 0) { + throw new Error( + `typescript policy is missing required rules: ${missingRules.join(", ")}`, + ); + } + + console.log( + `Production quality scope (${productionUnits.size}): ${ + [...productionUnits.keys()].join(", ") + }`, + ); + console.log( + `Required complexity rules: ${requiredComplexityRules.join(", ")}`, + ); +} + const unitName = process.argv[2] ?? "all"; try { @@ -141,6 +173,9 @@ try { if (acquireExit !== 0) { process.exitCode = acquireExit; } else { + if (unitName === "all") { + await reportAggregatePolicy(); + } process.exitCode = run( "node", [messcriptCli, paths.join(","), "text", "typescript", "--color=never"],