Files
storkit/script/release
Dave e74c370c7e Improve release changelog and fix MCP port
Generate structured changelogs from completed stories instead of raw
commit messages. Group by features, bug fixes, and refactors. Filter
out story-kit automation commits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:42:48 +00:00

203 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ── Configuration ──────────────────────────────────────────────
GITEA_URL="https://code.crashlabs.io"
REPO="dave/story-kit"
BINARY_NAME="story-kit"
# ── Load .env if present ───────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
if [ -f "${SCRIPT_DIR}/.env" ]; then
set -a
source "${SCRIPT_DIR}/.env"
set +a
fi
# ── Preflight ──────────────────────────────────────────────────
if [ -z "${GITEA_TOKEN:-}" ]; then
echo "Error: GITEA_TOKEN is not set."
echo "Create a token at ${GITEA_URL}/user/settings/applications"
echo "Then add to .env: GITEA_TOKEN=your_token"
exit 1
fi
VERSION="${1:-}"
if [ -z "$VERSION" ]; then
echo "Usage: script/release <version>"
echo "Example: script/release 0.2.0"
exit 1
fi
TAG="v${VERSION}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Error: Tag ${TAG} already exists."
exit 1
fi
if ! command -v cross >/dev/null 2>&1; then
echo "Error: 'cross' is not installed. Run: cargo install cross"
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "Error: Docker is not running. Start Docker Desktop first."
exit 1
fi
echo "==> Releasing ${TAG}"
# ── Build ──────────────────────────────────────────────────────
echo "==> Building macOS (native)..."
cargo build --release
echo "==> Building Linux (static musl via cross)..."
cross build --release --target x86_64-unknown-linux-musl
# ── Package ────────────────────────────────────────────────────
DIST="target/dist"
rm -rf "$DIST"
mkdir -p "$DIST"
cp "target/release/${BINARY_NAME}" "${DIST}/${BINARY_NAME}-macos-arm64"
cp "target/x86_64-unknown-linux-musl/release/${BINARY_NAME}" "${DIST}/${BINARY_NAME}-linux-amd64"
chmod +x "${DIST}"/*
echo "==> Binaries:"
ls -lh "${DIST}"/
# ── Changelog ──────────────────────────────────────────────────
echo "==> Generating changelog..."
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
LOG_RANGE="${PREV_TAG}..HEAD"
RANGE="${PREV_TAG}...${TAG}"
else
LOG_RANGE=""
RANGE="initial...${TAG}"
fi
# Extract completed stories/bugs/refactors from "story-kit: merge <id>" commits.
# Deduplicate (a story may have been merged more than once after reverts).
if [ -n "$LOG_RANGE" ]; then
MERGED_RAW=$(git log "$LOG_RANGE" --pretty=format:"%s" --no-merges \
| grep "^story-kit: merge " | sed 's/^story-kit: merge //' | sort -u)
else
MERGED_RAW=$(git log --pretty=format:"%s" --no-merges \
| grep "^story-kit: merge " | sed 's/^story-kit: merge //' | sort -u)
fi
# Categorise merged work items and format names.
FEATURES=""
FIXES=""
REFACTORS=""
while IFS= read -r item; do
[ -z "$item" ] && continue
# Strip the numeric prefix and type to get the human name.
name=$(echo "$item" | sed -E 's/^[0-9]+_(story|bug|refactor|spike)_//' | tr '_' ' ')
# Capitalise first letter.
name="$(echo "${name:0:1}" | tr '[:lower:]' '[:upper:]')${name:1}"
case "$item" in
*_bug_*) FIXES="${FIXES}- ${name}\n" ;;
*_refactor_*) REFACTORS="${REFACTORS}- ${name}\n" ;;
*) FEATURES="${FEATURES}- ${name}\n" ;;
esac
done <<< "$MERGED_RAW"
# Collect non-automation manual commits (direct fixes, version bumps, etc).
if [ -n "$LOG_RANGE" ]; then
MANUAL=$(git log "$LOG_RANGE" --pretty=format:"%s" --no-merges \
| grep -v "^story-kit: " \
| grep -v "^Revert \"story-kit: " \
| grep -v "^Bump version" \
| sed 's/^/- /')
else
MANUAL=$(git log --pretty=format:"%s" --no-merges \
| grep -v "^story-kit: " \
| grep -v "^Revert \"story-kit: " \
| grep -v "^Bump version" \
| sed 's/^/- /')
fi
# Assemble the release body.
RELEASE_BODY="## What's Changed"
if [ -n "$FEATURES" ]; then
RELEASE_BODY="${RELEASE_BODY}
### Features
$(echo -e "$FEATURES")"
fi
if [ -n "$FIXES" ]; then
RELEASE_BODY="${RELEASE_BODY}
### Bug Fixes
$(echo -e "$FIXES")"
fi
if [ -n "$REFACTORS" ]; then
RELEASE_BODY="${RELEASE_BODY}
### Refactors
$(echo -e "$REFACTORS")"
fi
if [ -n "$MANUAL" ]; then
RELEASE_BODY="${RELEASE_BODY}
### Other Changes
${MANUAL}"
fi
if [ -z "$FEATURES" ] && [ -z "$FIXES" ] && [ -z "$REFACTORS" ] && [ -z "$MANUAL" ]; then
RELEASE_BODY="${RELEASE_BODY}
- No changes since last release"
fi
RELEASE_BODY="${RELEASE_BODY}
**Full diff:** ${GITEA_URL}/${REPO}/compare/${RANGE}"
echo "$RELEASE_BODY"
# ── Tag & Push ─────────────────────────────────────────────────
echo "==> Tagging ${TAG}..."
git tag -a "$TAG" -m "Release ${TAG}"
git push origin "$TAG"
# ── Create Gitea Release ──────────────────────────────────────
echo "==> Creating release on Gitea..."
RELEASE_JSON=$(python3 -c "
import json, sys
print(json.dumps({
'tag_name': sys.argv[1],
'name': sys.argv[1],
'body': sys.argv[2]
}))
" "$TAG" "$RELEASE_BODY")
RELEASE_RESPONSE=$(curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases" \
-d "$RELEASE_JSON")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# ── Upload Binaries ───────────────────────────────────────────
for file in "${DIST}"/*; do
filename=$(basename "$file")
echo "==> Uploading ${filename}..."
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@${file};filename=${filename}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" > /dev/null
done
echo ""
echo "==> Done! Release ${TAG} published:"
echo " ${GITEA_URL}/${REPO}/releases/tag/${TAG}"