diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index ff07f75..08350e8 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -1,16 +1,20 @@ name: Auto-tag # Push-time auto-bump. When a commit lands on main that actually changes -# something user-facing (Sources, Tests, project.yml, bootstrap.sh, the -# release/auto-tag workflows themselves), this fires, bumps the patch part -# of the latest v* tag, and pushes the new tag. The existing Release -# workflow then picks up the tag push and produces a draft release with -# the DMG. +# something user-facing (Sources, Tests, project.yml, bootstrap.sh), this +# fires, inspects conventional-commit subjects since the previous tag to +# decide the bump level, tags, and dispatches the Release workflow. # -# For non-patch bumps (minor / major), use the manual "Bump & Release" -# workflow instead. For docs-only / dependabot / hooks-only commits, the -# paths filter below means this workflow doesn't even start, no tag, no -# release, no inflation. +# Bump rules: +# - any commit with `!` before the `:` (e.g. `feat!:`, `fix(scope)!:`) +# or a `BREAKING CHANGE:` footer -> major bump +# - otherwise any `feat(...):` commit -> minor bump +# - otherwise -> patch bump +# +# The manual "Bump & Release" workflow is still available to force a +# specific bump or cut a release out-of-band. For docs-only / dependabot +# / hooks-only commits the paths filter below means this workflow doesn't +# even start, no tag, no release, no inflation. on: push: @@ -34,7 +38,7 @@ concurrency: jobs: tag: - name: Compute & push patch tag + name: Compute & push tag runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -54,7 +58,7 @@ jobs: echo "skip=false" >> "$GITHUB_OUTPUT" fi - - name: Compute next patch version + - name: Compute next version from conventional commits if: steps.skip.outputs.skip == 'false' id: next run: | @@ -63,11 +67,37 @@ jobs: MAJOR=$(echo "$BASE" | cut -d. -f1) MINOR=$(echo "$BASE" | cut -d. -f2) PATCH=$(echo "$BASE" | cut -d. -f3) - PATCH=$((PATCH + 1)) + + # Commits to inspect: everything new since the previous tag. If + # there is no previous tag we look at the whole history. + if [[ "$LAST" == "v0.0.0" ]]; then + RANGE="HEAD" + else + RANGE="${LAST}..HEAD" + fi + + BUMP="patch" + # `!` before the `:` in the subject, OR a `BREAKING CHANGE:` + # footer anywhere in the message → major. + if git log "$RANGE" --format='%B' \ + | grep -qE '^[a-zA-Z]+(\([^)]+\))?!:|^BREAKING CHANGE:'; then + BUMP="major" + elif git log "$RANGE" --format='%s' \ + | grep -qE '^feat(\([^)]+\))?:'; then + BUMP="minor" + fi + + case "$BUMP" in + major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR + 1)); PATCH=0 ;; + patch) PATCH=$((PATCH + 1)) ;; + esac + NEW="v${MAJOR}.${MINOR}.${PATCH}" - echo "tag=$NEW" >> "$GITHUB_OUTPUT" - echo "previous=$LAST" >> "$GITHUB_OUTPUT" - echo "Bumping $LAST → $NEW (patch, auto)" + echo "tag=$NEW" >> "$GITHUB_OUTPUT" + echo "previous=$LAST" >> "$GITHUB_OUTPUT" + echo "bump=$BUMP" >> "$GITHUB_OUTPUT" + echo "Bumping $LAST → $NEW ($BUMP, auto)" - name: Create and push annotated tag if: steps.skip.outputs.skip == 'false' @@ -102,6 +132,7 @@ jobs: echo "" echo "- Previous: \`${{ steps.next.outputs.previous }}\`" echo "- New: \`${{ steps.next.outputs.tag }}\`" + echo "- Bump: \`${{ steps.next.outputs.bump }}\` (from conventional-commit subjects)" echo "" echo "Release workflow will fire on the tag push and produce a draft GitHub Release with the DMG." fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ef46fe..ba01c70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,6 +90,11 @@ maintainer will usually add the label after a quick eyeball. ## Commit messages +- Follow [Conventional Commits](https://www.conventionalcommits.org/): prefix + the subject with a type (`feat`, `fix`, `chore`, `docs`, `refactor`, `ci`, + `test`, etc.) and optional scope, e.g. `feat(player): add PiP toggle`. The + Auto-tag workflow reads these to pick the bump level — `feat:` → minor, + `!:` or `BREAKING CHANGE:` footer → major, everything else → patch. - Imperative mood: "Add Home tab", not "Added" or "Adds". - Subject ≤ 72 characters; explain *why* in the body, not *what*. - One topic per commit. We don't squash on merge; keep history readable. diff --git a/README.md b/README.md index cbd4800..c7df738 100644 --- a/README.md +++ b/README.md @@ -236,15 +236,17 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for PR conventions and detail. Releases are fully automated. The pipeline: 1. **Auto-tag** (`.github/workflows/auto-tag.yml`): every push to `main` that - touches `Sources/`, `Tests/`, `project.yml`, or `bootstrap.sh` gets a patch - bump (e.g. `v0.1.5 → v0.1.6`). Docs / dependabot / hook / format-config - pushes don't trigger it. + touches `Sources/`, `Tests/`, `project.yml`, or `bootstrap.sh` triggers a + tag. The bump level is derived from the conventional-commit subjects + since the previous tag: any `!:` / `BREAKING CHANGE:` commit → **major**, + otherwise any `feat(...):` commit → **minor**, otherwise → **patch**. + Docs / dependabot / hook / format-config pushes don't trigger it. 2. **Release** (`.github/workflows/release.yml`): fires on `v*` tag pushes, builds the `.app` (Release config), ad-hoc signs it, packages it as a `.dmg` with `create-dmg`, and publishes a GitHub Release. 3. **Bump & Release** (`.github/workflows/bump-release.yml`): manual entry - point (Actions tab → Run workflow) for `minor` / `major` bumps. Patches - are handled by Auto-tag. + point (Actions tab → Run workflow) to force a specific bump or cut a + release out-of-band. Versioning: `CFBundleShortVersionString` is derived from `git describe` (post-build script in `project.yml`): `0.1.0` on a tag, `0.1.0+N` past it.