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
48 changes: 39 additions & 9 deletions .github/scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -euo pipefail

usage() {
cat <<'EOF'
Usage: .github/scripts/release.sh <major|minor|patch>
Usage: .github/scripts/release.sh <major|minor|patch|publish>

Example:
.github/scripts/release.sh patch
Expand All @@ -15,8 +15,9 @@ This script:
1) Adds a new release section under [Unreleased] in CHANGELOG.md
2) Bumps VERSION
3) Creates a commit
4) Creates a tag
5) Pushes the tag to origin
4) Leaves the commit on the current feature branch for a PR

After that PR merges, run publish on updated main to tag and push the merged commit.
EOF
}

Expand All @@ -26,13 +27,44 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" || $# -ne 1 ]]; then
fi

bump_type="$1"
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

if [[ "$bump_type" == "publish" ]]; then
if [[ "$(git branch --show-current)" != "main" ]] || [[ -n "$(git status --porcelain)" ]]; then
echo "ERROR: publish requires a clean main checkout." >&2
exit 1
fi
git fetch origin main --tags
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
echo "ERROR: main must match origin/main before publishing." >&2
exit 1
fi
version="$(tr -d '[:space:]' < VERSION)"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || ! grep -Fq "## [$version] - " CHANGELOG.md; then
echo "ERROR: VERSION must match a versioned changelog entry." >&2
exit 1
fi
tag="v${version}"
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
echo "ERROR: tag ${tag} already exists; verify it before retrying." >&2
exit 1
fi
git tag "$tag" HEAD
git push origin "refs/tags/$tag"
echo "Published $tag at $(git rev-parse HEAD)"
exit 0
fi

if [[ "$bump_type" != "major" && "$bump_type" != "minor" && "$bump_type" != "patch" ]]; then
echo "ERROR: argument must be one of: major, minor, patch (got: '$bump_type')."
exit 1
fi

repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
if [[ "$(git branch --show-current)" == "main" ]] || [[ -z "$(git branch --show-current)" ]]; then
echo "ERROR: prepare the release on a feature branch for a PR." >&2
exit 1
fi

if [[ ! -f CHANGELOG.md ]]; then
echo "ERROR: CHANGELOG.md not found at repo root."
Expand Down Expand Up @@ -176,10 +208,8 @@ printf '%s\n' "$version" > VERSION

git add CHANGELOG.md VERSION
git commit -m "chore(release): ${tag}"
git tag "$tag"
git push origin "$tag"

echo "Release prepared."
echo "- Version: ${effective_version} -> ${version} (VERSION file before bump: ${current_version})"
echo "- Tag created and pushed: ${tag}"
echo "- Commit created locally (not pushed): chore(release): ${tag}"
echo "- Commit created locally: chore(release): ${tag}"
echo "- Open a PR to main, then run .github/scripts/release.sh publish on updated main."
34 changes: 34 additions & 0 deletions .github/scripts/tests/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
script="$(cd "$(dirname "$0")/.." && pwd)/release.sh"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
git init --bare -q "$tmp/remote.git"
git init -q -b main "$tmp/work"
cd "$tmp/work"
git config user.email test@example.com
git config user.name Test
git remote add origin "$tmp/remote.git"
printf '1.0.0\n' > VERSION
printf '# Changelog\n\n## [Unreleased]\n\n### Added\n\n- New behavior.\n\n## [1.0.0] - 2026-01-01\n' > CHANGELOG.md
git add VERSION CHANGELOG.md
git commit -qm initial
git push -q -u origin main
git checkout -qb release-test
bash "$script" minor
[[ "$(cat VERSION)" == '1.1.0' ]]
[[ -z "$(git tag -l v1.1.0)" ]]
if bash "$script" publish; then
echo 'Publishing from a feature branch should fail' >&2
exit 1
fi
git checkout -q main
git merge -q --ff-only release-test
git push -q origin main
bash "$script" publish
[[ "$(git rev-parse v1.1.0)" == "$(git rev-parse origin/main)" ]]
[[ "$(git ls-remote origin refs/tags/v1.1.0 | cut -f1)" == "$(git rev-parse HEAD)" ]]
if bash "$script" publish; then
echo 'Publishing an existing tag should fail' >&2
exit 1
fi
8 changes: 8 additions & 0 deletions .github/workflows/test-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ jobs:
persist-credentials: false
- name: Verify compliant and divergent fixtures
run: bash .github/tests/agent-drift.sh "$GITHUB_WORKSPACE/.agent-config-engine/scripts/check-project-agents.py"

release-script-tests:
name: release script tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Verify prepare and publish
run: bash .github/scripts/tests/release.sh
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ All contributors (including maintainers) should update `CHANGELOG.md` when creat

### Changed

- Prepare release versions in a PR and publish the tag only after the release commit is merged.

- Agent rules: migrated `.cursor/rules/*.mdc` to `.claude/rules/*.md` (`globs` → `paths`) and removed the remaining Cursor-specific files and references.

## [4.7.0] - 2026-10-29
Expand Down
32 changes: 5 additions & 27 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,33 +185,11 @@ After opening the PR, maintainers will review and may request changes. Once appr

Releases are made from `main`.

1. **Ensure you're on `main` and up to date**

```bash
git checkout main
git pull origin main
```

2. **Run the release script**

```bash
.github/scripts/release.sh patch
```

- Accepts exactly one bump type: `major`, `minor`, or `patch`
- Computes the next version from the current root `VERSION` file
- Moves current `[Unreleased]` entries under a new section `## [0.2.0] - YYYY-MM-DD`
- Bumps the root `VERSION` file
- Creates commit `chore(release): vX.Y.Z`
- Creates tag `vX.Y.Z` and pushes that tag to `origin`

3. **Push commit to `main`**

```bash
git push origin main
```

4. **Create a GitHub Release** (optional) from the tag and paste the changelog section for that version.
1. Create a feature branch from an up-to-date `main`.
2. Run `.github/scripts/release.sh minor` (or `patch` / `major`) on the feature branch. The script updates the changelog and version and commits them without creating a tag.
3. Push the branch, open a PR to `main`, and merge it after review and checks.
4. Update a clean local `main` to the merged commit, then run `.github/scripts/release.sh publish`. The script checks that `main` matches `origin/main`, creates the version tag on the merged commit, and pushes it.
5. Optionally create a GitHub Release from that tag and include the version's changelog section.

## License & Attribution

Expand Down
Loading