diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml new file mode 100644 index 0000000..f720d25 --- /dev/null +++ b/.github/workflows/auto-version.yml @@ -0,0 +1,137 @@ +name: Auto version & release + +# Fires on every push to main. Parses commit messages since the +# last semver tag, decides patch/minor/major bump, creates the +# tag, pushes. The tag push then triggers android-release.yml and +# docker-publish.yml. Fully hands-off — you never pick a version +# number; your commit messages do. +# +# Commit message grammar (Conventional Commits): +# feat: → minor bump (new feature, backward-compatible) +# fix: → patch bump (bug fix) +# feat!: / BREAKING CHANGE in body → major bump +# everything else (docs, refactor, chore, style, ci, test) → no bump +# +# Skip conditions (no new release created): +# - No commits match the above patterns +# - The most recent commit is itself a release commit ("Release v…") +# - [skip ci] appears in any commit message since the last tag + +on: + push: + branches: [main] + +permissions: + contents: write + +jobs: + version: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'Release v') && !contains(github.event.head_commit.message, '[skip ci]')" + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Find last semver tag + id: last + run: | + LAST=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1) + if [[ -z "$LAST" ]]; then + LAST="v0.0.0" + echo "no previous tag, starting from v0.0.0" + fi + echo "tag=$LAST" + echo "tag=$LAST" >> "$GITHUB_OUTPUT" + echo "version=${LAST#v}" >> "$GITHUB_OUTPUT" + + - name: Decide bump type from commit messages + id: decide + env: + LAST: ${{ steps.last.outputs.tag }} + run: | + # All commits from the last tag → HEAD (exclusive of tag commit) + if [[ "$LAST" == "v0.0.0" ]]; then + MSGS=$(git log --format='%s%n%b%n---') + else + MSGS=$(git log "${LAST}..HEAD" --format='%s%n%b%n---') + fi + + BUMP=none + if echo "$MSGS" | grep -qE '(^|\n)(BREAKING CHANGE:|[a-z]+(\([^)]+\))?!:)'; then + BUMP=major + elif echo "$MSGS" | grep -qE '(^|\n)feat(\([^)]+\))?: '; then + BUMP=minor + elif echo "$MSGS" | grep -qE '(^|\n)fix(\([^)]+\))?: '; then + BUMP=patch + fi + + echo "Bump type decided: $BUMP" + echo "bump=$BUMP" >> "$GITHUB_OUTPUT" + { + echo "### Commits since $LAST" + echo '```' + if [[ "$LAST" == "v0.0.0" ]]; then + git log --oneline | head -20 + else + git log "${LAST}..HEAD" --oneline + fi + echo '```' + echo "" + echo "**Bump decision**: \`$BUMP\`" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Stop if no release-worthy commits + if: steps.decide.outputs.bump == 'none' + run: | + echo "No feat / fix / BREAKING commits since last tag — not cutting a release." + echo "::notice::No release cut. Commit with 'feat:', 'fix:', or BREAKING CHANGE to trigger one." + + - name: Compute next version + id: next + if: steps.decide.outputs.bump != 'none' + env: + CUR: ${{ steps.last.outputs.version }} + BUMP: ${{ steps.decide.outputs.bump }} + run: | + IFS='.' read -r MAJ MIN PAT <<< "$CUR" + case "$BUMP" in + major) NEXT="$((MAJ+1)).0.0" ;; + minor) NEXT="${MAJ}.$((MIN+1)).0" ;; + patch) NEXT="${MAJ}.${MIN}.$((PAT+1))" ;; + esac + echo "next=$NEXT" >> "$GITHUB_OUTPUT" + echo "### Next version: v$NEXT" >> "$GITHUB_STEP_SUMMARY" + + - name: Configure git + if: steps.decide.outputs.bump != 'none' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Bump version strings + tag + push + if: steps.decide.outputs.bump != 'none' + env: + V: ${{ steps.next.outputs.next }} + run: | + IFS='.' read -r MAJ MIN PAT <<< "$V" + ANDROID_CODE=$(( MAJ * 100000 + MIN * 1000 + PAT )) + + sed -i -E "0,/(\"version\"[[:space:]]*:[[:space:]]*\")[^\"]+(\")/ s//\1${V}\2/" package.json + sed -i -E "0,/(\"version\"[[:space:]]*:[[:space:]]*\")[^\"]+(\")/ s//\1${V}\2/" mobile/package.json + sed -i -E \ + -e "s/versionCode +[0-9]+/versionCode ${ANDROID_CODE}/" \ + -e "s/versionName +\"[^\"]+\"/versionName \"${V}\"/" \ + mobile/android/app/build.gradle + + git add package.json mobile/package.json mobile/android/app/build.gradle + git commit -m "Release v${V}" + git tag -a "v${V}" -m "Release v${V}" + + git push origin HEAD + git push origin "v${V}" + + echo "### Released v$V" >> "$GITHUB_STEP_SUMMARY" + echo "android-release + docker-publish workflows will now run." >> "$GITHUB_STEP_SUMMARY"