diff --git a/AGENTS.md b/AGENTS.md index d18eacc..9a10010 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ This is a Bash CLI project. The active runtime is the root `devloop` executable. - `bash tests/devloop_test.sh`: run the shell test suite. - `./install.sh`: link `devloop` into `~/.local/bin` or `DEVLOOP_BIN_DIR`. - `./devloop --plain .specs/change.md`: example local CLI invocation from a target git worktree. -- `./release.sh 0.1.0 --dry-run`: validate the release path without changing files. +- `./release.sh patch --dry-run`: validate the release path without changing files. ## Coding Style & Naming Conventions diff --git a/README.md b/README.md index d9e3a58..3e081c7 100644 --- a/README.md +++ b/README.md @@ -164,14 +164,14 @@ Release notes in [`CHANGELOG.md`](CHANGELOG.md) are generated from Conventional brew install git-cliff ``` -Cut a release from a clean tree: +Cut a release from a clean tree by choosing the bump: ```sh -./release.sh 0.1.0 --dry-run -./release.sh 0.1.0 +./release.sh patch --dry-run +./release.sh patch ``` -That updates `VERSION` and [`CHANGELOG.md`](CHANGELOG.md), runs `bash tests/devloop_test.sh`, commits `chore: release 0.1.0`, and creates an annotated `v0.1.0` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`. +Use `patch`, `minor`, or `major`. The script reads the current [`VERSION`](VERSION), computes the next SemVer version, updates `VERSION` and [`CHANGELOG.md`](CHANGELOG.md), runs `bash tests/devloop_test.sh`, commits `chore: release `, and creates an annotated `v` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`. ## License diff --git a/release.sh b/release.sh index f54c256..976874d 100755 --- a/release.sh +++ b/release.sh @@ -15,14 +15,14 @@ ROOT="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)" release_usage() { cat <<'EOF' -usage: ./release.sh [--dry-run] [--push] +usage: ./release.sh [--dry-run] [--push] -Creates a release commit and annotated tag from a SemVer version. +Bumps VERSION, creates a release commit, and creates an annotated tag. Examples: - ./release.sh 0.1.0 --dry-run - ./release.sh 0.1.0 - ./release.sh 0.1.0 --push + ./release.sh patch --dry-run + ./release.sh minor + ./release.sh major --push EOF } @@ -35,6 +35,52 @@ release_tag_for_version() { printf 'v%s\n' "$1" } +release_current_version() { + sed -n '1p' "$ROOT/VERSION" 2>/dev/null || true +} + +release_next_version() { + local bump="$1" + local current="$2" + local major minor patch + + if ! release_version_valid "$current"; then + printf 'invalid current VERSION: %s\n' "$current" >&2 + return 2 + fi + + case "$current" in + *-*|*+*) + printf 'cannot bump prerelease/build VERSION: %s\n' "$current" >&2 + return 2 + ;; + esac + + IFS=. read -r major minor patch <&2 + return 2 + ;; + esac + + printf '%s.%s.%s\n' "$major" "$minor" "$patch" +} + release_require_command() { local command="$1" if command -v "$command" >/dev/null 2>&1; then return 0; fi @@ -74,7 +120,8 @@ release_assert_push_branch() { } release_main() { - local version="" + local bump="" + local current version local dry_run=false local push=false local tag branch @@ -90,16 +137,31 @@ release_main() { return 2 ;; *) - if [ -n "$version" ]; then + if [ -n "$bump" ]; then release_usage >&2 return 2 fi - version="$1" + bump="$1" ;; esac shift done + case "$bump" in + patch|minor|major) ;; + "") + release_usage >&2 + return 2 + ;; + *) + printf 'invalid bump: %s\n' "$bump" >&2 + release_usage >&2 + return 2 + ;; + esac + + current="$(release_current_version)" + version="$(release_next_version "$bump" "$current")" || return $? if [ -z "$version" ]; then release_usage >&2 return 2 @@ -113,7 +175,8 @@ release_main() { release_require_command git if [ "$dry_run" = true ]; then release_assert_tag_available "$tag" - printf 'release: %s\n' "$tag" + printf 'current: %s\n' "$current" + printf 'next: %s (%s)\n' "$version" "$tag" if [ -n "$(git -C "$ROOT" status --porcelain)" ]; then printf '%s\n' "note: actual release requires a clean working tree" fi diff --git a/tests/devloop_test.sh b/tests/devloop_test.sh index 92d64b8..eb67a09 100755 --- a/tests/devloop_test.sh +++ b/tests/devloop_test.sh @@ -305,12 +305,21 @@ ok "pure helpers" if release_version_valid "1.2"; then fail "release version accepted missing patch"; fi if release_version_valid "1.2.3-alpha.01"; then fail "release version accepted leading zero prerelease"; fi equals "$(release_tag_for_version "1.2.3")" "v1.2.3" "release tag" + equals "$(release_next_version patch "0.1.0")" "0.1.1" "patch bump" + equals "$(release_next_version minor "0.1.0")" "0.2.0" "minor bump" + equals "$(release_next_version major "0.1.0")" "1.0.0" "major bump" + if release_next_version patch "0.1.0-alpha.1" >/dev/null 2>&1; then fail "release bump accepted prerelease"; fi release_require_command() { if [ "$1" = "git-cliff" ]; then return 1; fi return 0 } - dry_run_output="$(release_main "9.9.9" --dry-run)" || fail "release dry-run required git-cliff" - contains "$dry_run_output" "would tag: v9.9.9" "release dry-run" + ROOT="$work/release-root" + mkdir -p "$ROOT" + git init -q "$ROOT" + printf '%s\n' "9.9.9" > "$ROOT/VERSION" + dry_run_output="$(release_main "patch" --dry-run)" || fail "release dry-run required git-cliff" + contains "$dry_run_output" "next: 9.9.10 (v9.9.10)" "release dry-run" + contains "$dry_run_output" "would tag: v9.9.10" "release dry-run" ) ok "release helpers"