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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <version>`, and creates an annotated `v<version>` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`.

## License

Expand Down
81 changes: 72 additions & 9 deletions release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ ROOT="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"

release_usage() {
cat <<'EOF'
usage: ./release.sh <version> [--dry-run] [--push]
usage: ./release.sh <patch|minor|major> [--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
}

Expand All @@ -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 <<EOF
$current
EOF
case "$bump" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
*)
printf 'unknown bump: %s\n' "$bump" >&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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
13 changes: 11 additions & 2 deletions tests/devloop_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down