Skip to content
Merged
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
121 changes: 121 additions & 0 deletions .github/workflows/monthly-dependency-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Monthly Dependency Release

# Opens a patch release PR on the 1st of each month if Dependabot has merged
# any dependency updates to develop since the last tag. The PR bumps the patch
# version — you review, merge to develop, then promote develop→main as normal.
#
# Nothing is auto-merged or auto-tagged. You stay in control of the release.

on:
schedule:
- cron: '0 9 1 * *' # 1st of each month at 09:00 UTC
workflow_dispatch: # manual trigger for testing

permissions:
contents: read

jobs:
open-release-pr:
name: Open patch release PR
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: develop
fetch-depth: 0

- name: Check for dependency commits since last tag
id: check
run: |
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")

if [[ -z "$LAST_TAG" ]]; then
echo "No tags found — skipping"
echo "has_updates=false" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "Last tag: $LAST_TAG"

# Match Dependabot commit messages: "chore: bump/Bump <package>..."
DEP_COUNT=$(git log "${LAST_TAG}..HEAD" --no-merges --format='%s' \
| grep -cEi '^chore(\([^)]+\))?: [Bb]ump ' || true)

echo "Dependency commits since $LAST_TAG: $DEP_COUNT"

if [[ "$DEP_COUNT" -eq 0 ]]; then
echo "Nothing to release — skipping"
echo "has_updates=false" >> "$GITHUB_OUTPUT"
else
echo "has_updates=true" >> "$GITHUB_OUTPUT"
echo "last_tag=$LAST_TAG" >> "$GITHUB_OUTPUT"
echo "dep_count=$DEP_COUNT" >> "$GITHUB_OUTPUT"
fi

- name: Check for existing release PR
id: existing
if: steps.check.outputs.has_updates == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
COUNT=$(gh pr list --base develop --state open \
--search "chore: release" --json number --jq 'length')
if [[ "$COUNT" -gt 0 ]]; then
echo "Open release PR already exists — skipping"
echo "pr_exists=true" >> "$GITHUB_OUTPUT"
else
echo "pr_exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Bump patch version
id: bump
if: steps.check.outputs.has_updates == 'true' && steps.existing.outputs.pr_exists == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Use bump-version.sh which handles VERSION + CHANGELOG atomically.
# The script also creates a local annotated tag — delete it immediately
# since tagging happens after the develop→main PR merges, not here.
./scripts/bump-version.sh patch
NEW_VERSION=$(tr -d '[:space:]' < VERSION)
git tag -d "v${NEW_VERSION}"

echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"

- name: Push branch and open PR
if: steps.check.outputs.has_updates == 'true' && steps.existing.outputs.pr_exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
NEW_VERSION="${{ steps.bump.outputs.new_version }}"
BRANCH="chore/release-v${NEW_VERSION}"
DEP_COUNT="${{ steps.check.outputs.dep_count }}"

git checkout -b "$BRANCH"
git push origin "$BRANCH"

gh pr create \
--base develop \
--head "$BRANCH" \
--title "chore: release v${NEW_VERSION} (monthly dependency patch)" \
--body "$(cat <<EOF
## Monthly dependency patch — v${NEW_VERSION}

This PR was opened automatically. It bumps the patch version to capture
**${DEP_COUNT} Dependabot update(s)** merged to \`develop\` since the last release.

## What to do
1. Review the dependency commits included in this release
2. Merge this PR into \`develop\`
3. Open the \`develop\`→\`main\` release PR (or run \`/publish-release\`)
4. Push the tag to trigger the release pipeline

> **Note:** CI will not auto-run on this PR due to GitHub token restrictions.
> All dependency changes were individually validated by CI when Dependabot
> merged them to \`develop\`. Trigger CI manually if you want an extra check.
EOF
)"
Loading