|
| 1 | +#!/usr/bin/env bash |
| 2 | +# bump-version.sh — single-command version bump across all 4 canonical files. |
| 3 | +# |
| 4 | +# Usage: |
| 5 | +# scripts/bump-version.sh 0.1.27 # bump to 0.1.27 |
| 6 | +# scripts/bump-version.sh --check # verify all files agree |
| 7 | +# scripts/bump-version.sh # print current version |
| 8 | +# |
| 9 | +# Touches: |
| 10 | +# 1. flowctl/crates/flowctl-cli/Cargo.toml (bare semver) |
| 11 | +# 2. .claude-plugin/flowctl-version (v-prefixed) |
| 12 | +# 3. .claude-plugin/plugin.json (bare semver) |
| 13 | +# 4. .claude-plugin/marketplace.json (bare semver × 3) |
| 14 | +# |
| 15 | +# After running: commit + tag v<version> + push to trigger GitHub Release. |
| 16 | + |
| 17 | +set -euo pipefail |
| 18 | + |
| 19 | +ROOT="$(cd "$(dirname "$0")/.." && pwd)" |
| 20 | +cd "$ROOT" |
| 21 | + |
| 22 | +CARGO="flowctl/crates/flowctl-cli/Cargo.toml" |
| 23 | +PIN=".claude-plugin/flowctl-version" |
| 24 | +PLUGIN=".claude-plugin/plugin.json" |
| 25 | +MARKET=".claude-plugin/marketplace.json" |
| 26 | + |
| 27 | +# ── Read current versions ─────────────────────────────────────────── |
| 28 | +current_cargo() { awk -F'"' '/^version = /{print $2; exit}' "$CARGO"; } |
| 29 | +current_pin() { tr -d ' \t\n\r' < "$PIN" | sed 's/^v//'; } |
| 30 | +current_plugin() { python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))["version"])' "$PLUGIN"; } |
| 31 | +current_market() { python3 -c 'import json,sys;d=json.load(open(sys.argv[1]));print(d["version"])' "$MARKET"; } |
| 32 | + |
| 33 | +print_current() { |
| 34 | + printf "%-50s %s\n" "$CARGO" "$(current_cargo)" |
| 35 | + printf "%-50s %s\n" "$PIN" "v$(current_pin)" |
| 36 | + printf "%-50s %s\n" "$PLUGIN" "$(current_plugin)" |
| 37 | + printf "%-50s %s\n" "$MARKET" "$(current_market)" |
| 38 | +} |
| 39 | + |
| 40 | +# ── Check mode ────────────────────────────────────────────────────── |
| 41 | +if [ "${1:-}" = "--check" ]; then |
| 42 | + c="$(current_cargo)"; p="$(current_pin)"; pl="$(current_plugin)"; m="$(current_market)" |
| 43 | + if [ "$c" = "$p" ] && [ "$p" = "$pl" ] && [ "$pl" = "$m" ]; then |
| 44 | + echo "✓ all files agree on v$c" |
| 45 | + exit 0 |
| 46 | + fi |
| 47 | + echo "✗ version drift detected:" >&2 |
| 48 | + print_current >&2 |
| 49 | + exit 1 |
| 50 | +fi |
| 51 | + |
| 52 | +# ── No args: just show ────────────────────────────────────────────── |
| 53 | +if [ $# -eq 0 ]; then |
| 54 | + print_current |
| 55 | + exit 0 |
| 56 | +fi |
| 57 | + |
| 58 | +# ── Bump mode ─────────────────────────────────────────────────────── |
| 59 | +NEW="$1" |
| 60 | +NEW="${NEW#v}" # strip leading v if present |
| 61 | + |
| 62 | +# Validate semver-ish (X.Y.Z, optionally with prerelease) |
| 63 | +if ! printf '%s' "$NEW" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$'; then |
| 64 | + echo "error: invalid version '$NEW' (expected X.Y.Z)" >&2 |
| 65 | + exit 1 |
| 66 | +fi |
| 67 | + |
| 68 | +echo "bumping → v$NEW" |
| 69 | + |
| 70 | +# 1. Cargo.toml — target the flowctl-cli package line only |
| 71 | +# (line starts with `version = "` near the top) |
| 72 | +python3 - "$CARGO" "$NEW" <<'PY' |
| 73 | +import re, sys |
| 74 | +path, new = sys.argv[1], sys.argv[2] |
| 75 | +src = open(path).read() |
| 76 | +# Match first `version = "..."` line (package version, not deps) |
| 77 | +src = re.sub(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new}"', src, count=1) |
| 78 | +open(path, 'w').write(src) |
| 79 | +PY |
| 80 | + |
| 81 | +# 2. flowctl-version — v-prefixed |
| 82 | +printf 'v%s\n' "$NEW" > "$PIN" |
| 83 | + |
| 84 | +# 3. plugin.json — update "version" field at top level |
| 85 | +python3 - "$PLUGIN" "$NEW" <<'PY' |
| 86 | +import json, sys |
| 87 | +path, new = sys.argv[1], sys.argv[2] |
| 88 | +with open(path) as f: |
| 89 | + data = json.load(f) |
| 90 | +data["version"] = new |
| 91 | +with open(path, 'w') as f: |
| 92 | + json.dump(data, f, indent=2, ensure_ascii=False) |
| 93 | + f.write('\n') |
| 94 | +PY |
| 95 | + |
| 96 | +# 4. marketplace.json — update all 3 version fields |
| 97 | +python3 - "$MARKET" "$NEW" <<'PY' |
| 98 | +import json, sys |
| 99 | +path, new = sys.argv[1], sys.argv[2] |
| 100 | +with open(path) as f: |
| 101 | + data = json.load(f) |
| 102 | +data["version"] = new |
| 103 | +data["metadata"]["version"] = new |
| 104 | +for plugin in data.get("plugins", []): |
| 105 | + plugin["version"] = new |
| 106 | +with open(path, 'w') as f: |
| 107 | + json.dump(data, f, indent=2, ensure_ascii=False) |
| 108 | + f.write('\n') |
| 109 | +PY |
| 110 | + |
| 111 | +echo "" |
| 112 | +echo "updated files:" |
| 113 | +print_current |
| 114 | +echo "" |
| 115 | +echo "next steps:" |
| 116 | +echo " cd flowctl && cargo build --release -p flowctl-cli # updates Cargo.lock" |
| 117 | +echo " git add -u && git commit -m \"chore: bump to v$NEW\"" |
| 118 | +echo " git tag v$NEW && git push && git push --tags" |
0 commit comments