Skip to content
Merged
Show file tree
Hide file tree
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 Mar 31, 2026
1dcfd60
Add catalog update automation and workspace validation
SnowboardTechie Mar 31, 2026
33601b4
Add dependency management documentation
SnowboardTechie Mar 31, 2026
9d7ea6e
Fix review findings: website catalog, exit code parsing, self-trigger
SnowboardTechie Mar 31, 2026
6b25562
Add audit step to catalog validation, fix cron schedule comment
SnowboardTechie Mar 31, 2026
f74fd05
fix: audit level, website vitest gate, website tests, catalog drift c…
SnowboardTechie Mar 31, 2026
6d9bdb3
Fix review findings: exit code signaling, docs accuracy, removal guid…
SnowboardTechie Apr 1, 2026
e41e7b2
Document YAML parser assumptions, add workflow_call trigger to catalo…
SnowboardTechie Apr 1, 2026
5737080
Remove pnpm-lock.yaml trigger from individual lib workflows
SnowboardTechie Apr 2, 2026
35c63f0
Clarify exit code semantics for dry-run mode
SnowboardTechie Apr 2, 2026
344c992
Fix workflow self-trigger path in ci-lib-changelog-emitter
SnowboardTechie Apr 2, 2026
0db2af8
fix: use actions v6 and node 22.x in deps-catalog-check workflow
SnowboardTechie Apr 2, 2026
367576e
fix: bump pnpm/action-setup to v5 in deps-catalog-check workflow
SnowboardTechie Apr 2, 2026
62a755c
Distinguish pnpm outdated failures from outdated-deps exit code
SnowboardTechie Apr 2, 2026
56a05d2
fix: clarify catalog workflow schedule with UTC time
SnowboardTechie Apr 2, 2026
07b4fa5
Address PR review feedback: auto-parse catalog deps, daily Dependabot…
SnowboardTechie Apr 6, 2026
f472387
Fix vite/defu audit vulnerabilities and trim script comments
SnowboardTechie Apr 6, 2026
361deb9
Add CI validation for catalog deps in Dependabot ignore list
SnowboardTechie Apr 6, 2026
0b1a400
Address PR #647 review feedback
SnowboardTechie Apr 8, 2026
4891e06
fix: use npm package name in changeset ignore and remove from packages
SnowboardTechie Apr 8, 2026
8286f74
Extract catalog dep validation into standalone script
SnowboardTechie Apr 9, 2026
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
4 changes: 3 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"ignore": [
"typespec-versioning-changelog"
],
"packages": [
"lib/python-sdk",
"lib/core",
Expand Down
155 changes: 155 additions & 0 deletions .github/dependabot.yml
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"
Comment thread
jcrichlake marked this conversation as resolved.
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"
9 changes: 9 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ sdk:
- any-glob-to-any-file:
- lib/python-sdk/**
- lib/ts-sdk/**

dependencies:
- changed-files:
- any-glob-to-any-file:
- pnpm-lock.yaml
- pnpm-workspace.yaml
- "**/package.json"
- "**/pyproject.toml"
- "**/poetry.lock"
148 changes: 148 additions & 0 deletions .github/scripts/update-catalog-deps.sh
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 ==="
58 changes: 58 additions & 0 deletions .github/scripts/validate-catalog-deps.sh
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"
Loading
Loading