-
Notifications
You must be signed in to change notification settings - Fork 162
ci: group Dependabot example updates and verify only what changed #844
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bnusunny
merged 27 commits into
main
from
ci/dependabot-grouping-and-targeted-example-verification
Sep 17, 2026
Merged
Changes from 4 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
6f8f472
ci: group Dependabot security updates for the examples
bnusunny 52896af
ci: verify only the examples a pull request touches
bnusunny ccefd41
ci: auto-merge verified example-only Dependabot updates
bnusunny fa41e92
ci: document the verified Dependabot grouping semantics
bnusunny f314573
ci: harden the Dependabot auto-merge guards
bnusunny 33c1cb3
ci: grant actions:read, count jobs safely, cover bundler
bnusunny 38e46fd
ci: make Verify Examples self-verifying and gate on every check
bnusunny 9be72a5
ci: keep both fail-safe paths from aborting instead of refusing
bnusunny 1d2aca2
ci: make one-PR-per-example structural, and classify merge failures
bnusunny 3c1ba63
ci: re-evaluate auto-merge on a schedule, and stop swallowing jq
bnusunny 3a3940f
ci: spell out Dependabot's directories, and three review fixes
bnusunny 1d2c360
ci: four review fixes, and a guard against example config drift
bnusunny b21954d
ci: complete the manifest table, require an open PR, fix a stale comment
bnusunny bb4e344
ci: grant checks/statuses, fail closed on an absent rollup, widen pus…
bnusunny 399f8a5
Merge branch 'main' into ci/dependabot-grouping-and-targeted-example-…
bnusunny 8ee5b00
ci: shorten the group names, and narrow what counts as shared
bnusunny 8a35cb2
ci: fix two ways the drift guard could lie, and refresh a stale header
bnusunny b1670c3
ci: merge after approval, not auto-merge on green
bnusunny f049b3c
ci: let the ruleset decide whether the merge is unattended
bnusunny b39624c
ci: settle on merge-after-approval, and stop floating a ruleset bypass
bnusunny fb69e5d
ci: drop the merge automation, leave Dependabot PRs to their owners
bnusunny 0316c21
ci: fail on a missing matrix key, assert the prefix, say what was ver…
bnusunny e26d40f
ci: assert the matrix entries' shape, and scope the policy keys to ex…
bnusunny 3a96664
ci: no-renames diff, anchor example paths, assert the last two group …
bnusunny 5b7e8b6
ci: catch a duplicated directory, and re-run the guard when its limit…
bnusunny b13d801
ci: derive the kinds from the matrix, normalize paths, lint both temp…
bnusunny 89a1c58
ci: derive the covered set from the matrix, fix the header sim, asser…
bnusunny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # Dependabot configuration for the example applications. | ||
| # | ||
| # The adapter itself (Cargo.toml at the repo root) is deliberately NOT managed here: | ||
| # it ships as the lambda-adapter binary, and its dependencies are reviewed by hand. | ||
| # | ||
| # Why this file exists: without it, Dependabot opens one pull request per advisory | ||
| # per manifest. That produced 69 open PRs, eight of them against the single lockfile | ||
| # in examples/remix/remix-app, which then conflict with each other as soon as one | ||
| # lands. The `groups` blocks below batch each ecosystem's security fixes so an | ||
| # example app is updated by one PR instead of eight. | ||
| # | ||
| # Grouping is per directory: Dependabot treats each (ecosystem, directory) pair as its | ||
| # own update and does not batch across directories unless `group-by: dependency-name` | ||
| # is set, which applies to version updates only. So this yields one pull request per | ||
| # example app per ecosystem, which is the intent — a broken bump then fails one | ||
| # example's verification instead of blocking every example's fixes at once. | ||
| # | ||
| # `open-pull-requests-limit: 0` disables *version* updates and leaves *security* | ||
| # updates on, which keeps the current behavior: Dependabot only opens PRs for | ||
| # advisories, not for every dependency that drifts behind. Security updates are | ||
| # explicitly exempt from this limit and do not count toward it. Raise it per ecosystem | ||
| # if you later want routine version bumps too. | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: npm | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-npm: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
|
||
| - package-ecosystem: pip | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-pip: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
|
||
| - package-ecosystem: gomod | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-gomod: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
|
||
| - package-ecosystem: maven | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-maven: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
|
||
| - package-ecosystem: nuget | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-nuget: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
|
||
| - package-ecosystem: cargo | ||
| directories: | ||
| - "/examples/**" | ||
| schedule: | ||
| interval: weekly | ||
| open-pull-requests-limit: 0 | ||
| commit-message: | ||
| prefix: chore | ||
| groups: | ||
| examples-cargo: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "image": [ | ||
| { "name": "expressjs", "path": "/", "expect_body": "Hi there!" }, | ||
| { "name": "fastapi", "path": "/", "expect_body": "message" }, | ||
| { "name": "fastapi-background-tasks", "path": "/", "expect_body": "message" }, | ||
| { "name": "fasthtml", "path": "/", "expect_body": "Hello World" }, | ||
| { "name": "gin", "path": "/", "expect_body": "message" }, | ||
| { "name": "nextjs", "path": "/", "expect_body": "Next.js Logo" }, | ||
| { "name": "remix", "path": "/", "expect_body": "Welcome to" }, | ||
| { "name": "springboot", "path": "/v1/", "expect_body": "Hello, world!" } | ||
| ], | ||
| "zip": [ | ||
|
bnusunny marked this conversation as resolved.
|
||
| { "name": "deno-zip", "path": "/", "expect_body": "success", "port": "8000" }, | ||
| { "name": "expressjs-zip", "path": "/", "expect_body": "Hi there!", "port": "8000" }, | ||
| { "name": "fastapi-zip", "path": "/", "expect_body": "message", "port": "8000" }, | ||
| { "name": "fasthtml-zip", "path": "/", "expect_body": "Hello World", "port": "8000" }, | ||
| { "name": "flask-zip", "path": "/", "expect_body": "message", "port": "8000" }, | ||
| { "name": "gin-zip", "path": "/", "expect_body": "message", "port": "8000" }, | ||
| { "name": "remix-zip", "path": "/", "expect_body": "Welcome to", "port": "8000" }, | ||
| { "name": "springboot-zip", "path": "/v1/", "expect_body": "Hello, world!", "port": "8000" } | ||
| ], | ||
| "stream": [ | ||
| { "name": "fasthtml-response-streaming", "kind": "image", "path": "/", "expect_body": "Serverless Bedtime" }, | ||
| { "name": "fasthtml-response-streaming-zip", "kind": "zip-fasthtml", "path": "/", "expect_body": "Click to stream" } | ||
| ] | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Picks which examples the Verify Examples workflow needs to run, and writes one | ||
| # matrix per job kind (image, zip, stream) to $GITHUB_OUTPUT. | ||
| # | ||
| # A dependency bump under examples/remix/remix-app has no bearing on springboot or | ||
| # deno-zip, so verifying all 18 matrix entries for it burns runners for no signal. | ||
| # With ~70 open Dependabot PRs against the examples that cost dominates CI. | ||
| # | ||
| # Fails safe: anything this script cannot resolve confidently — no base commit, a | ||
| # base commit not present locally, a change to shared code — verifies everything. | ||
| # | ||
| # Inputs: | ||
| # BASE_SHA base commit to diff against; empty means "verify everything" | ||
| # GITHUB_OUTPUT set by Actions | ||
| set -euo pipefail | ||
|
|
||
| MATRIX="$(dirname "$0")/../example-matrix.json" | ||
|
|
||
| emit_all() { | ||
| local kind | ||
| for kind in image zip stream; do | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| echo "$kind=$(jq -c ".$kind" "$MATRIX")" >>"$GITHUB_OUTPUT" | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| done | ||
| } | ||
|
|
||
| if [[ -z "${BASE_SHA:-}" ]]; then | ||
| echo "No base commit (push or manual run): verifying every example." | ||
| emit_all | ||
| exit 0 | ||
| fi | ||
|
|
||
| if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then | ||
| echo "Base commit $BASE_SHA is not available locally: verifying every example." | ||
| emit_all | ||
| exit 0 | ||
| fi | ||
|
|
||
| # HEAD is the pull request's merge commit, so diffing from the merge base yields | ||
| # exactly the changes the PR contributes. | ||
| base="$(git merge-base "$BASE_SHA" HEAD)" | ||
| changed="$(git diff --name-only "$base" HEAD)" | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| echo "Changed files:" | ||
| echo "$changed" | sed 's/^/ /' | ||
|
|
||
| # Shared inputs every example is built against: the adapter itself, the layer | ||
| # wrapper, this workflow's own machinery. | ||
| if grep -qE '^(src/|layer/|Cargo\.toml$|Cargo\.lock$|\.github/workflows/examples\.yaml$|\.github/scripts/|\.github/example-matrix\.json$)' <<<"$changed"; then | ||
|
bnusunny marked this conversation as resolved.
Outdated
bnusunny marked this conversation as resolved.
Outdated
|
||
| echo "A shared path changed: verifying every example." | ||
| emit_all | ||
| exit 0 | ||
| fi | ||
|
|
||
| # examples/<name>/... -> <name> | ||
| names="$(grep -oE '^examples/[^/]+' <<<"$changed" | cut -d/ -f2 | sort -u | jq -R . | jq -sc .)" | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| echo "Changed examples: $names" | ||
|
|
||
| for kind in image zip stream; do | ||
| matrix="$(jq -c --argjson names "$names" "[.$kind[] | select(.name as \$n | \$names | index(\$n))]" "$MATRIX")" | ||
| echo "$kind=$matrix" | ||
| echo "$kind=$matrix" >>"$GITHUB_OUTPUT" | ||
| done | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| name: Dependabot Auto-merge | ||
|
|
||
| # Merges Dependabot pull requests that only touch example applications, once Verify | ||
| # Examples has actually verified them. | ||
| # | ||
| # Why workflow_run rather than `gh pr merge --auto`: auto-merge is gated on a | ||
| # repository's *required* status checks, and Verify Examples is path-filtered to | ||
| # examples/**. A required check from a path-filtered workflow never reports on pull | ||
| # requests outside those paths, which would block every source-only pull request | ||
| # forever. Keying off the completed run sidesteps that: the run itself is the evidence, | ||
| # and it is tied to the head commit that will be merged. | ||
| # | ||
| # Requires no branch protection and no repository settings. | ||
| on: | ||
| workflow_run: | ||
| workflows: ["Verify Examples"] | ||
| types: | ||
| - completed | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
bnusunny marked this conversation as resolved.
Outdated
bnusunny marked this conversation as resolved.
Outdated
|
||
|
|
||
| jobs: | ||
| merge: | ||
| # Only a green pull request run can merge anything. A push run has no pull request | ||
| # to merge, and a failed run is the whole point of the gate. | ||
| if: >- | ||
| github.event.workflow_run.event == 'pull_request' && | ||
| github.event.workflow_run.conclusion == 'success' | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Merge if this is an example-only Dependabot update that was verified | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| REPO: ${{ github.repository }} | ||
| RUN_ID: ${{ github.event.workflow_run.id }} | ||
| PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| if [[ -z "${PR_NUMBER:-}" ]]; then | ||
| echo "Run is not associated with a pull request; nothing to merge." | ||
| exit 0 | ||
| fi | ||
|
|
||
| author=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json author -q .author.login) | ||
| if [[ "$author" != "app/dependabot" && "$author" != "dependabot[bot]" ]]; then | ||
| echo "PR #$PR_NUMBER is authored by $author, not Dependabot. Skipping." | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Scope by what the PR actually changes rather than by its branch name: | ||
| # grouped updates do not reliably encode the directory in the ref. Examples | ||
| # are demo apps, so a bad bump costs a broken sample; the adapter's own | ||
| # dependencies, the workflows, and the templates stay manual. | ||
| outside=$(gh api --paginate "repos/$REPO/pulls/$PR_NUMBER/files" -q '.[].filename' \ | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| | grep -v '^examples/' || true) | ||
| if [[ -n "$outside" ]]; then | ||
| echo "PR #$PR_NUMBER changes files outside examples/:" | ||
| echo "$outside" | sed 's/^/ /' | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Refuse to merge on template validation alone. If `select` filtered every | ||
| # matrix entry out, the changed example is not covered by a build-and-boot | ||
| # test, and a human should look at it. This is what keeps an uncovered | ||
| # example (nextjs-zip, say) from riding in on a green-but-empty run. | ||
| verified=$(gh api "repos/$REPO/actions/runs/$RUN_ID/jobs" --paginate \ | ||
| -q '[.jobs[] | select(.name | startswith("test-")) | select(.conclusion == "success")] | length') | ||
| if [[ "$verified" -eq 0 ]]; then | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
| echo "No test-* job ran for PR #$PR_NUMBER: the changed example has no" | ||
| echo "build-and-boot coverage, so this needs a human. Skipping." | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "PR #$PR_NUMBER is example-only and passed $verified verification job(s). Merging." | ||
| gh pr merge "$PR_NUMBER" --repo "$REPO" --squash --delete-branch | ||
|
bnusunny marked this conversation as resolved.
Outdated
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.