|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +# release-dashboard.sh — Tag and push a new dashboard release |
| 5 | +# |
| 6 | +# Usage: |
| 7 | +# ./scripts/release-dashboard.sh 0.2.2 # release dashboard-v0.2.2 |
| 8 | +# ./scripts/release-dashboard.sh 0.2.2 --dry # preview without committing/pushing |
| 9 | +# |
| 10 | +# What it does: |
| 11 | +# 1. Validates the version is semver |
| 12 | +# 2. Checks for clean working tree |
| 13 | +# 3. Bumps version in tauri.conf.json |
| 14 | +# 4. Runs Rust check (cargo check) |
| 15 | +# 5. Commits the version bump |
| 16 | +# 6. Creates a git tag (dashboard-v0.2.2) |
| 17 | +# 7. Pushes commit + tag to origin |
| 18 | +# 8. Waits for CI to build all platforms |
| 19 | +# 9. Publishes the draft release |
| 20 | +# 10. Adds release notes |
| 21 | + |
| 22 | +VERSION="${1:-}" |
| 23 | +DRY="${2:-}" |
| 24 | + |
| 25 | +if [[ -z "$VERSION" ]]; then |
| 26 | + echo "Usage: ./scripts/release-dashboard.sh <version> [--dry]" |
| 27 | + echo " e.g. ./scripts/release-dashboard.sh 0.2.2" |
| 28 | + exit 1 |
| 29 | +fi |
| 30 | + |
| 31 | +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then |
| 32 | + echo "Error: '$VERSION' is not valid semver (expected X.Y.Z)" |
| 33 | + exit 1 |
| 34 | +fi |
| 35 | + |
| 36 | +TAG="dashboard-v$VERSION" |
| 37 | +TAURI_CONF="packages/dashboard/src-tauri/tauri.conf.json" |
| 38 | + |
| 39 | +# Check if tag already exists |
| 40 | +if git rev-parse "$TAG" >/dev/null 2>&1; then |
| 41 | + echo "Error: tag '$TAG' already exists" |
| 42 | + exit 1 |
| 43 | +fi |
| 44 | + |
| 45 | +# Check for clean working tree |
| 46 | +if [[ -n "$(git status --porcelain)" ]]; then |
| 47 | + echo "Error: working tree is not clean — commit or stash changes first" |
| 48 | + git status --short |
| 49 | + exit 1 |
| 50 | +fi |
| 51 | + |
| 52 | +# Check we're on main/master |
| 53 | +BRANCH=$(git branch --show-current) |
| 54 | +if [[ "$BRANCH" != "main" && "$BRANCH" != "master" ]]; then |
| 55 | + echo "Warning: releasing from '$BRANCH' (not main/master)" |
| 56 | + read -rp "Continue? [y/N] " confirm |
| 57 | + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then |
| 58 | + echo "Aborted." |
| 59 | + exit 1 |
| 60 | + fi |
| 61 | +fi |
| 62 | + |
| 63 | +echo "" |
| 64 | +echo " Releasing Magic Context Dashboard $TAG" |
| 65 | +echo " ───────────────────────────────────────" |
| 66 | +echo "" |
| 67 | + |
| 68 | +# Read current version from tauri.conf.json |
| 69 | +CURRENT_VERSION=$(python3 -c "import json; print(json.load(open('$TAURI_CONF'))['version'])") |
| 70 | +echo " Current version: $CURRENT_VERSION" |
| 71 | +echo " New version: $VERSION" |
| 72 | +echo "" |
| 73 | + |
| 74 | +# Dry run |
| 75 | +if [[ "$DRY" == "--dry" ]]; then |
| 76 | + echo "[DRY RUN] Would:" |
| 77 | + echo " 1. Update $TAURI_CONF version to $VERSION" |
| 78 | + echo " 2. Run cargo check" |
| 79 | + echo " 3. Commit, tag $TAG, push to origin" |
| 80 | + echo " 4. Wait for CI, publish release, add notes" |
| 81 | + exit 0 |
| 82 | +fi |
| 83 | + |
| 84 | +# Step 1: Bump version in tauri.conf.json and update README download link |
| 85 | +echo "→ Bumping version in tauri.conf.json..." |
| 86 | +python3 -c " |
| 87 | +import json |
| 88 | +with open('$TAURI_CONF', 'r') as f: |
| 89 | + conf = json.load(f) |
| 90 | +conf['version'] = '$VERSION' |
| 91 | +with open('$TAURI_CONF', 'w') as f: |
| 92 | + json.dump(conf, f, indent=2) |
| 93 | + f.write('\n') |
| 94 | +" |
| 95 | +echo " ✓ Updated to $VERSION" |
| 96 | + |
| 97 | +echo "→ Updating README download link..." |
| 98 | +sed -i '' "s|releases/tag/dashboard-v[0-9]*\.[0-9]*\.[0-9]*|releases/tag/$TAG|g" README.md |
| 99 | +echo " ✓ README points to $TAG" |
| 100 | +echo "" |
| 101 | + |
| 102 | +# Step 2: Cargo check |
| 103 | +echo "→ Running cargo check..." |
| 104 | +cd packages/dashboard |
| 105 | +cargo check --manifest-path src-tauri/Cargo.toml 2>&1 || { echo "Error: Cargo check failed"; exit 1; } |
| 106 | +cd ../.. |
| 107 | +echo " ✓ Rust compiles" |
| 108 | +echo "" |
| 109 | + |
| 110 | +# Step 3: Commit |
| 111 | +echo "→ Committing version bump..." |
| 112 | +git add -A |
| 113 | +if git diff --cached --quiet; then |
| 114 | + echo " (no changes — version already at $VERSION)" |
| 115 | +else |
| 116 | + git commit -m "dashboard: bump version to $VERSION" |
| 117 | +fi |
| 118 | + |
| 119 | +# Step 4: Tag |
| 120 | +echo "→ Creating tag $TAG..." |
| 121 | +git tag -a "$TAG" -m "Dashboard Release $TAG" |
| 122 | +echo "" |
| 123 | + |
| 124 | +# Step 5: Push |
| 125 | +echo "→ Pushing to origin..." |
| 126 | +git push origin "$BRANCH" |
| 127 | +git push origin "$TAG" |
| 128 | +echo "" |
| 129 | + |
| 130 | +echo " ✓ Tagged and pushed $TAG" |
| 131 | +echo " → CI is now building all platforms" |
| 132 | +echo " → Watch: https://github.com/cortexkit/opencode-magic-context/actions" |
| 133 | +echo "" |
| 134 | + |
| 135 | +# Step 6: Wait for CI |
| 136 | +echo "→ Waiting for CI to create the draft release..." |
| 137 | +echo " (checking every 30s for up to 60 minutes)" |
| 138 | +ATTEMPTS=0 |
| 139 | +MAX_ATTEMPTS=120 |
| 140 | +while [[ $ATTEMPTS -lt $MAX_ATTEMPTS ]]; do |
| 141 | + RELEASE_STATE=$(gh release view "$TAG" --repo cortexkit/opencode-magic-context --json isDraft --jq '.isDraft' 2>/dev/null || echo "not_found") |
| 142 | + |
| 143 | + if [[ "$RELEASE_STATE" == "true" ]]; then |
| 144 | + echo " ✓ Draft release found" |
| 145 | + break |
| 146 | + elif [[ "$RELEASE_STATE" == "false" ]]; then |
| 147 | + echo " ✓ Release already published" |
| 148 | + break |
| 149 | + fi |
| 150 | + |
| 151 | + ATTEMPTS=$((ATTEMPTS + 1)) |
| 152 | + if [[ $((ATTEMPTS % 4)) -eq 0 ]]; then |
| 153 | + echo " ... still waiting ($((ATTEMPTS * 30 / 60))m elapsed)" |
| 154 | + fi |
| 155 | + sleep 30 |
| 156 | +done |
| 157 | + |
| 158 | +if [[ $ATTEMPTS -ge $MAX_ATTEMPTS ]]; then |
| 159 | + echo " ⚠ Timed out waiting for release. Check CI manually." |
| 160 | + echo " → https://github.com/cortexkit/opencode-magic-context/actions" |
| 161 | + exit 0 |
| 162 | +fi |
| 163 | + |
| 164 | +# Step 7: Check all platform assets |
| 165 | +echo "" |
| 166 | +echo "→ Checking release assets..." |
| 167 | +ASSET_COUNT=$(gh release view "$TAG" --repo cortexkit/opencode-magic-context --json assets --jq '.assets | length' 2>/dev/null || echo "0") |
| 168 | +echo " Found $ASSET_COUNT assets" |
| 169 | + |
| 170 | +if [[ "$ASSET_COUNT" -lt 10 ]]; then |
| 171 | + echo " ⚠ Expected ~17 assets (4 platforms × ~4 files + latest.json)" |
| 172 | + echo " → Some platforms may still be building. Wait and check manually." |
| 173 | + read -rp " Publish anyway? [y/N] " confirm |
| 174 | + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then |
| 175 | + echo " Skipping publish. Run manually: gh release edit $TAG --draft=false" |
| 176 | + exit 0 |
| 177 | + fi |
| 178 | +fi |
| 179 | + |
| 180 | +# Step 8: Prompt for release notes |
| 181 | +echo "" |
| 182 | +echo "→ Enter release notes (end with Ctrl-D or empty line):" |
| 183 | +echo " (markdown supported)" |
| 184 | +echo "" |
| 185 | +NOTES="" |
| 186 | +while IFS= read -r line; do |
| 187 | + [[ -z "$line" ]] && break |
| 188 | + NOTES="$NOTES$line |
| 189 | +" |
| 190 | +done |
| 191 | + |
| 192 | +# Step 9: Publish the release |
| 193 | +echo "" |
| 194 | +echo "→ Publishing release..." |
| 195 | +if [[ -n "$NOTES" ]]; then |
| 196 | + gh release edit "$TAG" --repo cortexkit/opencode-magic-context --draft=false --notes "$NOTES" |
| 197 | +else |
| 198 | + gh release edit "$TAG" --repo cortexkit/opencode-magic-context --draft=false |
| 199 | +fi |
| 200 | + |
| 201 | +echo "" |
| 202 | +echo " ✓ Dashboard $TAG released!" |
| 203 | +echo " → https://github.com/cortexkit/opencode-magic-context/releases/tag/$TAG" |
0 commit comments