#!/usr/bin/env bash # tag-day.sh — Lumotia tag-day orchestrator # # One command runs the entire morning-of release dance: # 1. Run scripts/pre-tag-verify.sh (all 7 gates must be green) # 2. Read the release version from src-tauri/Cargo.toml (or argv) # 3. Check CHANGELOG.md for the 2026-MM-DD date placeholder — offer to fill # it with today's date, prompt for a custom date, or abort # 4. Print git status + proposed tag command; prompt y/n # 5. git tag -a vX.Y.Z -m "Release vX.Y.Z" + git push origin vX.Y.Z # 6. Watch CI via `gh run watch` if gh CLI is present + authenticated # 7. Print smoke-test next steps + link to tester-onboarding-kit.md # # Usage: # ./scripts/tag-day.sh [vX.Y.Z] # # If the version argument is omitted the script reads it from # [workspace.package].version in Cargo.toml. # # Exit codes: # 0 tag created and pushed (or all steps completed cleanly) # 1 user aborted or a gate failed set -euo pipefail # ── helpers ────────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BOLD='\033[1m' RESET='\033[0m' REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" cd "$REPO_ROOT" info() { printf "${BOLD}%s${RESET}\n" "$*"; } ok() { printf "${GREEN} ✓${RESET} %s\n" "$*"; } warn() { printf "${YELLOW} ⚠${RESET} %s\n" "$*"; } abort() { printf "\n${RED}${BOLD}✗ ABORTED: %s${RESET}\n\n" "$*" >&2; exit 1; } confirm() { # Usage: confirm "prompt text" → returns 0 if user enters y/Y, exits otherwise local prompt="$1" read -r -p "$prompt [y/N] " ans case "$ans" in y|Y) return 0 ;; *) abort "User chose not to proceed." ;; esac } printf "\n${BOLD}╔══════════════════════════════════════════════╗${RESET}\n" printf "${BOLD}║ Lumotia tag-day orchestrator ║${RESET}\n" printf "${BOLD}╚══════════════════════════════════════════════╝${RESET}\n\n" # ── Step 1: pre-tag verification ───────────────────────────────────────────── info "Step 1/7 — Running scripts/pre-tag-verify.sh (all 7 gates must pass)..." VERIFY="$REPO_ROOT/scripts/pre-tag-verify.sh" if [[ ! -f "$VERIFY" ]]; then abort "scripts/pre-tag-verify.sh not found. Restore it before running tag-day." fi if [[ ! -x "$VERIFY" ]]; then abort "scripts/pre-tag-verify.sh is not executable. Run: chmod +x scripts/pre-tag-verify.sh" fi # pre-tag-verify.sh already exits non-zero on any failure; we let it print its # own output so the user sees exactly what failed. if ! bash "$VERIFY"; then abort "pre-tag-verify.sh reported failures — see output above. Fix and re-run tag-day.sh." fi ok "All 7 pre-tag gates passed." # ── Step 2: resolve version ─────────────────────────────────────────────────── info "Step 2/7 — Resolving release version..." if [[ $# -ge 1 && -n "$1" ]]; then VERSION="${1#v}" # strip leading 'v' if supplied TAG="v${VERSION}" ok "Version from argv: ${TAG}" else # Extract from [workspace.package].version in root Cargo.toml VERSION=$(grep -A10 '^\[workspace\.package\]' Cargo.toml \ | grep '^version' \ | head -1 \ | grep -oP '"\K[^"]+' 2>/dev/null || true) if [[ -z "$VERSION" ]]; then abort "Could not parse version from Cargo.toml [workspace.package]. Pass it as an argument: ./scripts/tag-day.sh v0.1.0" fi TAG="v${VERSION}" ok "Version from Cargo.toml: ${TAG}" fi # ── Step 3: CHANGELOG date check ───────────────────────────────────────────── info "Step 3/7 — Checking CHANGELOG.md for date placeholder..." if [[ ! -f CHANGELOG.md ]]; then abort "CHANGELOG.md not found at repo root." fi TODAY="$(date -u +%Y-%m-%d)" if grep -qE '[0-9]{4}-MM-DD' CHANGELOG.md; then printf "\n" warn "CHANGELOG.md still has the date placeholder. Today is ${TODAY}." printf "Replace placeholder with today's date? [y/N/abort] " read -r changelog_ans case "$changelog_ans" in y|Y) sed -i "s/[0-9]\{4\}-MM-DD/${TODAY}/g" CHANGELOG.md ok "Replaced date placeholder in CHANGELOG.md with ${TODAY}." # Verify replacement if grep -qE '[0-9]{4}-MM-DD' CHANGELOG.md; then abort "sed replacement did not remove all placeholders. Check CHANGELOG.md manually." fi ;; n|N) printf " Enter the release date to use (YYYY-MM-DD): " read -r custom_date if [[ ! "$custom_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then abort "Date '${custom_date}' is not in YYYY-MM-DD format." fi sed -i "s/[0-9]\{4\}-MM-DD/${custom_date}/g" CHANGELOG.md ok "Replaced date placeholder in CHANGELOG.md with ${custom_date}." if grep -qE '[0-9]{4}-MM-DD' CHANGELOG.md; then abort "sed replacement did not remove all placeholders. Check CHANGELOG.md manually." fi ;; abort|ABORT|a) abort "User chose to abort at CHANGELOG date step." ;; *) abort "Unrecognised response '${changelog_ans}'. Aborting. Valid responses: y / n / abort" ;; esac else ok "CHANGELOG.md has no date placeholder — ready." fi # ── Step 4: git status + confirm tag command ────────────────────────────────── info "Step 4/7 — Reviewing git status and confirming tag..." printf "\n── git status ───────────────────────────────────────────────────────────\n" git status --short printf "─────────────────────────────────────────────────────────────────────────\n\n" printf "Proposed commands:\n" printf " ${BOLD}git tag -a %s -m \"Release %s\"${RESET}\n" "$TAG" "$TAG" printf " ${BOLD}git push origin %s${RESET}\n\n" "$TAG" # Check if the tag already exists locally if git tag --list "$TAG" | grep -q "$TAG"; then warn "Tag ${TAG} already exists locally." confirm "Delete the existing local tag and re-create it?" git tag -d "$TAG" fi confirm "Confirm: create tag ${TAG} and push to origin?" # ── Step 5: tag + push ──────────────────────────────────────────────────────── info "Step 5/7 — Creating tag and pushing..." git tag -a "$TAG" -m "Release ${TAG}" ok "Tag ${TAG} created locally." git push origin "$TAG" ok "Tag ${TAG} pushed to origin." # ── Step 6: CI watch (optional) ────────────────────────────────────────────── info "Step 6/7 — CI watch..." GH_OK=false if command -v gh &>/dev/null; then if gh auth status &>/dev/null; then GH_OK=true else warn "gh CLI found but not authenticated (gh auth status failed). Skipping CI watch." fi else warn "gh CLI not installed. Skipping CI watch." fi if $GH_OK; then printf "\n Opening CI run for tag ${TAG}...\n" # Wait a moment for the push to register with GitHub sleep 3 # gh run watch will block until the run completes; Ctrl-C to detach. if ! gh run watch --exit-status 2>&1; then warn "CI run watch exited non-zero or was interrupted. Check manually:" printf " https://github.com/jakeadriansames/lumotia/actions\n\n" else ok "CI completed successfully." fi else printf "\n Open this URL to watch CI:\n" printf " ${BOLD}https://github.com/jakeadriansames/lumotia/actions${RESET}\n\n" fi # ── Step 7: next steps ──────────────────────────────────────────────────────── info "Step 7/7 — Smoke-test next steps" printf "\n" printf " Once CI has produced artefacts for all platforms:\n\n" printf " 1. Download the Linux AppImage from the CI release artefacts.\n" printf " 2. Run the smoke harness against it:\n" printf " ${BOLD}./scripts/smoke-linux.sh /path/to/lumotia-%s-linux-x86_64.AppImage${RESET}\n" "$VERSION" printf " 3. Complete the manual cells (Capture, Cleanup, Export, History search).\n" printf " 4. Repeat on macOS / Windows if testers are available.\n" printf " 5. Fill in the smoke-test matrix in docs/release/v0.1-checklist.md.\n\n" printf " Tester onboarding kit:\n" printf " ${BOLD}docs/release/tester-onboarding-kit.md${RESET}\n\n" printf "${GREEN}${BOLD}✓ Tag day complete. Tag %s is live on origin.${RESET}\n\n" "$TAG"