ci: reuse quality gate across CI and release workflows (#167)

This commit is contained in:
2026-07-13 23:14:19 -07:00
committed by GitHub
parent 2e2ee3f028
commit 8b2cee2c58
14 changed files with 315 additions and 322 deletions
+8 -18
View File
@@ -12,19 +12,6 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
scripts: Record<string, string>;
};
test('ci workflow lints changelog fragments', () => {
assert.match(ciWorkflow, /bun run changelog:lint/);
});
test('ci workflow checks pull requests for required changelog fragments', () => {
assert.match(ciWorkflow, /bun run changelog:pr-check/);
assert.match(ciWorkflow, /skip-changelog/);
});
test('ci workflow verifies generated config examples stay in sync', () => {
assert.match(ciWorkflow, /bun run verify:config-example/);
});
test('package scripts expose a sharded maintained source coverage lane with lcov output', () => {
assert.equal(
packageJson.scripts['test:coverage:src'],
@@ -32,11 +19,14 @@ test('package scripts expose a sharded maintained source coverage lane with lcov
);
});
test('ci workflow runs the maintained source coverage lane and uploads lcov output', () => {
assert.match(ciWorkflow, /name: Coverage suite \(maintained source lane\)/);
assert.match(ciWorkflow, /run: bun run test:coverage:src/);
assert.match(ciWorkflow, /name: Upload coverage artifact/);
assert.match(ciWorkflow, /path: coverage\/test-src\/lcov\.info/);
test('ci delegates its gate instead of duplicating quality steps', () => {
assert.match(
ciWorkflow,
/build-test-audit:\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
);
assert.doesNotMatch(ciWorkflow, /oven-sh\/setup-bun/);
assert.doesNotMatch(ciWorkflow, /bun run test:coverage:src/);
assert.doesNotMatch(ciWorkflow, /bun run test:env/);
});
test('main docs deploy exists, serializes deploys, and uses Cloudflare credentials', () => {
+10 -5
View File
@@ -33,11 +33,16 @@ test('prerelease workflow uses committed prerelease notes and never calls claude
assert.doesNotMatch(prereleaseWorkflow, /run: bun run changelog:build/);
});
test('prerelease workflow includes the environment suite in the gate sequence', () => {
test('prerelease delegates its quality gate instead of duplicating quality steps', () => {
assert.match(
prereleaseWorkflow,
/Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite(?: \(source\))?\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/,
/quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
);
const qualityGateJob = prereleaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0];
assert.ok(qualityGateJob);
assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/);
assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/);
assert.doesNotMatch(qualityGateJob, /bun run test:env/);
});
test('prerelease workflow publishes GitHub prereleases and keeps them off latest', () => {
@@ -46,15 +51,15 @@ test('prerelease workflow publishes GitHub prereleases and keeps them off latest
assert.match(prereleaseWorkflow, /gh release create[\s\S]*--latest=false/);
});
test('prerelease workflow scopes dependency caches by runner architecture', () => {
test('prerelease packaging workflows scope dependency caches by runner architecture', () => {
const archScopedCacheKeyMatches = prereleaseWorkflow.match(
/key:\s*\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g,
);
const archScopedRestoreKeyMatches = prereleaseWorkflow.match(
/\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g,
);
assert.equal(archScopedCacheKeyMatches?.length, 5);
assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 10);
assert.equal(archScopedCacheKeyMatches?.length, 4);
assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 8);
});
test('prerelease workflow builds and uploads all release platforms', () => {
+66
View File
@@ -0,0 +1,66 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const qualityGateWorkflowPath = resolve(__dirname, '../.github/workflows/quality-gate.yml');
const qualityGateWorkflow = existsSync(qualityGateWorkflowPath)
? readFileSync(qualityGateWorkflowPath, 'utf8').replace(/\r\n/g, '\n')
: '';
const verificationDocPath = resolve(__dirname, '../docs/workflow/verification.md');
const verificationDoc = readFileSync(verificationDocPath, 'utf8').replace(/\r\n/g, '\n');
test('quality gate is a reusable workflow', () => {
assert.match(qualityGateWorkflow, /on:\s*\n\s*workflow_call:/);
assert.match(qualityGateWorkflow, /permissions:\s*\n\s*contents: read/);
});
test('quality gate checkout does not persist GitHub credentials', () => {
assert.match(
qualityGateWorkflow,
/uses: actions\/checkout@v4[\s\S]*?fetch-depth: 0[\s\S]*?submodules: true[\s\S]*?persist-credentials: false/,
);
});
test('quality gate installs Lua and runs the environment suite before coverage', () => {
assert.match(qualityGateWorkflow, /name: Install Lua/);
assert.match(qualityGateWorkflow, /apt-get install -y lua5\.4/);
assert.match(
qualityGateWorkflow,
/Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/,
);
});
test('quality gate uploads maintained source coverage', () => {
assert.match(qualityGateWorkflow, /run: bun run test:coverage:src/);
assert.match(qualityGateWorkflow, /name: Upload coverage artifact/);
assert.match(qualityGateWorkflow, /path: coverage\/test-src\/lcov\.info/);
});
test('quality gate keeps pull request changelog enforcement event-aware', () => {
assert.match(qualityGateWorkflow, /bun run changelog:lint/);
assert.match(qualityGateWorkflow, /if: github\.event_name == 'pull_request'/);
assert.match(
qualityGateWorkflow,
/env:\s*\n\s*BASE_REF: \${{ github\.base_ref }}\s*\n\s*PR_LABELS: \${{ join\(github\.event\.pull_request\.labels\.\*\.name, ','\) }}\s*\n\s*run: bun run changelog:pr-check --base-ref "origin\/\$BASE_REF" --head-ref "HEAD" --labels "\$PR_LABELS"/,
);
assert.match(qualityGateWorkflow, /skip-changelog/);
});
test('quality gate verifies generated config examples', () => {
assert.match(qualityGateWorkflow, /bun run verify:config-example/);
});
test('quality gate blocks on high-severity audit findings', () => {
const securityAuditStep =
qualityGateWorkflow.match(/- name: Security audit[\s\S]*?(?=\n {6}- name:|\n?$)/)?.[0] ?? '';
const dependencyAuditPolicySection =
verificationDoc.match(/## Dependency Audit Policy[\s\S]*?(?=\n## |\n?$)/)?.[0] ?? '';
assert.match(securityAuditStep, /run: bun audit --audit-level high/);
assert.doesNotMatch(securityAuditStep, /continue-on-error: true/);
assert.match(
dependencyAuditPolicySection,
/`bun audit --audit-level high` blocks the reusable quality gate/,
);
});
+10 -5
View File
@@ -81,11 +81,16 @@ test('release workflow verifies generated config examples before packaging artif
assert.match(releaseWorkflow, /bun run verify:config-example/);
});
test('release quality gate runs the maintained source coverage lane and uploads lcov output', () => {
assert.match(releaseWorkflow, /name: Coverage suite \(maintained source lane\)/);
assert.match(releaseWorkflow, /run: bun run test:coverage:src/);
assert.match(releaseWorkflow, /name: Upload coverage artifact/);
assert.match(releaseWorkflow, /path: coverage\/test-src\/lcov\.info/);
test('release delegates its quality gate instead of duplicating quality steps', () => {
assert.match(
releaseWorkflow,
/quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/,
);
const qualityGateJob = releaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0];
assert.ok(qualityGateJob);
assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/);
assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/);
assert.doesNotMatch(qualityGateJob, /bun run test:env/);
});
test('release build jobs install and cache stats dependencies before packaging', () => {