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
92 changes: 51 additions & 41 deletions .github/workflows/automerge-dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,62 +1,72 @@
# Auto-merge safe Dependabot pull requests after required checks pass.
# Enables auto-merge (squash) on Dependabot PRs and any PR labelled "automerge".
# Draft PRs are skipped. Small updates (patch and minor) auto-merge on any
# ecosystem. Major updates only auto-merge for GitHub Actions, where CI runs the
# action itself so a breaking bump goes red; major application-dependency bumps
# (npm, pip and the like) are held for manual review, because CI can pass while
# runtime behaviour breaks. CI must still pass before any merge lands.
#
# Rules:
# - Dependabot author only
# - Semantic version patch or minor bump only
# - Uses GitHub auto-merge so required checks are still enforced
# - Deletes source branch after merge
# Branch deletion for merges the token could not clean up is handled by the
# scheduled Repo maintenance workflow. See update-pr-branches.yml for the detail.

name: Dependabot Auto-merge
name: Auto-merge Dependabot

on:
pull_request_target:
types: [opened, reopened, synchronize]
types: [opened, reopened, synchronize, ready_for_review, labeled]

permissions:
contents: write
pull-requests: write

jobs:
automerge:
enable-automerge:
name: Auto-merge safe Dependabot PRs
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest

steps:
# Parse versions from the Dependabot PR title.
# Expected title pattern:
# "... from X.Y.Z to A.B.C ..."
- name: Detect safe semver bump
id: semver
shell: bash
- name: Fetch Dependabot metadata
id: meta
if: github.event.pull_request.user.login == 'dependabot[bot]'
uses: dependabot/fetch-metadata@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge for eligible PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_IS_DRAFT: ${{ github.event.pull_request.draft }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
UPDATE_TYPE: ${{ steps.meta.outputs.update-type }}
ECOSYSTEM: ${{ steps.meta.outputs.package-ecosystem }}
run: |
TITLE='${{ github.event.pull_request.title }}'
if [[ "$TITLE" =~ from[[:space:]]([0-9]+)\.([0-9]+)\.([0-9]+)[[:space:]]to[[:space:]]([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
FROM_MAJOR="${BASH_REMATCH[1]}"
FROM_MINOR="${BASH_REMATCH[2]}"
TO_MAJOR="${BASH_REMATCH[4]}"
TO_MINOR="${BASH_REMATCH[5]}"

# Patch/minor update means major version did not change.
if [[ "$FROM_MAJOR" == "$TO_MAJOR" ]]; then
echo "safe=true" >> "$GITHUB_OUTPUT"
# Never auto-merge drafts - they are explicitly not ready
if [ "$PR_IS_DRAFT" = "true" ]; then
echo "Draft PR - skipping auto-merge setup"
exit 0
fi

# Non-Dependabot PRs opt in with the automerge label
if [ "$PR_AUTHOR" != "dependabot[bot]" ]; then
if echo "$PR_LABELS" | grep -Eq '(^|,)automerge(,|$)'; then
gh pr merge --auto --squash --delete-branch "$PR_URL"
else
echo "safe=false" >> "$GITHUB_OUTPUT"
echo "Auto-merge not enabled - add the automerge label for non-Dependabot PRs"
fi
else
# If the title cannot be parsed safely, skip auto-merge.
echo "safe=false" >> "$GITHUB_OUTPUT"
exit 0
fi

# Enable auto-merge so GitHub merges only after required checks succeed.
- name: Enable auto-merge
if: steps.semver.outputs.safe == 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
gh pr merge "${{ github.event.pull_request.number }}" --auto --squash --delete-branch
# Dependabot: hold major bumps for application dependencies (anything
# that is not GitHub Actions), auto-merge everything else.
if [ "$UPDATE_TYPE" = "version-update:semver-major" ]; then
case "$ECOSYSTEM" in
github_actions|github-actions)
echo "Major GitHub Actions bump - CI runs the action, enabling auto-merge" ;;
*)
echo "Holding major $ECOSYSTEM bump for manual review"
exit 0 ;;
esac
fi

# Log when a PR is intentionally skipped.
- name: Skip unsafe update
if: steps.semver.outputs.safe != 'true'
run: echo "Skipping auto-merge because this is not a safe patch/minor bump."
gh pr merge --auto --squash --delete-branch "$PR_URL"
49 changes: 49 additions & 0 deletions .github/workflows/update-pr-branches.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Deletes merged PR branches that the merge itself failed to clean up.
#
# GitHub does not trigger other workflows' push or pull_request events for actions
# performed with a workflow's own GITHUB_TOKEN (anti-recursion safeguard), so the
# automerge job's squash-merge commits do not reliably fire this on push: main. A
# schedule trigger does not depend on what authored the previous event, so it is
# used as the reliable path; deleting an already-merged branch never pushes a commit
# to an open PR, so it cannot trigger further CI.

name: Repo maintenance

on:
push:
branches: [main]
schedule:
# Every 2 hours - cheap safety net for branch deletion only
- cron: "0 */2 * * *"
workflow_dispatch: {}

permissions:
contents: write
pull-requests: write

jobs:
maintain-branches:
runs-on: ubuntu-latest
steps:
- name: Delete head branches of merged PRs that still exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
# Recently merged PRs - their head branch should have been deleted at
# merge time, but the GITHUB_TOKEN restriction above means it often did not
merged_branches=$(gh api "repos/$REPO/pulls?state=closed&sort=updated&direction=desc&per_page=30" \
--jq '[.[] | select(.merged_at != null and .head.repo.full_name == "'"$REPO"'") | .head.ref] | .[]')

if [ -z "$merged_branches" ]; then
echo "No recently merged PRs found"
exit 0
fi

while read -r branch; do
[ "$branch" = "main" ] && continue
if gh api "repos/$REPO/branches/$branch" >/dev/null 2>&1; then
echo "Deleting stale merged branch: $branch"
gh api -X DELETE "repos/$REPO/git/refs/heads/$branch" || echo "Failed to delete $branch - may already be gone"
fi
done <<< "$merged_branches"
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added

- GitHub Sponsors added to `.github/FUNDING.yml` as first entry; order updated to github, buy_me_a_coffee, patreon
- `Repo maintenance` workflow (`update-pr-branches.yml`) to delete merged PR branches the merge could not clean up

### Changed

- Auto-merge is now ecosystem-aware: patch and minor Dependabot bumps and major GitHub Actions bumps auto-merge once CI passes, but major `npm` and `pip` bumps are held for manual review since a breaking runtime change could pass lint and build yet still deploy; previously major bumps were skipped entirely

## [2.3.0] - 2026-06-03

Expand Down
15 changes: 15 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,18 @@ GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

---

Third-party dependencies

PHAEMOS depends on third-party open-source packages that are not vendored into this
repository but are pulled in at build and run time. They are declared in:

- backend/requirements.txt (Python packages)
- frontend/package.json (npm packages)

Each of those packages is distributed under its own license by its own authors, and those
licenses govern the packages themselves. To produce the full dependency license list, run
`pip-licenses` against the installed Python environment and `license-checker` against the
installed npm environment.
Loading