-
Notifications
You must be signed in to change notification settings - Fork 8
[Issue #612] Dependabot and catalog dependency update automation #647
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
SnowboardTechie
merged 21 commits into
main
from
612-dependabot-and-catalog-update-automation
Apr 9, 2026
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f1b7894
Add Dependabot config, changelog-emitter to changesets, and dependenc…
SnowboardTechie 1dcfd60
Add catalog update automation and workspace validation
SnowboardTechie 33601b4
Add dependency management documentation
SnowboardTechie 9d7ea6e
Fix review findings: website catalog, exit code parsing, self-trigger
SnowboardTechie 6b25562
Add audit step to catalog validation, fix cron schedule comment
SnowboardTechie f74fd05
fix: audit level, website vitest gate, website tests, catalog drift c…
SnowboardTechie 6d9bdb3
Fix review findings: exit code signaling, docs accuracy, removal guid…
SnowboardTechie e41e7b2
Document YAML parser assumptions, add workflow_call trigger to catalo…
SnowboardTechie 5737080
Remove pnpm-lock.yaml trigger from individual lib workflows
SnowboardTechie 35c63f0
Clarify exit code semantics for dry-run mode
SnowboardTechie 344c992
Fix workflow self-trigger path in ci-lib-changelog-emitter
SnowboardTechie 0db2af8
fix: use actions v6 and node 22.x in deps-catalog-check workflow
SnowboardTechie 367576e
fix: bump pnpm/action-setup to v5 in deps-catalog-check workflow
SnowboardTechie 62a755c
Distinguish pnpm outdated failures from outdated-deps exit code
SnowboardTechie 56a05d2
fix: clarify catalog workflow schedule with UTC time
SnowboardTechie 07b4fa5
Address PR review feedback: auto-parse catalog deps, daily Dependabot…
SnowboardTechie f472387
Fix vite/defu audit vulnerabilities and trim script comments
SnowboardTechie 361deb9
Add CI validation for catalog deps in Dependabot ignore list
SnowboardTechie 0b1a400
Address PR #647 review feedback
SnowboardTechie 4891e06
fix: use npm package name in changeset ignore and remove from packages
SnowboardTechie 8286f74
Extract catalog dep validation into standalone script
SnowboardTechie 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
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
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,155 @@ | ||
| # Automated dependency updates for the CommonGrants monorepo. | ||
| # | ||
| # Split strategy: | ||
| # - Dependabot handles non-catalog deps (templates, examples, Python, Actions, non-catalog workspace deps) | ||
| # - A scheduled GitHub Action (deps-catalog-check.yml) handles catalog-managed deps | ||
| # (avoids dependabot-core pnpm catalog bugs: #14339, #13347, #12244, #12445, #13000) | ||
| # | ||
| # See DEPENDENCY_MANAGEMENT.md for full strategy documentation. | ||
|
|
||
| version: 2 | ||
| updates: | ||
| # ── World A: Root pnpm workspace (shared lockfile) ── | ||
| # Handles only non-catalog deps. Catalog deps (@typespec/*, vitest, etc.) | ||
| # are ignored here and managed by the deps-catalog-check workflow instead. | ||
| - package-ecosystem: "npm" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "daily" | ||
| time: "09:00" | ||
| timezone: "America/New_York" | ||
| groups: | ||
| website-framework: | ||
| patterns: | ||
| - "astro" | ||
| - "@astrojs/*" | ||
| - "@starlight/*" | ||
| minor-patch: | ||
| exclude-patterns: | ||
| - "astro" | ||
| - "@astrojs/*" | ||
| - "@starlight/*" | ||
| update-types: | ||
| - "minor" | ||
| - "patch" | ||
| ignore: | ||
| # Internal workspace deps | ||
| - dependency-name: "@common-grants/*" | ||
| # Catalog-managed deps — handled by scheduled workflow instead | ||
| # (avoids dependabot-core pnpm catalog bugs #14339, #13347, #12244, #12445, #13000) | ||
| - dependency-name: "@typespec/*" | ||
| - dependency-name: "vitest" | ||
| - dependency-name: "@vitest/*" | ||
| - dependency-name: "eslint-plugin-vitest" | ||
| - dependency-name: "@types/node" | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
| labels: | ||
| - "dependencies" | ||
| open-pull-requests-limit: 10 | ||
|
|
||
| # ── World B: Isolated lockfile directories ── | ||
|
|
||
| # templates/express-js (npm, weekly) | ||
| - package-ecosystem: "npm" | ||
| directory: "/templates/express-js" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "tuesday" | ||
| groups: | ||
| all-deps: | ||
| patterns: ["*"] | ||
| ignore: | ||
| - dependency-name: "@common-grants/*" | ||
| labels: ["dependencies"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # templates/quickstart (npm, weekly) | ||
| - package-ecosystem: "npm" | ||
| directory: "/templates/quickstart" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "tuesday" | ||
| groups: | ||
| all-deps: | ||
| patterns: ["*"] | ||
| ignore: | ||
| - dependency-name: "@common-grants/*" | ||
| labels: ["dependencies"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # lib/python-sdk (pip/Poetry, weekly) | ||
| - package-ecosystem: "pip" | ||
| directory: "/lib/python-sdk" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "wednesday" | ||
| labels: ["dependencies", "python"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # templates/fast-api (pip/Poetry, monthly) | ||
| - package-ecosystem: "pip" | ||
| directory: "/templates/fast-api" | ||
| schedule: | ||
| interval: "monthly" | ||
| groups: | ||
| all-deps: | ||
| patterns: ["*"] | ||
| ignore: | ||
| - dependency-name: "common-grants-sdk" | ||
| labels: ["dependencies", "python"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # examples/pa-opportunity-example (pip/Poetry, monthly) | ||
| - package-ecosystem: "pip" | ||
| directory: "/examples/pa-opportunity-example" | ||
| schedule: | ||
| interval: "monthly" | ||
| groups: | ||
| all-deps: | ||
| patterns: ["*"] | ||
| ignore: | ||
| - dependency-name: "common-grants-sdk" | ||
| labels: ["dependencies", "python"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # examples/ca-opportunity-example (pip/Poetry, monthly) | ||
| - package-ecosystem: "pip" | ||
| directory: "/examples/ca-opportunity-example" | ||
| schedule: | ||
| interval: "monthly" | ||
| groups: | ||
| all-deps: | ||
| patterns: ["*"] | ||
| ignore: | ||
| - dependency-name: "common-grants-sdk" | ||
| labels: ["dependencies", "python"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
|
|
||
| # ── World C: GitHub Actions ── | ||
|
|
||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "monday" | ||
| groups: | ||
| actions: | ||
| patterns: ["*"] | ||
| labels: ["dependencies"] | ||
| commit-message: | ||
| prefix: "chore" | ||
| include: "scope" | ||
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
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,148 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Update catalog-managed dependencies to latest versions by modifying | ||
| # pnpm-workspace.yaml version ranges and regenerating the lockfile. | ||
| # These deps are excluded from Dependabot to avoid lockfile corruption | ||
| # (dependabot-core #14339). | ||
| # | ||
| # Usage: | ||
| # ./.github/scripts/update-catalog-deps.sh # Update and regenerate lockfile | ||
| # ./.github/scripts/update-catalog-deps.sh --dry-run # Check only, no changes | ||
| # | ||
| # Exit codes: | ||
| # 0 = updates available (applied unless --dry-run) | ||
| # 1 = error | ||
| # 2 = no updates available | ||
| # | ||
| # Catalog deps are parsed from pnpm-workspace.yaml (both `catalog:` and | ||
| # `catalogs: <name>:` sections). | ||
|
|
||
| DRY_RUN=false | ||
| if [[ "${1:-}" == "--dry-run" ]]; then | ||
| DRY_RUN=true | ||
| fi | ||
|
|
||
| echo "=== Checking for catalog dependency updates ===" | ||
|
|
||
| WORKSPACE_FILE="pnpm-workspace.yaml" | ||
| if [[ ! -f "$WORKSPACE_FILE" ]]; then | ||
| echo "ERROR: $WORKSPACE_FILE not found" | ||
| exit 1 | ||
| fi | ||
|
|
||
| UPDATED_COUNT=0 | ||
|
|
||
| # check_and_update_dep <dep_name> <current_range> <catalog_label> | ||
| # | ||
| # Queries npm for the latest version, compares against the current range, | ||
| # and updates pnpm-workspace.yaml if a newer version is available. | ||
| check_and_update_dep() { | ||
| local dep_name="$1" | ||
| local current_range="$2" | ||
| local catalog_label="$3" | ||
|
|
||
| # Extract prefix (^, ~, or empty) and base version | ||
| local prefix="" | ||
| local base_version="$current_range" | ||
| if [[ "$current_range" == "^"* ]]; then | ||
| prefix="^" | ||
| base_version="${current_range:1}" | ||
| elif [[ "$current_range" == "~"* ]]; then | ||
| prefix="~" | ||
| base_version="${current_range:1}" | ||
| fi | ||
|
|
||
| # Query npm registry for latest version | ||
| local latest | ||
| latest=$(npm view "$dep_name" version 2>/dev/null) || { | ||
| echo " WARNING: Could not fetch latest version for $dep_name, skipping" | ||
| return | ||
| } | ||
|
|
||
| # Compare versions: is latest newer than base_version? | ||
| local newer | ||
| newer=$(printf '%s\n%s\n' "$base_version" "$latest" | sort -V | tail -1) | ||
|
|
||
| if [[ "$latest" != "$base_version" && "$newer" == "$latest" ]]; then | ||
| local new_range="${prefix}${latest}" | ||
| echo " UPDATE ($catalog_label): $dep_name $current_range -> $new_range" | ||
|
|
||
| if [[ "$DRY_RUN" == "false" ]]; then | ||
| # Build sed pattern that matches both quoted and unquoted dep names. | ||
| # Use | as delimiter since dep names contain / | ||
| # Match: ' <dep_name>: <range>' or ' '<dep_name>': <range>' | ||
| # Also handle 4-space indent for named catalogs | ||
| sed -i.bak "s|\\([ ]*\\)\\(['\"]\\{0,1\\}${dep_name}['\"]\\{0,1\\}\\): *${current_range}|\\1\\2: ${new_range}|" "$WORKSPACE_FILE" | ||
| fi | ||
|
|
||
| UPDATED_COUNT=$((UPDATED_COUNT + 1)) | ||
| else | ||
| echo " OK ($catalog_label): $dep_name ${current_range} (latest: $latest)" | ||
| fi | ||
| } | ||
|
|
||
| # --- Process default catalog --- | ||
| echo "" | ||
| echo "--- Checking default catalog deps ---" | ||
|
|
||
| while IFS= read -r line; do | ||
| [[ -z "$line" ]] && continue | ||
| # Parse dep name: strip leading spaces and quotes, extract up to the colon | ||
| dep_name=$(echo "$line" | sed "s/^ //; s/^['\"]//; s/['\"] *:.*//; s/ *:.*//") | ||
| # Parse version range: everything after the colon+space | ||
| current_range=$(echo "$line" | sed "s/^[^:]*: *//") | ||
|
|
||
| check_and_update_dep "$dep_name" "$current_range" "default" | ||
| done < <( | ||
| awk '/^catalog:/{found=1; next} /^[^ ]/{if(found) exit} found && /^ [^ ]/' "$WORKSPACE_FILE" | ||
| ) | ||
|
|
||
| # --- Process named catalogs (e.g. catalogs: website:) --- | ||
| # Extract named catalog sections: lines indented with 4 spaces under a named catalog | ||
| echo "" | ||
| echo "--- Checking named catalog deps ---" | ||
|
|
||
| current_catalog="" | ||
| while IFS= read -r line; do | ||
| # Detect catalog name lines (2-space indent, ends with colon) | ||
| if echo "$line" | grep -qE '^ [a-zA-Z].*:$'; then | ||
| current_catalog=$(echo "$line" | sed 's/^ *//; s/:$//') | ||
| continue | ||
| fi | ||
| # Dep lines are indented with 4 spaces | ||
| if [[ -n "$current_catalog" ]] && echo "$line" | grep -qE '^ [^ ]'; then | ||
| dep_name=$(echo "$line" | sed "s/^ //; s/^['\"]//; s/['\"] *:.*//; s/ *:.*//") | ||
| current_range=$(echo "$line" | sed "s/^[^:]*: *//") | ||
| check_and_update_dep "$dep_name" "$current_range" "$current_catalog" | ||
| fi | ||
| done < <( | ||
| awk '/^catalogs:/{found=1; next} /^[^ ]/{if(found) exit} found' "$WORKSPACE_FILE" | ||
| ) | ||
|
|
||
| # Clean up sed backup files | ||
| rm -f "${WORKSPACE_FILE}.bak" | ||
|
|
||
| echo "" | ||
| if [[ $UPDATED_COUNT -eq 0 ]]; then | ||
| echo "All catalog dependencies are up to date." | ||
| exit 2 | ||
| fi | ||
|
|
||
| echo "$UPDATED_COUNT catalog dep(s) have updates." | ||
|
|
||
| if [[ "$DRY_RUN" == "true" ]]; then | ||
| echo "Dry run: no changes applied." | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "" | ||
| echo "--- Regenerating lockfile ---" | ||
| pnpm install | ||
|
|
||
| echo "" | ||
| echo "--- Summary of changes ---" | ||
| git diff --stat pnpm-workspace.yaml pnpm-lock.yaml 2>/dev/null || echo "(not in a git repo or no changes)" | ||
|
|
||
| echo "" | ||
| echo "=== Catalog dependency update complete ===" |
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,58 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Validate that every catalog-managed dependency in pnpm-workspace.yaml | ||
| # is covered by a matching ignore pattern in .github/dependabot.yml. | ||
| # | ||
| # This prevents Dependabot from opening PRs for catalog deps, which would | ||
| # corrupt the lockfile (dependabot-core #14339). | ||
| # | ||
| # Usage: | ||
| # ./.github/scripts/validate-catalog-deps.sh # Run from repo root | ||
| # | ||
| # Exit codes: | ||
| # 0 = all catalog deps are covered | ||
| # 1 = one or more catalog deps missing from ignore list | ||
|
|
||
| WORKSPACE_FILE="pnpm-workspace.yaml" | ||
| DEPENDABOT_FILE=".github/dependabot.yml" | ||
|
|
||
| if [[ ! -f "$WORKSPACE_FILE" ]]; then | ||
| echo "ERROR: $WORKSPACE_FILE not found (run from repo root)" | ||
| exit 1 | ||
| fi | ||
|
|
||
| if [[ ! -f "$DEPENDABOT_FILE" ]]; then | ||
| echo "ERROR: $DEPENDABOT_FILE not found" | ||
| exit 1 | ||
| fi | ||
|
|
||
| CATALOG_DEPS=$(awk '/^catalog:/{found=1; next} /^[^ ]/{if(found) exit} found && /^ /' "$WORKSPACE_FILE" \ | ||
| | sed "s/^ //; s/^'//; s/':.*//" | sed "s/:.*//" | sort) | ||
|
|
||
| IGNORE_PATTERNS=$(grep 'dependency-name:' "$DEPENDABOT_FILE" \ | ||
| | sed 's/.*dependency-name: *//; s/"//g; s/'"'"'//g' | sort -u) | ||
|
|
||
| uncovered=() | ||
| while IFS= read -r dep; do | ||
| [[ -z "$dep" ]] && continue | ||
| covered=false | ||
| while IFS= read -r pattern; do | ||
| # shellcheck disable=SC2053 | ||
| if [[ "$dep" == $pattern ]]; then | ||
| covered=true | ||
| break | ||
| fi | ||
| done <<< "$IGNORE_PATTERNS" | ||
| if [[ "$covered" == "false" ]]; then | ||
| uncovered+=("$dep") | ||
| fi | ||
| done <<< "$CATALOG_DEPS" | ||
|
|
||
| if [[ ${#uncovered[@]} -gt 0 ]]; then | ||
| echo "::error::Catalog deps not in Dependabot ignore list: ${uncovered[*]}" | ||
| echo "Add them to the ignore: section in $DEPENDABOT_FILE" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "All catalog deps covered by Dependabot ignore list" |
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.