Skip to content

chore(deps): bump node from aa27a5f to a1d9d67 in the all-docker group #31

chore(deps): bump node from aa27a5f to a1d9d67 in the all-docker group

chore(deps): bump node from aa27a5f to a1d9d67 in the all-docker group #31

Workflow file for this run

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