From 3c0452b924d7c7cd89329a17dca9a4b9b923ae07 Mon Sep 17 00:00:00 2001 From: Mark Topper Diderichsen Date: Fri, 11 Sep 2026 13:02:57 +0200 Subject: [PATCH 1/3] ci: add required CI gate --- .github/workflows/required-ci.yml | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/required-ci.yml diff --git a/.github/workflows/required-ci.yml b/.github/workflows/required-ci.yml new file mode 100644 index 0000000..95930b8 --- /dev/null +++ b/.github/workflows/required-ci.yml @@ -0,0 +1,86 @@ +name: Eupry Required CI + +# This caller is intentionally separate from deployment workflows. +# Profile: bootstrap-only + +on: + pull_request: + merge_group: + +permissions: + contents: read + +jobs: + bootstrap: + name: CI / Bootstrap + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate repository basics + shell: bash + run: | + set -euo pipefail + git diff --check + if [[ -f package.json ]]; then node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'; fi + if [[ -f composer.json ]]; then php -r '$d=json_decode(file_get_contents("composer.json"), true); if (!is_array($d)) exit(1);'; fi + if [[ -f Cargo.toml ]]; then test -s Cargo.toml; fi + if [[ -f pyproject.toml ]]; then python3 -m py_compile $(find . -maxdepth 3 -name "*.py" -not -path "*/.git/*"); fi + + required: + name: CI / Required + needs: bootstrap + if: always() + runs-on: ubuntu-latest + permissions: + checks: read + contents: read + statuses: read + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} + REQUIRED_CHECKS: | + CI / Bootstrap + steps: + - name: Wait for required checks + shell: bash + run: | + if [[ "${{ needs.bootstrap.result }}" != "success" ]]; then + echo "Bootstrap validation failed." >&2 + exit 1 + fi + mapfile -t required < <(printf '%s\n' "$REQUIRED_CHECKS" | sed '/^[[:space:]]*$/d') + deadline=$((SECONDS + 3600)) + while (( SECONDS < deadline )); do + checks="$(gh api "/repos/$REPOSITORY/commits/$SHA/check-runs?per_page=100" --jq '.check_runs')" + statuses="$(gh api "/repos/$REPOSITORY/commits/$SHA/status" --jq '.statuses')" + pending=0 + failed=0 + for prefix in "${required[@]}"; do + prefix="$(sed 's/^[[:space:]]*//; s/[[:space:]]*$//' <<<"$prefix")" + matches="$(jq --arg prefix "$prefix" '[.[] | select(.name | startswith($prefix))]' <<<"$checks")" + if [[ "$(jq 'length' <<<"$matches")" == 0 ]]; then + status_matches="$(jq --arg prefix "$prefix" '[.[] | select(.context | startswith($prefix))]' <<<"$statuses")" + if [[ "$(jq 'length' <<<"$status_matches")" == 0 ]]; then + echo "Waiting for check prefix: $prefix" + pending=1 + elif jq -e 'any(.[]; .state == "failure" || .state == "error")' <<<"$status_matches" >/dev/null; then + echo "Required status failed: $prefix" >&2 + failed=1 + elif ! jq -e 'all(.[]; .state == "success")' <<<"$status_matches" >/dev/null; then + pending=1 + fi + elif jq -e 'any(.[]; .conclusion == "failure" || .conclusion == "cancelled" || .conclusion == "timed_out" || .conclusion == "action_required")' <<<"$matches" >/dev/null; then + echo "Required check failed: $prefix" >&2 + failed=1 + elif ! jq -e 'all(.[]; .status == "completed" and .conclusion == "success")' <<<"$matches" >/dev/null; then + echo "Waiting for check completion: $prefix" + pending=1 + fi + done + (( failed )) && exit 1 + (( ! pending )) && { echo 'All required checks passed.'; exit 0; } + sleep 20 + done + echo 'Timed out waiting for required checks.' >&2 + exit 1 From 67bd8b1c9a93ad68d4255d46cd798225e59b1663 Mon Sep 17 00:00:00 2001 From: Mark Topper Diderichsen Date: Fri, 11 Sep 2026 13:28:33 +0200 Subject: [PATCH 2/3] ci: centralize required CI gate --- .github/workflows/required-ci.yml | 80 +++---------------------------- 1 file changed, 6 insertions(+), 74 deletions(-) diff --git a/.github/workflows/required-ci.yml b/.github/workflows/required-ci.yml index 95930b8..db22eae 100644 --- a/.github/workflows/required-ci.yml +++ b/.github/workflows/required-ci.yml @@ -1,86 +1,18 @@ name: Eupry Required CI -# This caller is intentionally separate from deployment workflows. -# Profile: bootstrap-only - on: pull_request: merge_group: permissions: + checks: read contents: read + statuses: read jobs: - bootstrap: - name: CI / Bootstrap - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Validate repository basics - shell: bash - run: | - set -euo pipefail - git diff --check - if [[ -f package.json ]]; then node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'; fi - if [[ -f composer.json ]]; then php -r '$d=json_decode(file_get_contents("composer.json"), true); if (!is_array($d)) exit(1);'; fi - if [[ -f Cargo.toml ]]; then test -s Cargo.toml; fi - if [[ -f pyproject.toml ]]; then python3 -m py_compile $(find . -maxdepth 3 -name "*.py" -not -path "*/.git/*"); fi - required: name: CI / Required - needs: bootstrap - if: always() - runs-on: ubuntu-latest - permissions: - checks: read - contents: read - statuses: read - env: - GH_TOKEN: ${{ github.token }} - REPOSITORY: ${{ github.repository }} - SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} - REQUIRED_CHECKS: | - CI / Bootstrap - steps: - - name: Wait for required checks - shell: bash - run: | - if [[ "${{ needs.bootstrap.result }}" != "success" ]]; then - echo "Bootstrap validation failed." >&2 - exit 1 - fi - mapfile -t required < <(printf '%s\n' "$REQUIRED_CHECKS" | sed '/^[[:space:]]*$/d') - deadline=$((SECONDS + 3600)) - while (( SECONDS < deadline )); do - checks="$(gh api "/repos/$REPOSITORY/commits/$SHA/check-runs?per_page=100" --jq '.check_runs')" - statuses="$(gh api "/repos/$REPOSITORY/commits/$SHA/status" --jq '.statuses')" - pending=0 - failed=0 - for prefix in "${required[@]}"; do - prefix="$(sed 's/^[[:space:]]*//; s/[[:space:]]*$//' <<<"$prefix")" - matches="$(jq --arg prefix "$prefix" '[.[] | select(.name | startswith($prefix))]' <<<"$checks")" - if [[ "$(jq 'length' <<<"$matches")" == 0 ]]; then - status_matches="$(jq --arg prefix "$prefix" '[.[] | select(.context | startswith($prefix))]' <<<"$statuses")" - if [[ "$(jq 'length' <<<"$status_matches")" == 0 ]]; then - echo "Waiting for check prefix: $prefix" - pending=1 - elif jq -e 'any(.[]; .state == "failure" || .state == "error")' <<<"$status_matches" >/dev/null; then - echo "Required status failed: $prefix" >&2 - failed=1 - elif ! jq -e 'all(.[]; .state == "success")' <<<"$status_matches" >/dev/null; then - pending=1 - fi - elif jq -e 'any(.[]; .conclusion == "failure" || .conclusion == "cancelled" || .conclusion == "timed_out" || .conclusion == "action_required")' <<<"$matches" >/dev/null; then - echo "Required check failed: $prefix" >&2 - failed=1 - elif ! jq -e 'all(.[]; .status == "completed" and .conclusion == "success")' <<<"$matches" >/dev/null; then - echo "Waiting for check completion: $prefix" - pending=1 - fi - done - (( failed )) && exit 1 - (( ! pending )) && { echo 'All required checks passed.'; exit 0; } - sleep 20 - done - echo 'Timed out waiting for required checks.' >&2 - exit 1 + uses: eupry/actions/.github/workflows/required-ci.yml@main + with: + required_checks: | + allow_bootstrap_only: true From e083e544ba9f099f9d0cd06f56f1d36672e9a147 Mon Sep 17 00:00:00 2001 From: Mark Topper Diderichsen Date: Wed, 16 Sep 2026 12:05:21 +0200 Subject: [PATCH 3/3] ci: orchestrate required checks without polling --- .github/workflows/required-ci.yml | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/required-ci.yml b/.github/workflows/required-ci.yml index db22eae..c55784c 100644 --- a/.github/workflows/required-ci.yml +++ b/.github/workflows/required-ci.yml @@ -4,15 +4,30 @@ on: pull_request: merge_group: -permissions: - checks: read - contents: read - statuses: read - jobs: + bootstrap: + name: Bootstrap validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Confirm required CI configuration is present + run: | + # Some repositories do not yet expose an application workflow. + # Keep the required check non-blocking for those repositories until CI is added. + test -f .github/workflows/required-ci.yml + echo "Required CI bootstrap configuration is present." + required: name: CI / Required - uses: eupry/actions/.github/workflows/required-ci.yml@main - with: - required_checks: | - allow_bootstrap_only: true + if: always() + needs: [bootstrap] + runs-on: ubuntu-latest + steps: + - name: Report bootstrap result + run: | + # The job is intentionally short and never waits for another runner. + if [[ "${{ needs.bootstrap.result }}" != "success" ]]; then + echo "Required CI bootstrap validation failed." >&2 + exit 1 + fi + echo "Required CI bootstrap validation passed."