Skip to content

Commit 268d8da

Browse files
Add release script (#1672)
## Summary - Adds `extras/make_release.sh` to automate and validate the release process - Validates semver, clean working tree, CHANGELOG.md, shard.yml, and openapi.yaml versions - Extracts release notes, opens editor for tag message, creates annotated tag, and pushes with confirmation ## Test plan - [ ] Run `./extras/make_release.sh` with no args — prints usage - [ ] Run `./extras/make_release.sh abc` — rejects invalid semver - [ ] Run `./extras/make_release.sh 99.99.99` — fails on changelog check - [ ] Run with a real version after preparing changelog/versions — creates tag successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent d024c25 commit 268d8da

1 file changed

Lines changed: 77 additions & 0 deletions

File tree

extras/make_release.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
usage() {
5+
echo "Usage: $0 VERSION"
6+
echo " VERSION semver version, e.g. 2.7.0 or 2.7.0-rc.1"
7+
exit 1
8+
}
9+
10+
die() { echo "ERROR: $1" >&2; exit 1; }
11+
12+
[ $# -eq 1 ] || usage
13+
VERSION="$1"
14+
15+
# 1. Validate semver pattern
16+
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || die "Invalid semver: $VERSION"
17+
18+
# 2. Working tree must be clean
19+
git diff --quiet || die "Unstaged changes in working tree"
20+
git diff --cached --quiet || die "Staged uncommitted changes"
21+
22+
# 3. Verify CHANGELOG.md
23+
CHANGELOG="CHANGELOG.md"
24+
[ -f "$CHANGELOG" ] || die "$CHANGELOG not found"
25+
26+
if grep -qP '^## Unreleased' "$CHANGELOG"; then
27+
die "CHANGELOG.md still has an '## Unreleased' section — rename it to ## [$VERSION] before releasing"
28+
fi
29+
30+
if ! grep -qP "^## \[${VERSION//./\\.}\]" "$CHANGELOG"; then
31+
die "CHANGELOG.md has no heading for version $VERSION"
32+
fi
33+
34+
# 4. Verify shard.yml
35+
SHARD="shard.yml"
36+
if ! grep -qP "^version: ${VERSION//./\\.}$" "$SHARD"; then
37+
die "shard.yml version does not match $VERSION"
38+
fi
39+
40+
# 5. Verify openapi.yaml
41+
OPENAPI="static/docs/openapi.yaml"
42+
if ! grep -qP "version: v${VERSION//./\\.}$" "$OPENAPI"; then
43+
die "openapi.yaml version does not match v$VERSION"
44+
fi
45+
46+
# 6. Tag must not already exist
47+
TAG="v$VERSION"
48+
if git rev-parse "$TAG" >/dev/null 2>&1; then
49+
die "Tag $TAG already exists"
50+
fi
51+
52+
# 7. Extract release notes from CHANGELOG.md
53+
ESCAPED_VERSION="${VERSION//./\\.}"
54+
NOTES=$(sed -n "/^## \[$ESCAPED_VERSION\]/,/^## \[/{/^## \[/!p;}" "$CHANGELOG")
55+
[ -n "$NOTES" ] || die "Could not extract release notes for $VERSION"
56+
57+
# 8. Let user edit tag message
58+
TMPFILE=$(mktemp)
59+
trap 'rm -f "$TMPFILE"' EXIT
60+
printf "%s\n\n%s\n" "$TAG" "$NOTES" > "$TMPFILE"
61+
"${EDITOR:-vi}" "$TMPFILE"
62+
63+
# Abort if file is empty after editing
64+
[ -s "$TMPFILE" ] || die "Tag message is empty, aborting"
65+
66+
# 9. Create annotated tag
67+
git tag -a "$TAG" -F "$TMPFILE"
68+
echo "Created tag $TAG"
69+
70+
# 10. Push tag (with confirmation)
71+
read -rp "Push $TAG to origin? [y/N] " REPLY
72+
if [[ "$REPLY" =~ ^[Yy]$ ]]; then
73+
git push origin "$TAG"
74+
echo "Pushed $TAG to origin"
75+
else
76+
echo "Tag $TAG created locally but not pushed"
77+
fi

0 commit comments

Comments
 (0)