diff --git a/prepare-release.sh b/prepare-release.sh new file mode 100755 index 0000000..8a40f65 --- /dev/null +++ b/prepare-release.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# prepare-release.sh — prepare a release commit and tag (run on dev machine) +# +# Usage: +# ./prepare-release.sh 1.0.0 "Release notes here..." +# +# This script: +# 1. Writes release notes to releases/v.md +# 2. Updates packageVersion in build.gradle.kts +# 3. Commits both +# 4. Creates and pushes the annotated tag +# +# After this, the Mac operator runs ./release.sh to build and upload. + +set -euo pipefail + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo " Example: $0 1.0.0 'First release with Android and iPhone support'" + exit 1 +fi + +VERSION="$1" +TAG="v${VERSION}" +NOTES="$2" + +# ── Pre-flight ──────────────────────────────────────────────────────────────── +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "ERROR: Uncommitted changes. Commit or stash first." + exit 1 +fi + +if git tag | grep -q "^${TAG}$"; then + echo "ERROR: Tag $TAG already exists." + exit 1 +fi + +# ── Write release notes ─────────────────────────────────────────────────────── +mkdir -p releases +NOTES_FILE="releases/${TAG}.md" +cat > "$NOTES_FILE" << EOF +$NOTES +EOF +echo "→ Wrote $NOTES_FILE" + +# ── Update version ──────────────────────────────────────────────────────────── +sed -i "s/packageVersion = \"[^\"]*\"/packageVersion = \"${VERSION}\"/" build.gradle.kts +echo "→ Updated packageVersion to $VERSION" + +# ── Commit ──────────────────────────────────────────────────────────────────── +git add "$NOTES_FILE" build.gradle.kts +git commit -m "Release $TAG" +echo "→ Committed release files" + +# ── Tag ─────────────────────────────────────────────────────────────────────── +git tag -a "$TAG" -m "$NOTES" +echo "→ Created tag $TAG" + +# ── Push ────────────────────────────────────────────────────────────────────── +echo "→ Pushing to origin..." +GIT_ASKPASS='' GIT_TERMINAL_PROMPT=0 git push origin HEAD:refs/heads/main 2>&1 || \ + echo " (push to main may require running from Mac — tag is local)" +GIT_ASKPASS='' GIT_TERMINAL_PROMPT=0 git push origin "$TAG" 2>&1 || \ + echo " (tag push may require running from Mac)" + +echo "" +echo "✓ Release $TAG prepared." +echo " On your Mac: git fetch --tags && git checkout $TAG && ./release.sh" diff --git a/release.sh b/release.sh index 07c99cc..de81b35 100755 --- a/release.sh +++ b/release.sh @@ -1,113 +1,60 @@ #!/bin/bash # release.sh — build and publish a PhotoPhetch release to Gitea # -# Usage: -# ./release.sh 1.0.0 "First release with Android and iPhone support" -# ./release.sh 1.1.0 # prompts for release notes +# Usage (on your Mac after pulling tagged commit): +# ./release.sh # -# Requires: -# - curl (built into macOS) -# - git -# - ./gradlew (Gradle wrapper in this directory) -# - GITEA_TOKEN environment variable, or it will prompt -# Get a token: https://git.bolenpad.com/user/settings/applications +# The release version and notes are read from the current git tag and +# releases/.md — both committed by the dev machine before tagging. +# +# Requires GITEA_TOKEN environment variable. +# Get a token: https://git.bolenpad.com/user/settings/applications +# Add to ~/.zshrc: export GITEA_TOKEN=your_token_here set -euo pipefail -# ── Config ────────────────────────────────────────────────────────────────── +# ── Config ─────────────────────────────────────────────────────────────────── GITEA_URL="https://git.bolenpad.com" GITEA_USER="kbolen" GITEA_REPO="photophetch" -# ── Arguments ─────────────────────────────────────────────────────────────── -if [ $# -lt 1 ]; then - echo "Usage: $0 [release-notes]" - echo " Example: $0 1.0.0 'First stable release'" +# ── Resolve version from current git tag ───────────────────────────────────── +TAG=$(git describe --exact-match --tags HEAD 2>/dev/null || echo "") +if [ -z "$TAG" ]; then + echo "ERROR: HEAD is not on a tag. Pull the latest and checkout the release tag." + echo " git fetch --tags && git checkout v1.0.0" exit 1 fi -VERSION="$1" -TAG="v${VERSION}" +VERSION="${TAG#v}" # strip leading 'v' +echo "→ Building release $TAG (version $VERSION)" -# Release notes: from argument, or auto-generate from git log -if [ $# -ge 2 ]; then - NOTES="$2" -else - # Auto-generate from git log since last tag (or all commits if no tags yet) - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - if [ -n "$LAST_TAG" ]; then - RAW_LOG=$(git log "${LAST_TAG}..HEAD" --oneline --no-merges) - LOG_RANGE="since $LAST_TAG" - else - RAW_LOG=$(git log --oneline --no-merges | head -30) - LOG_RANGE="all commits" - fi - - echo "" - echo "Auto-generated release notes ($LOG_RANGE):" - echo "──────────────────────────────────────────" - echo "$RAW_LOG" - echo "──────────────────────────────────────────" - echo "" - echo "Accept these as release notes? [y=yes, e=edit, q=quit]" - read -r CHOICE - - case "$CHOICE" in - y|Y) - NOTES="$RAW_LOG" - ;; - e|E) - TMPFILE=$(mktemp /tmp/photophetch-release-notes.XXXXXX.md) - echo "$RAW_LOG" > "$TMPFILE" - ${EDITOR:-nano} "$TMPFILE" - NOTES=$(cat "$TMPFILE") - rm -f "$TMPFILE" - ;; - *) - echo "Aborted." - exit 1 - ;; - esac +# ── Read release notes from releases/.md ──────────────────────────────── +NOTES_FILE="releases/${TAG}.md" +if [ ! -f "$NOTES_FILE" ]; then + echo "ERROR: Release notes file not found: $NOTES_FILE" + echo " The dev machine should have committed this before tagging." + exit 1 fi +NOTES=$(cat "$NOTES_FILE") +echo "→ Release notes: $NOTES_FILE" -# Gitea token +# ── Gitea token ─────────────────────────────────────────────────────────────── if [ -z "${GITEA_TOKEN:-}" ]; then - echo "GITEA_TOKEN not set." - read -rsp "Enter Gitea API token: " GITEA_TOKEN + read -rsp "GITEA_TOKEN not set. Enter token: " GITEA_TOKEN echo fi -# ── Pre-flight checks ──────────────────────────────────────────────────────── -echo "→ Checking for uncommitted changes..." -if ! git diff --quiet || ! git diff --cached --quiet; then - echo "ERROR: Uncommitted changes exist. Commit or stash them first." - exit 1 +# ── Check packageVersion matches tag ───────────────────────────────────────── +GRADLE_VERSION=$(grep 'packageVersion' build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') +if [ "$GRADLE_VERSION" != "$VERSION" ]; then + echo "WARNING: build.gradle.kts packageVersion ($GRADLE_VERSION) != tag version ($VERSION)" + echo "Continue anyway? [y/N]" + read -r CONFIRM + [[ "$CONFIRM" == "y" || "$CONFIRM" == "Y" ]] || exit 1 fi -echo "→ Checking we're on main..." -BRANCH=$(git rev-parse --abbrev-ref HEAD) -if [ "$BRANCH" != "main" ]; then - echo "ERROR: Not on main branch (currently on '$BRANCH'). Switch to main first." - exit 1 -fi - -echo "→ Checking tag $TAG doesn't already exist..." -if git tag | grep -q "^${TAG}$"; then - echo "ERROR: Tag $TAG already exists." - exit 1 -fi - -# ── Update version in build.gradle.kts ────────────────────────────────────── -echo "→ Updating version to $VERSION in build.gradle.kts..." -sed -i.bak "s/packageVersion = \"[^\"]*\"/packageVersion = \"${VERSION}\"/" build.gradle.kts -rm -f build.gradle.kts.bak - -# ── Commit version bump ────────────────────────────────────────────────────── -echo "→ Committing version bump..." -git add build.gradle.kts -git commit -m "Bump version to $VERSION" - -# ── Build DMG ─────────────────────────────────────────────────────────────── +# ── Build DMG ───────────────────────────────────────────────────────────────── echo "→ Building DMG (this takes a few minutes)..." ./gradlew packageDmg @@ -119,16 +66,20 @@ fi DMG_NAME=$(basename "$DMG_PATH") echo "→ Built: $DMG_PATH" -# ── Tag and push ───────────────────────────────────────────────────────────── -echo "→ Creating tag $TAG..." -git tag -a "$TAG" -m "Release $TAG" +# ── Check release doesn't already exist ────────────────────────────────────── +EXISTING=$(curl -s -o /dev/null -w "%{http_code}" \ + "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITEA_TOKEN}") +if [ "$EXISTING" = "200" ]; then + echo "ERROR: Release $TAG already exists on Gitea." + exit 1 +fi -echo "→ Pushing commits and tag..." -git push origin main -git push origin "$TAG" - -# ── Create Gitea release ───────────────────────────────────────────────────── +# ── Create Gitea release ────────────────────────────────────────────────────── echo "→ Creating Gitea release $TAG..." +# Escape notes for JSON +NOTES_JSON=$(echo "$NOTES" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") + RELEASE_RESPONSE=$(curl -s -X POST \ "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases" \ -H "Authorization: token ${GITEA_TOKEN}" \ @@ -136,7 +87,7 @@ RELEASE_RESPONSE=$(curl -s -X POST \ -d "{ \"tag_name\": \"${TAG}\", \"name\": \"PhotoPhetch ${TAG}\", - \"body\": \"${NOTES}\", + \"body\": ${NOTES_JSON}, \"draft\": false, \"prerelease\": false }") @@ -149,7 +100,7 @@ if [ -z "$RELEASE_ID" ]; then fi echo "→ Created release ID $RELEASE_ID" -# ── Upload DMG ─────────────────────────────────────────────────────────────── +# ── Upload DMG ──────────────────────────────────────────────────────────────── echo "→ Uploading $DMG_NAME..." UPLOAD_RESPONSE=$(curl -s -X POST \ "${GITEA_URL}/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=${DMG_NAME}" \ @@ -159,12 +110,10 @@ UPLOAD_RESPONSE=$(curl -s -X POST \ DOWNLOAD_URL=$(echo "$UPLOAD_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('browser_download_url',''))" 2>/dev/null) if [ -z "$DOWNLOAD_URL" ]; then - echo "WARNING: Upload may have failed. Response:" + echo "WARNING: Upload response unexpected:" echo "$UPLOAD_RESPONSE" else + echo "" echo "✓ Released: $DOWNLOAD_URL" + echo " Release page: ${GITEA_URL}/${GITEA_USER}/${GITEA_REPO}/releases/tag/${TAG}" fi - -echo "" -echo "✓ Done! PhotoPhetch $TAG released." -echo " Release page: ${GITEA_URL}/${GITEA_USER}/${GITEA_REPO}/releases/tag/${TAG}" diff --git a/releases/.gitkeep b/releases/.gitkeep new file mode 100644 index 0000000..e69de29