diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml new file mode 100644 index 0000000..c94d79e --- /dev/null +++ b/.github/workflows/bump.yml @@ -0,0 +1,37 @@ +name: Bump & Release + +on: + workflow_dispatch: + inputs: + bump: + description: 'Override bump type (leave empty to auto-detect)' + required: false + type: choice + options: + - '' + - patch + - minor + - major + +jobs: + bump: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Run release script + run: bash scripts/release.sh ${{ github.event.inputs.bump }} diff --git a/CLAUDE.md b/CLAUDE.md index 3760b39..d831a45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,10 @@ A `Makefile` wraps all common tasks for tool-agnostic usage. Run `make help` to | `make docker-down` | — | Stop containers | | `make docker-logs` | — | Tail container logs | | `make clean` | — | Remove build artifacts + test DBs | +| `make release` | — | Auto-bump version from commits, tag, and push | +| `make release-patch` | — | Bump patch version, tag, and push | +| `make release-minor` | — | Bump minor version, tag, and push | +| `make release-major` | — | Bump major version, tag, and push | ## CI/CD diff --git a/Makefile b/Makefile index 625ace8..5fc24e6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: help dev build preview check test test-unit test-e2e lint \ db-push db-generate db-migrate db-studio docs-api \ - install docker-build docker-up docker-down docker-logs clean + install docker-build docker-up docker-down docker-logs clean \ + release release-patch release-minor release-major help: ## List all targets @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}' @@ -62,3 +63,15 @@ docker-logs: ## Tail container logs clean: ## Remove build artifacts and test DBs rm -rf build .svelte-kit node_modules/.vite rm -f data/test-crumbs.db + +release: ## Auto-bump version from commits, tag, and push + bash scripts/release.sh $(BUMP) + +release-patch: ## Bump patch version, tag, and push + bash scripts/release.sh patch + +release-minor: ## Bump minor version, tag, and push + bash scripts/release.sh minor + +release-major: ## Bump major version, tag, and push + bash scripts/release.sh major diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..dc73cc3 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Automate version bump, tag, and push based on conventional commits. +# Usage: bash scripts/release.sh [patch|minor|major] +# If no argument is given, the bump type is inferred from commits since the last tag. + +BUMP="${1:-}" + +# --- Safety checks --- + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [[ "$BRANCH" != "main" ]]; then + echo "Error: must be on the main branch (currently on '$BRANCH')" >&2 + exit 1 +fi + +if [[ -n "$(git status --porcelain)" ]]; then + echo "Error: working tree is not clean" >&2 + exit 1 +fi + +git fetch origin main --tags --quiet +LOCAL=$(git rev-parse HEAD) +REMOTE=$(git rev-parse origin/main) +if [[ "$LOCAL" != "$REMOTE" ]]; then + echo "Error: local main is not in sync with origin/main" >&2 + echo " local: $LOCAL" >&2 + echo " remote: $REMOTE" >&2 + exit 1 +fi + +# --- Bump type inference --- + +if [[ -z "$BUMP" ]]; then + LAST_TAG=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || echo "") + if [[ -z "$LAST_TAG" ]]; then + echo "No previous v* tag found, defaulting to patch" + BUMP="patch" + else + COMMITS=$(git log "$LAST_TAG"..HEAD --pretty=format:'%s%n%b') + if echo "$COMMITS" | grep -qiE 'BREAKING CHANGE:|^[a-z]+(\(.*\))?!:'; then + BUMP="major" + elif echo "$COMMITS" | grep -qE '^feat(\(.*\))?:'; then + BUMP="minor" + else + BUMP="patch" + fi + fi +fi + +if [[ "$BUMP" != "patch" && "$BUMP" != "minor" && "$BUMP" != "major" ]]; then + echo "Error: invalid bump type '$BUMP' (must be patch, minor, or major)" >&2 + exit 1 +fi + +# --- Bump, tag, push --- + +echo "Bumping $BUMP version..." +npm version "$BUMP" -m "chore: bump version to %s" --no-git-tag-version + +NEW_VERSION=$(node -p "require('./package.json').version") +git add package.json +git commit -m "chore: bump version to $NEW_VERSION" +git tag "v$NEW_VERSION" + +echo "Pushing to origin..." +git push origin main --tags + +echo "" +echo "Released v$NEW_VERSION ($BUMP bump)" +echo "GitHub Actions release workflow will handle the rest."