diff --git a/.github/scripts/evaluate_quality_gate.mjs b/.github/scripts/evaluate_quality_gate.mjs index 71d2a4e..3ad6860 100644 --- a/.github/scripts/evaluate_quality_gate.mjs +++ b/.github/scripts/evaluate_quality_gate.mjs @@ -1,7 +1,7 @@ const detected = JSON.parse(process.env.DETECTED ?? '{}'); const needs = JSON.parse(process.env.NEEDS ?? '{}'); -const required = new Set(['secret-scan']); +const required = new Set(); for (const [job, applicable] of Object.entries(detected)) { if (applicable === true) required.add(job); } diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index f31ba95..13a9d99 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -49,6 +49,7 @@ jobs: heavy: ${{ steps.scope.outputs.heavy }} secret_scan: ${{ steps.scope.outputs.secret_scan }} required_contexts: ${{ steps.native.outputs.required_contexts }} + native_required: ${{ steps.native.outputs.required_contexts != '[]' }} source_sha: ${{ steps.resolve-source.outputs.source_sha }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -106,7 +107,7 @@ jobs: node-ci: name: node-ci needs: detect - if: ${{ needs.detect.outputs.node == 'true' && needs.detect.outputs.heavy == 'true' }} + if: ${{ needs.detect.outputs.node == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' }} uses: ./.github/workflows/node-pnpm-ci.yml with: runs-on: ${{ inputs.linux-runs-on }} @@ -116,7 +117,7 @@ jobs: bun-ci: name: bun-ci needs: detect - if: ${{ needs.detect.outputs.bun == 'true' && needs.detect.outputs.heavy == 'true' }} + if: ${{ needs.detect.outputs.bun == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' }} uses: ./.github/workflows/bun-ci.yml with: runs-on: ${{ inputs.linux-runs-on }} @@ -124,7 +125,7 @@ jobs: python-ci: name: python-ci needs: detect - if: ${{ needs.detect.outputs.python == 'true' && needs.detect.outputs.heavy == 'true' }} + if: ${{ needs.detect.outputs.python == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' }} uses: ./.github/workflows/python-ci.yml with: runs-on: ${{ inputs.linux-runs-on }} @@ -134,7 +135,7 @@ jobs: rust-ci: name: rust-ci needs: detect - if: ${{ needs.detect.outputs.rust == 'true' && needs.detect.outputs.heavy == 'true' }} + if: ${{ needs.detect.outputs.rust == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' }} uses: ./.github/workflows/rust-cargo-ci.yml with: runs-on: ${{ inputs.linux-runs-on }} @@ -146,7 +147,7 @@ jobs: # macOS, the org has no self-hosted macOS runner, and GitHub-hosted runners # are blocked for private repos (zero spending limit) — the job would fail # at assignment before running a single step. Public repos keep the gate. - if: ${{ needs.detect.outputs.swift == 'true' && needs.detect.outputs.heavy == 'true' && !github.event.repository.private }} + if: ${{ needs.detect.outputs.swift == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' && !github.event.repository.private }} uses: ./.github/workflows/swift-ci.yml with: runs-on: ${{ inputs.swift-runs-on }} @@ -154,6 +155,8 @@ jobs: secret-scan: name: secret-scan + needs: detect + if: ${{ needs.detect.outputs.native_required != 'true' }} uses: ./.github/workflows/secret-scan.yml with: runs-on: ${{ inputs.linux-runs-on }} @@ -278,7 +281,8 @@ jobs: "bun-ci": ${{ needs.detect.outputs.bun == 'true' && needs.detect.outputs.heavy == 'true' }}, "python-ci": ${{ needs.detect.outputs.python == 'true' && needs.detect.outputs.heavy == 'true' }}, "rust-ci": ${{ needs.detect.outputs.rust == 'true' && needs.detect.outputs.heavy == 'true' }}, - "swift-ci": ${{ needs.detect.outputs.swift == 'true' && needs.detect.outputs.heavy == 'true' && !github.event.repository.private }}, + "swift-ci": ${{ needs.detect.outputs.swift == 'true' && needs.detect.outputs.heavy == 'true' && needs.detect.outputs.native_required != 'true' && !github.event.repository.private }}, + "secret-scan": ${{ needs.detect.outputs.native_required != 'true' }}, "feature-flag-check": ${{ needs.detect.outputs.heavy == 'true' }}, "repository-gates": ${{ needs.detect.outputs.required_contexts != '[]' }}} run: node .quality-gate-source/.github/scripts/evaluate_quality_gate.mjs diff --git a/tests/test_common_required_gates.py b/tests/test_common_required_gates.py index 71831a2..9f6c4c5 100644 --- a/tests/test_common_required_gates.py +++ b/tests/test_common_required_gates.py @@ -201,9 +201,9 @@ def test_non_applicable_job_may_be_skipped(self) -> None: ) self.assertEqual(result.returncode, 0, result.stderr) - def test_secret_scan_is_required_even_when_all_capabilities_are_absent(self) -> None: + def test_secret_scan_is_required_when_selected(self) -> None: result = self.evaluate( - {"deno-ci": False}, + {"deno-ci": False, "secret-scan": True}, { "deno-ci": {"result": "skipped"}, "secret-scan": {"result": "skipped"}, @@ -212,6 +212,16 @@ def test_secret_scan_is_required_even_when_all_capabilities_are_absent(self) -> self.assertNotEqual(result.returncode, 0) self.assertIn("secret-scan", result.stderr) + def test_secret_scan_may_be_skipped_when_native_gate_owns_it(self) -> None: + result = self.evaluate( + {"deno-ci": False, "secret-scan": False}, + { + "deno-ci": {"result": "skipped"}, + "secret-scan": {"result": "skipped"}, + }, + ) + self.assertEqual(result.returncode, 0, result.stderr) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_workflow_wiring.py b/tests/test_workflow_wiring.py index 2c1ab6e..2c71bf8 100644 --- a/tests/test_workflow_wiring.py +++ b/tests/test_workflow_wiring.py @@ -80,6 +80,16 @@ def test_native_gate_payload_excludes_unneeded_check_run_fields(self) -> None: self.assertIn("name: checkRun.name", source) self.assertIn("workflow_path: match ? runIds.get(match[1]) ?? null : null", source) + def test_native_gate_repositories_do_not_run_duplicate_language_or_secret_gates(self) -> None: + source = QUALITY_GATE.read_text() + self.assertIn("native_required: ${{ steps.native.outputs.required_contexts != '[]' }}", source) + self.assertGreaterEqual(source.count("needs.detect.outputs.native_required != 'true'"), 6) + + def test_secret_scan_is_required_only_when_selected(self) -> None: + evaluator = (ROOT / ".github/scripts/evaluate_quality_gate.mjs").read_text() + self.assertIn("const required = new Set();", evaluator) + self.assertNotIn("new Set(['secret-scan'])", evaluator) + if __name__ == "__main__": unittest.main()