diff --git a/.github/workflows/pr-fast-ci.yml b/.github/workflows/pr-fast-ci.yml index 28f1be6..bac5f11 100644 --- a/.github/workflows/pr-fast-ci.yml +++ b/.github/workflows/pr-fast-ci.yml @@ -73,11 +73,44 @@ jobs: - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh + verify-dependabot-commits: + name: Verify Dependabot-only Commits + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + timeout-minutes: 5 + if: github.event.pull_request.draft == false + outputs: + bot_only: ${{ steps.verify.outputs.bot_only }} + env: + PR_COMMITS_URL: ${{ github.event.pull_request.commits_url }} + GITHUB_TOKEN: ${{ github.token }} + steps: + - id: verify + name: Require every PR commit to be Dependabot + run: | + set -euo pipefail + page=1 + commits_seen=0 + bot_only=true + while :; do + response="$(curl --fail-with-body --silent --show-error --location --header "Authorization: Bearer $GITHUB_TOKEN" --header "Accept: application/vnd.github+json" "${PR_COMMITS_URL}?per_page=100&page=$page")" + jq -e 'type == "array"' <<<"$response" >/dev/null + count="$(jq 'length' <<<"$response")" + [[ "$count" -gt 0 ]] || break + commits_seen=$((commits_seen + count)) + if ! jq -e 'all(.[]; .author != null and .author.login == "dependabot[bot]")' <<<"$response" >/dev/null; then bot_only=false; fi + [[ "$count" -lt 100 ]] && break + page=$((page + 1)) + done + [[ "$commits_seen" -gt 0 ]] || bot_only=false + echo "Verified $commits_seen PR commit(s); Dependabot-only=$bot_only" + echo "bot_only=$bot_only" >> "$GITHUB_OUTPUT" + validate-pr-description: name: Validate PR Description runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 5 - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && needs.verify-dependabot-commits.outputs.bot_only != 'true' + needs: verify-dependabot-commits env: PR_BODY: ${{ github.event.pull_request.body }} steps: @@ -139,7 +172,8 @@ jobs: name: Validate PR Governance runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 5 - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && needs.verify-dependabot-commits.outputs.bot_only != 'true' + needs: verify-dependabot-commits env: PR_TITLE: ${{ github.event.pull_request.title }} PR_BODY: ${{ github.event.pull_request.body }} @@ -176,6 +210,7 @@ jobs: needs: - changes - fast-checks + - verify-dependabot-commits - validate-pr-description - validate-secrets - validate-pr-governance @@ -186,6 +221,7 @@ jobs: RESULTS: >- changes=${{ needs.changes.result }} fast-checks=${{ needs.fast-checks.result }} + verify-dependabot-commits=${{ needs.verify-dependabot-commits.result }} validate-pr-description=${{ needs.validate-pr-description.result }} validate-secrets=${{ needs.validate-secrets.result }} validate-pr-governance=${{ needs.validate-pr-governance.result }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 26d325d..790939a 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -28,7 +28,7 @@ on: jobs: publish: - uses: OMT-Global/bootstrap/.github/workflows/release-publish-reusable.yml@d9c5bc7e50f4bcc97e4b4d3d2efc64e4ab3dca50 + uses: OMT-Global/bootstrap/.github/workflows/release-publish-reusable.yml@05296606785553deb8f36e4f18313076dfc9646d with: tag: ${{ inputs.tag }} channel: ${{ inputs.channel }} diff --git a/src/archetypes.ts b/src/archetypes.ts index 1842dd0..415b23c 100644 --- a/src/archetypes.ts +++ b/src/archetypes.ts @@ -2556,11 +2556,44 @@ ${indentBlock(setupSteps(manifest), 10)} - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh + verify-dependabot-commits: + name: Verify Dependabot-only Commits + runs-on: ${shellRunner} + timeout-minutes: 5 + if: github.event.pull_request.draft == false + outputs: + bot_only: \${{ steps.verify.outputs.bot_only }} + env: + PR_COMMITS_URL: \${{ github.event.pull_request.commits_url }} + GITHUB_TOKEN: \${{ github.token }} + steps: + - id: verify + name: Require every PR commit to be Dependabot + run: | + set -euo pipefail + page=1 + commits_seen=0 + bot_only=true + while :; do + response="$(curl --fail-with-body --silent --show-error --location --header "Authorization: Bearer $GITHUB_TOKEN" --header "Accept: application/vnd.github+json" "\${PR_COMMITS_URL}?per_page=100&page=$page")" + jq -e 'type == "array"' <<<"$response" >/dev/null + count="$(jq 'length' <<<"$response")" + [[ "$count" -gt 0 ]] || break + commits_seen=$((commits_seen + count)) + if ! jq -e 'all(.[]; .author != null and .author.login == "dependabot[bot]")' <<<"$response" >/dev/null; then bot_only=false; fi + [[ "$count" -lt 100 ]] && break + page=$((page + 1)) + done + [[ "$commits_seen" -gt 0 ]] || bot_only=false + echo "Verified $commits_seen PR commit(s); Dependabot-only=$bot_only" + echo "bot_only=$bot_only" >> "$GITHUB_OUTPUT" + validate-pr-description: name: Validate PR Description runs-on: ${shellRunner} timeout-minutes: 5 - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && needs.verify-dependabot-commits.outputs.bot_only != 'true' + needs: verify-dependabot-commits env: PR_BODY: \${{ github.event.pull_request.body }} steps: @@ -2622,7 +2655,8 @@ ${indentBlock(setupSteps(manifest), 10)} name: Validate PR Governance runs-on: ${shellRunner} timeout-minutes: 5 - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && needs.verify-dependabot-commits.outputs.bot_only != 'true' + needs: verify-dependabot-commits env: PR_TITLE: \${{ github.event.pull_request.title }} PR_BODY: \${{ github.event.pull_request.body }} @@ -2659,6 +2693,7 @@ ${indentBlock(setupSteps(manifest), 10)} needs: - changes - fast-checks + - verify-dependabot-commits - validate-pr-description - validate-secrets - validate-pr-governance @@ -2669,6 +2704,7 @@ ${indentBlock(setupSteps(manifest), 10)} RESULTS: >- changes=\${{ needs.changes.result }} fast-checks=\${{ needs.fast-checks.result }} + verify-dependabot-commits=\${{ needs.verify-dependabot-commits.result }} validate-pr-description=\${{ needs.validate-pr-description.result }} validate-secrets=\${{ needs.validate-secrets.result }} validate-pr-governance=\${{ needs.validate-pr-governance.result }} diff --git a/tests/render.test.ts b/tests/render.test.ts index 6dd2b4f..3122146 100644 --- a/tests/render.test.ts +++ b/tests/render.test.ts @@ -66,6 +66,10 @@ describe("renderManagedFiles", () => { expect(prWorkflow?.contents).toContain("validate-pr-governance:"); expect(prWorkflow?.contents).toContain("pull-requests: read"); expect(prWorkflow?.contents).toContain("PR_COMMITS_URL:"); + expect(prWorkflow?.contents).toContain("verify-dependabot-commits:"); + expect(prWorkflow?.contents).toContain("all(.[]; .author != null and .author.login == \"dependabot[bot]\")"); + expect(prWorkflow?.contents).toContain("needs.verify-dependabot-commits.outputs.bot_only != 'true'"); + expect(prWorkflow?.contents).toContain("- verify-dependabot-commits"); expect(files.find((file) => file.path === "scripts/ci/check-pr-governance.sh")?.contents).toContain("PRS-DCO-001"); expect(prWorkflow?.contents).toContain("validate-action-pins:"); expect(files.find((file) => file.path === "scripts/ci/check-action-pins.sh")?.contents).toContain("SA-ACTION-PIN-001");