Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
504 changes: 70 additions & 434 deletions .github/dependabot.yml

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions .github/policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,15 @@ required_files:
# Every Go recipe is its own module. go.mod is required for three
# reasons, in increasing order of subtlety:
# 1. Without it the recipe cannot be built, tested, or versioned.
# 2. .github/scripts/generate_dependabot.py detects Go recipes by
# the presence of go.mod (`_is_gomod`), so a recipe without one
# silently receives no dependency updates.
# 2. Dependabot's `gomod` ecosystem keys off go.mod, so a recipe
# without one silently receives no dependency updates. Note that
# go.mod alone is not sufficient today: .github/dependabot.yml
# declares no `gomod` entry yet, so the FIRST Go recipe must add
# one. That is enforced, not left to memory —
# test_dependabot_config.py fails any ecosystem present in the
# tree but missing from the config. The orphan cleanup in
# .github/scripts/recipe_manifests.py detects Go recipes the same
# way (`_is_gomod`).
# 3. .github/workflows/go-format.yml groups changed files by their
# owning go.mod and SKIPS any file it cannot attribute to a
# module — because gci's `localmodule` import section reads
Expand Down
24 changes: 12 additions & 12 deletions .github/scripts/close_orphan_dependabot_prs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@

Where the live set comes from
-----------------------------
The recipe tree, via recipe_manifests.scan() — the same scanner
generate_dependabot.py uses to decide what goes INTO dependabot.yml.
The tree, via recipe_manifests.scan() — NOT .github/dependabot.yml.

This script used to recover the set by regex-scanning the enumerated
`directory:` keys back out of that generated file. Reading the shared scanner
directly is both simpler and safer: rediscovering by parsing meant a
formatting change to the generator's output could silently empty this set,
and an empty set makes every open Dependabot PR look orphaned.
This script used to recover the set by regex-scanning enumerated `directory:`
keys out of dependabot.yml. That file now uses glob patterns (`directories:`)
so that adding a recipe requires no config change, which leaves nothing to
parse: the old parser would have found zero pairs, judged every open
Dependabot PR an orphan, and closed them all with --delete-branch.

It also stops assuming dependabot.yml enumerates directories at all, which
matters the moment that file moves to glob patterns.
Scanning the tree is also the more accurate source. The globs in
dependabot.yml are resolved by Dependabot against the tree, so the tree is
what actually determines which directories are live.

Uses `gh pr close <n> --delete-branch` with no explanatory comment: the GitHub
GraphQL `addComment` mutation has an anti-abuse throttle that trips on large
batches (observed in practice at ~80 comments in a burst). The close+delete-
branch itself is the audit signal; the workflow log enumerates every closed
PR.
branch itself is the audit signal; the workflow log below enumerates every
closed PR.

Invoked by .github/workflows/sync-dependabot-config.yml.
Invoked by .github/workflows/dependabot-housekeeping.yml.

Requires: `gh` on PATH, GITHUB_TOKEN in the environment.

Expand Down
210 changes: 0 additions & 210 deletions .github/scripts/generate_dependabot.py

This file was deleted.

54 changes: 29 additions & 25 deletions .github/scripts/recipe_manifests.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@

Why this exists as its own module
---------------------------------
Two scripts need the same answer, and they must never disagree:

generate_dependabot.py decides what goes INTO .github/dependabot.yml
close_orphan_dependabot_prs.py decides which open Dependabot PRs target a
directory that no longer exists, and closes
them with --delete-branch

The second used to recover the list by regex-scanning the enumerated
`directory:` keys back out of the file the first had written. Re-deriving by
parsing was fragile in a destructive code path: a formatting change to the
generated output could silently empty the set, and an empty set makes every
open Dependabot PR look orphaned.

Sharing the scanner removes that class of bug outright — there is one
definition of "dependency-managed directory", so the generator and the
cleanup cannot drift apart. STATIC_ENTRIES below exists for the same reason,
for the entries that are configured unconditionally rather than discovered.
`.github/dependabot.yml` is static and glob-based — Dependabot resolves those
globs against the tree itself, so nothing here feeds that file.

close_orphan_dependabot_prs.py still needs the concrete list, to decide
whether an open Dependabot PR targets a directory that no longer exists. It
closes what it decides with --delete-branch, so the answer has to be right.

It used to recover the list by regex-scanning the enumerated `directory:`
keys out of dependabot.yml. That stopped being possible the moment the file
switched to globs: the old parser would have found nothing, judged every open
Dependabot PR an orphan, and closed the lot. Scanning the tree is also the
more faithful source, since the tree is what Dependabot resolves those globs
against.

STATIC_ENTRIES below covers the other half — entries configured
unconditionally rather than discovered. Nothing in the tree can turn them up,
so they are listed once here and asserted against the real config by
tests/test_dependabot_config.py. An entry present in dependabot.yml but
missing from that list would have its PRs closed as orphans.

Zero third-party dependencies, matching its callers.
"""
Expand Down Expand Up @@ -112,14 +114,16 @@ def _is_npm(d: Path) -> bool:
# /.github/workflows and any root action.yml, so its directory is always "/"
# and no amount of scanning the recipe tree would turn it up.
#
# Shared for the same reason as the detectors above, and this half is the
# dangerous one. generate_dependabot.py appends these to dependabot.yml;
# close_orphan_dependabot_prs.py adds them to the set of live pairs so their
# PRs are never treated as orphans. When the two lists were written out
# separately, adding a static entry to the generator alone meant the cleanup
# did not recognise it and closed its PRs with --delete-branch — and because
# such an entry produces roughly one grouped PR a week, the --max-close
# circuit breaker would never trip on it.
# close_orphan_dependabot_prs.py folds these into the set of live pairs so
# their PRs are never treated as orphans. Because they cannot be discovered,
# an entry that exists in dependabot.yml but not in this list has its PRs
# closed with --delete-branch — and since such an entry produces roughly one
# grouped PR a week, the --max-close circuit breaker would never trip on it.
# tests/test_dependabot_config.py asserts the two agree, in both directions.
#
# `extra_labels` is carried because dependabot.yml gives github-actions its
# own extra label; keeping it here means the list fully describes the entry
# rather than half of it.
#
# Each tuple is (package-ecosystem, directory, extra_labels).
STATIC_ENTRIES: list[tuple[str, str, list[str]]] = [
Expand Down
Loading
Loading