chore(ci): bump the all-actions group with 3 updates #42
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Combine Dependabot PRs | |
| # Combine all open Dependabot PRs into a single consolidated PR. | |
| # | |
| # Dependabot cannot group dependency updates across ecosystems | |
| # (npm + github-actions + docker each produce their own PR). This | |
| # workflow stitches every open PR labeled `dependencies` into one | |
| # branch and opens a single PR, so reviewers only have to look at | |
| # one diff and CI runs once. | |
| # | |
| # Triggers: | |
| # - pull_request_target → fires right after Dependabot opens a | |
| # `dependencies`-labeled PR. A 2-minute settle delay + concurrency | |
| # cancel ensures that when Dependabot opens 3 PRs in a wave (one | |
| # per ecosystem) only the final run actually performs the combine. | |
| # - workflow_dispatch → run on demand from the Actions tab | |
| # | |
| # Behavior: | |
| # - Picks all open PRs from `app/dependabot` carrying the | |
| # `dependencies` label whose branches still merge cleanly into main. | |
| # - Updates the stable branch `combined-deps/open` (force-with-lease) | |
| # and opens or updates a single PR against it, so repeated runs | |
| # converge on the same PR instead of creating new ones. | |
| # - Skips PRs that cause a merge conflict and reports them in the | |
| # PR body so a human can deal with them individually. | |
| # - Does NOT close the source PRs automatically — merging the | |
| # combined PR will auto-close them because git sees their | |
| # commits as already in main. | |
| # | |
| # Security note on `pull_request_target`: | |
| # The job runs in the base-repo context with write permissions, but | |
| # never checks out PR-supplied code. It only invokes `gh` + `git` on | |
| # PR refs to merge their commits into our branch. No PR-controlled | |
| # script executes here. | |
| # | |
| # Token note: | |
| # PRs created with the default `GITHUB_TOKEN` do NOT trigger downstream | |
| # workflow runs (GitHub anti-recursion). To get CI to start on the | |
| # combined PR automatically, add a fine-grained PAT as repository secret | |
| # `COMBINE_PRS_TOKEN` (scopes: contents:write, pull-requests:write). | |
| # Without that secret the workflow still falls back to `GITHUB_TOKEN`, | |
| # the combined PR will be created, but you'll have to close+reopen it | |
| # once to kick CI. | |
| on: | |
| pull_request_target: | |
| types: [opened, reopened, ready_for_review, labeled] | |
| workflow_dispatch: | |
| inputs: | |
| dry-run: | |
| description: 'List PRs that would be combined without pushing or opening a PR' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| # A wave of Dependabot PRs triggers this workflow N times in ~1-2 minutes. | |
| # Cancel any in-flight run when a new one starts; the last run sees all PRs. | |
| concurrency: | |
| group: combine-prs | |
| cancel-in-progress: true | |
| jobs: | |
| combine: | |
| name: Combine open Dependabot PRs | |
| runs-on: ubuntu-latest | |
| # Only act on Dependabot PRs (or manual dispatch). Without this guard | |
| # every PR open/label event would otherwise trigger the workflow. | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event.pull_request.user.login == 'dependabot[bot]' && | |
| contains(github.event.pull_request.labels.*.name, 'dependencies')) | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Wait for Dependabot wave to settle | |
| if: github.event_name == 'pull_request_target' | |
| run: sleep 120 | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: main | |
| token: ${{ secrets.COMBINE_PRS_TOKEN || secrets.GITHUB_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Combine open Dependabot PRs | |
| env: | |
| GH_TOKEN: ${{ secrets.COMBINE_PRS_TOKEN || secrets.GITHUB_TOKEN }} | |
| DRY_RUN: ${{ inputs.dry-run }} | |
| run: | | |
| set -euo pipefail | |
| DATE=$(date +%Y-%m-%d) | |
| # Stable branch name so repeated runs (Dependabot waves, manual | |
| # dispatches, retries) converge on a single combined PR instead | |
| # of spawning a new one per date. | |
| COMBINED_BRANCH="combined-deps/open" | |
| mapfile -t PRS < <(gh pr list \ | |
| --state open \ | |
| --author "app/dependabot" \ | |
| --label dependencies \ | |
| --json number,title,headRefName \ | |
| --jq '.[] | "\(.number)\t\(.title)\t\(.headRefName)"') | |
| if [ "${#PRS[@]}" -eq 0 ]; then | |
| echo "No open Dependabot PRs labeled 'dependencies' found. Nothing to do." | |
| exit 0 | |
| fi | |
| echo "Found ${#PRS[@]} candidate PR(s):" | |
| printf ' %s\n' "${PRS[@]}" | |
| if [ "${DRY_RUN}" = "true" ]; then | |
| echo "Dry run requested — exiting before any push." | |
| exit 0 | |
| fi | |
| git fetch origin main --quiet | |
| git checkout -B "${COMBINED_BRANCH}" origin/main | |
| MERGED_NUMBERS=() | |
| MERGED_LINES=() | |
| SKIPPED_LINES=() | |
| for entry in "${PRS[@]}"; do | |
| IFS=$'\t' read -r number title head_ref <<<"${entry}" | |
| echo "::group::Merging #${number} (${head_ref})" | |
| git fetch origin "pull/${number}/head:pr-${number}" --quiet | |
| if git merge --no-edit --no-ff -m "Merge #${number}: ${title}" "pr-${number}"; then | |
| MERGED_NUMBERS+=("${number}") | |
| MERGED_LINES+=("- #${number} ${title}") | |
| echo "Merged #${number}" | |
| else | |
| echo "Conflict merging #${number}, aborting that merge." | |
| git merge --abort || true | |
| SKIPPED_LINES+=("- #${number} ${title} _(conflict — must be resolved manually)_") | |
| fi | |
| echo "::endgroup::" | |
| done | |
| if [ "${#MERGED_NUMBERS[@]}" -eq 0 ]; then | |
| echo "No PRs merged cleanly. Nothing to push." | |
| exit 0 | |
| fi | |
| git push --force-with-lease origin "${COMBINED_BRANCH}" | |
| BODY_FILE=$(mktemp) | |
| { | |
| echo "Combined dependency update generated by \`combine-prs.yml\` on ${DATE}." | |
| echo | |
| echo "Merging this PR will auto-close the source Dependabot PRs because" | |
| echo "their commits will be reachable from \`main\`." | |
| echo | |
| echo "### Combined PRs" | |
| printf '%s\n' "${MERGED_LINES[@]}" | |
| if [ "${#SKIPPED_LINES[@]}" -gt 0 ]; then | |
| echo | |
| echo "### Skipped (merge conflict)" | |
| printf '%s\n' "${SKIPPED_LINES[@]}" | |
| fi | |
| } > "${BODY_FILE}" | |
| EXISTING=$(gh pr list \ | |
| --head "${COMBINED_BRANCH}" \ | |
| --state open \ | |
| --json number \ | |
| --jq '.[0].number // empty') | |
| if [ -n "${EXISTING}" ]; then | |
| echo "Updating existing combined PR #${EXISTING}" | |
| gh pr edit "${EXISTING}" \ | |
| --title "chore(deps): combined dependency updates ${DATE}" \ | |
| --body-file "${BODY_FILE}" | |
| else | |
| gh pr create \ | |
| --base main \ | |
| --head "${COMBINED_BRANCH}" \ | |
| --title "chore(deps): combined dependency updates ${DATE}" \ | |
| --label dependencies \ | |
| --body-file "${BODY_FILE}" | |
| fi |