shipwright-cli 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright release — Release train automation ║
|
|
4
|
+
# ║ Bump versions, generate changelog, create tags and GitHub releases ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="2.0.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
12
|
+
|
|
13
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
14
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
15
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
16
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
17
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
18
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
19
|
+
RED='\033[38;2;248;113;113m' # error
|
|
20
|
+
DIM='\033[2m'
|
|
21
|
+
BOLD='\033[1m'
|
|
22
|
+
RESET='\033[0m'
|
|
23
|
+
|
|
24
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
25
|
+
# shellcheck source=lib/compat.sh
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
+
|
|
28
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
31
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
32
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
33
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
34
|
+
|
|
35
|
+
emit_event() {
|
|
36
|
+
local event_type="$1"; shift
|
|
37
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
38
|
+
mkdir -p "$(dirname "$events_file")"
|
|
39
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
40
|
+
while [[ $# -gt 0 ]]; do
|
|
41
|
+
local key="${1%%=*}" val="${1#*=}"
|
|
42
|
+
payload="${payload},\"${key}\":\"${val}\""
|
|
43
|
+
shift
|
|
44
|
+
done
|
|
45
|
+
payload="${payload}}"
|
|
46
|
+
echo "$payload" >> "$events_file"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# ─── Parse flags ───────────────────────────────────────────────────────────
|
|
50
|
+
DRY_RUN=false
|
|
51
|
+
VERSION_TYPE=""
|
|
52
|
+
FROM_TAG=""
|
|
53
|
+
TO_TAG="HEAD"
|
|
54
|
+
|
|
55
|
+
for arg in "$@"; do
|
|
56
|
+
case "$arg" in
|
|
57
|
+
--dry-run) DRY_RUN=true ;;
|
|
58
|
+
--major) VERSION_TYPE="major" ;;
|
|
59
|
+
--minor) VERSION_TYPE="minor" ;;
|
|
60
|
+
--patch) VERSION_TYPE="patch" ;;
|
|
61
|
+
--from) shift_next=true ;;
|
|
62
|
+
--from=*) FROM_TAG="${arg#--from=}" ;;
|
|
63
|
+
--to) shift_next=true ;;
|
|
64
|
+
--to=*) TO_TAG="${arg#--to=}" ;;
|
|
65
|
+
*)
|
|
66
|
+
if [[ "${shift_next:-false}" == "true" ]]; then
|
|
67
|
+
case "${prev_arg:-}" in
|
|
68
|
+
--from) FROM_TAG="$arg" ;;
|
|
69
|
+
--to) TO_TAG="$arg" ;;
|
|
70
|
+
esac
|
|
71
|
+
shift_next=false
|
|
72
|
+
fi
|
|
73
|
+
prev_arg="$arg"
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
# ─── Git helpers ───────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
# Get latest tag
|
|
81
|
+
get_latest_tag() {
|
|
82
|
+
git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Parse semantic version (e.g., "v1.2.3" -> major=1, minor=2, patch=3)
|
|
86
|
+
parse_version() {
|
|
87
|
+
local version="$1"
|
|
88
|
+
# Strip leading 'v' if present
|
|
89
|
+
version="${version#v}"
|
|
90
|
+
|
|
91
|
+
IFS='.' read -r major minor patch <<< "$version"
|
|
92
|
+
|
|
93
|
+
echo "$major|$minor|${patch:-0}"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Compare semantic versions
|
|
97
|
+
# Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
|
98
|
+
compare_versions() {
|
|
99
|
+
local v1="$1" v2="$2"
|
|
100
|
+
|
|
101
|
+
IFS='|' read -r m1 mi1 p1 <<< "$(parse_version "$v1")"
|
|
102
|
+
IFS='|' read -r m2 mi2 p2 <<< "$(parse_version "$v2")"
|
|
103
|
+
|
|
104
|
+
if [[ $m1 -lt $m2 ]]; then echo -1; return; fi
|
|
105
|
+
if [[ $m1 -gt $m2 ]]; then echo 1; return; fi
|
|
106
|
+
if [[ $mi1 -lt $mi2 ]]; then echo -1; return; fi
|
|
107
|
+
if [[ $mi1 -gt $mi2 ]]; then echo 1; return; fi
|
|
108
|
+
if [[ $p1 -lt $p2 ]]; then echo -1; return; fi
|
|
109
|
+
if [[ $p1 -gt $p2 ]]; then echo 1; return; fi
|
|
110
|
+
echo 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Bump version
|
|
114
|
+
bump_version() {
|
|
115
|
+
local current="$1" bump_type="$2"
|
|
116
|
+
|
|
117
|
+
IFS='|' read -r major minor patch <<< "$(parse_version "$current")"
|
|
118
|
+
|
|
119
|
+
case "$bump_type" in
|
|
120
|
+
major)
|
|
121
|
+
major=$((major + 1))
|
|
122
|
+
minor=0
|
|
123
|
+
patch=0
|
|
124
|
+
;;
|
|
125
|
+
minor)
|
|
126
|
+
minor=$((minor + 1))
|
|
127
|
+
patch=0
|
|
128
|
+
;;
|
|
129
|
+
patch)
|
|
130
|
+
patch=$((patch + 1))
|
|
131
|
+
;;
|
|
132
|
+
esac
|
|
133
|
+
|
|
134
|
+
echo "v${major}.${minor}.${patch}"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Get conventional commit type from message
|
|
138
|
+
get_commit_type() {
|
|
139
|
+
local msg="$1"
|
|
140
|
+
# Extract type from "type: description" or "type(scope): description"
|
|
141
|
+
if [[ $msg =~ ^([a-z]+) ]]; then
|
|
142
|
+
echo "${BASH_REMATCH[1]}"
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# Check if commit contains BREAKING CHANGE
|
|
147
|
+
has_breaking_change() {
|
|
148
|
+
local commit_hash="$1"
|
|
149
|
+
git show "$commit_hash" | grep -q "BREAKING CHANGE:" && echo "yes" || echo ""
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# ─── Changelog generation ─────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
# Get commits between two revisions
|
|
155
|
+
get_commits() {
|
|
156
|
+
local from="$1" to="$2"
|
|
157
|
+
|
|
158
|
+
if [[ "$from" == "v0.0.0" ]]; then
|
|
159
|
+
git log "$to" --oneline --format="%h|%s|%b" 2>/dev/null || true
|
|
160
|
+
else
|
|
161
|
+
git log "${from}..${to}" --oneline --format="%h|%s|%b" 2>/dev/null || true
|
|
162
|
+
fi
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Parse commits and categorize them (using parallel arrays for bash 3.2 compatibility)
|
|
166
|
+
categorize_commits() {
|
|
167
|
+
local from="$1" to="$2"
|
|
168
|
+
|
|
169
|
+
# Use parallel arrays instead of associative arrays
|
|
170
|
+
local breaking_hashes=() breaking_subjects=()
|
|
171
|
+
local features_hashes=() features_subjects=()
|
|
172
|
+
local fixes_hashes=() fixes_subjects=()
|
|
173
|
+
local chores_hashes=() chores_subjects=()
|
|
174
|
+
local docs_hashes=() docs_subjects=()
|
|
175
|
+
|
|
176
|
+
# Read commits from git log
|
|
177
|
+
while IFS='|' read -r hash subject body; do
|
|
178
|
+
[[ -z "$hash" ]] && continue
|
|
179
|
+
|
|
180
|
+
local type
|
|
181
|
+
type="$(get_commit_type "$subject")"
|
|
182
|
+
|
|
183
|
+
# Check for breaking change
|
|
184
|
+
local breaking_flag=""
|
|
185
|
+
if echo "$body" | grep -q "BREAKING CHANGE:"; then
|
|
186
|
+
breaking_flag="yes"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Categorize using parallel arrays
|
|
190
|
+
case "$type" in
|
|
191
|
+
feat)
|
|
192
|
+
if [[ "$breaking_flag" == "yes" ]]; then
|
|
193
|
+
breaking_hashes+=("$hash")
|
|
194
|
+
breaking_subjects+=("$subject")
|
|
195
|
+
else
|
|
196
|
+
features_hashes+=("$hash")
|
|
197
|
+
features_subjects+=("$subject")
|
|
198
|
+
fi
|
|
199
|
+
;;
|
|
200
|
+
fix)
|
|
201
|
+
fixes_hashes+=("$hash")
|
|
202
|
+
fixes_subjects+=("$subject")
|
|
203
|
+
;;
|
|
204
|
+
chore|ci|test)
|
|
205
|
+
chores_hashes+=("$hash")
|
|
206
|
+
chores_subjects+=("$subject")
|
|
207
|
+
;;
|
|
208
|
+
docs)
|
|
209
|
+
docs_hashes+=("$hash")
|
|
210
|
+
docs_subjects+=("$subject")
|
|
211
|
+
;;
|
|
212
|
+
esac
|
|
213
|
+
done < <(get_commits "$from" "$to")
|
|
214
|
+
|
|
215
|
+
# Output data (not used by default, kept for future use)
|
|
216
|
+
true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Generate markdown changelog (bash 3.2 compatible - no associative arrays)
|
|
220
|
+
generate_changelog_md() {
|
|
221
|
+
local version="$1" from="$2" to="$3"
|
|
222
|
+
|
|
223
|
+
echo "## [$version] — $(date +%Y-%m-%d)"
|
|
224
|
+
echo ""
|
|
225
|
+
|
|
226
|
+
# Use grep filtering to categorize commits
|
|
227
|
+
local breaking_commits docs_commits features_commits fixes_commits
|
|
228
|
+
|
|
229
|
+
breaking_commits="$(get_commits "$from" "$to" | while IFS='|' read -r hash subject body; do
|
|
230
|
+
[[ -z "$hash" ]] && continue
|
|
231
|
+
type="$(get_commit_type "$subject")"
|
|
232
|
+
if [[ "$type" == "feat" ]] && echo "$body" | grep -q "BREAKING CHANGE:"; then
|
|
233
|
+
echo "$hash|$subject"
|
|
234
|
+
fi
|
|
235
|
+
done)"
|
|
236
|
+
|
|
237
|
+
features_commits="$(get_commits "$from" "$to" | while IFS='|' read -r hash subject body; do
|
|
238
|
+
[[ -z "$hash" ]] && continue
|
|
239
|
+
type="$(get_commit_type "$subject")"
|
|
240
|
+
if [[ "$type" == "feat" ]] && ! echo "$body" | grep -q "BREAKING CHANGE:"; then
|
|
241
|
+
echo "$hash|$subject"
|
|
242
|
+
fi
|
|
243
|
+
done)"
|
|
244
|
+
|
|
245
|
+
fixes_commits="$(get_commits "$from" "$to" | while IFS='|' read -r hash subject body; do
|
|
246
|
+
[[ -z "$hash" ]] && continue
|
|
247
|
+
type="$(get_commit_type "$subject")"
|
|
248
|
+
[[ "$type" == "fix" ]] && echo "$hash|$subject"
|
|
249
|
+
done)"
|
|
250
|
+
|
|
251
|
+
docs_commits="$(get_commits "$from" "$to" | while IFS='|' read -r hash subject body; do
|
|
252
|
+
[[ -z "$hash" ]] && continue
|
|
253
|
+
type="$(get_commit_type "$subject")"
|
|
254
|
+
[[ "$type" == "docs" ]] && echo "$hash|$subject"
|
|
255
|
+
done)"
|
|
256
|
+
|
|
257
|
+
# Breaking changes section
|
|
258
|
+
if [[ -n "$breaking_commits" ]]; then
|
|
259
|
+
echo "### Breaking Changes"
|
|
260
|
+
echo ""
|
|
261
|
+
echo "$breaking_commits" | while IFS='|' read -r hash subject; do
|
|
262
|
+
[[ -z "$hash" ]] && continue
|
|
263
|
+
echo "- $subject ([\`$hash\`](https://github.com/sethdford/shipwright/commit/$hash))"
|
|
264
|
+
done
|
|
265
|
+
echo ""
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
# Features section
|
|
269
|
+
if [[ -n "$features_commits" ]]; then
|
|
270
|
+
echo "### Features"
|
|
271
|
+
echo ""
|
|
272
|
+
echo "$features_commits" | while IFS='|' read -r hash subject; do
|
|
273
|
+
[[ -z "$hash" ]] && continue
|
|
274
|
+
echo "- $subject ([\`$hash\`](https://github.com/sethdford/shipwright/commit/$hash))"
|
|
275
|
+
done
|
|
276
|
+
echo ""
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
# Fixes section
|
|
280
|
+
if [[ -n "$fixes_commits" ]]; then
|
|
281
|
+
echo "### Bug Fixes"
|
|
282
|
+
echo ""
|
|
283
|
+
echo "$fixes_commits" | while IFS='|' read -r hash subject; do
|
|
284
|
+
[[ -z "$hash" ]] && continue
|
|
285
|
+
echo "- $subject ([\`$hash\`](https://github.com/sethdford/shipwright/commit/$hash))"
|
|
286
|
+
done
|
|
287
|
+
echo ""
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# Docs section
|
|
291
|
+
if [[ -n "$docs_commits" ]]; then
|
|
292
|
+
echo "### Documentation"
|
|
293
|
+
echo ""
|
|
294
|
+
echo "$docs_commits" | while IFS='|' read -r hash subject; do
|
|
295
|
+
[[ -z "$hash" ]] && continue
|
|
296
|
+
echo "- $subject ([\`$hash\`](https://github.com/sethdford/shipwright/commit/$hash))"
|
|
297
|
+
done
|
|
298
|
+
echo ""
|
|
299
|
+
fi
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# ─── Version detection ─────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
# Detect next version based on commit types
|
|
305
|
+
detect_next_version() {
|
|
306
|
+
local current="$1" from="$2" to="$3"
|
|
307
|
+
|
|
308
|
+
local has_breaking=false
|
|
309
|
+
local has_feature=false
|
|
310
|
+
|
|
311
|
+
while IFS='|' read -r hash subject body; do
|
|
312
|
+
[[ -z "$hash" ]] && continue
|
|
313
|
+
|
|
314
|
+
local type
|
|
315
|
+
type="$(get_commit_type "$subject")"
|
|
316
|
+
|
|
317
|
+
case "$type" in
|
|
318
|
+
feat)
|
|
319
|
+
has_feature=true
|
|
320
|
+
if echo "$body" | grep -q "BREAKING CHANGE:"; then
|
|
321
|
+
has_breaking=true
|
|
322
|
+
fi
|
|
323
|
+
;;
|
|
324
|
+
esac
|
|
325
|
+
done < <(get_commits "$from" "$to")
|
|
326
|
+
|
|
327
|
+
if $has_breaking; then
|
|
328
|
+
bump_version "$current" "major"
|
|
329
|
+
elif $has_feature; then
|
|
330
|
+
bump_version "$current" "minor"
|
|
331
|
+
else
|
|
332
|
+
bump_version "$current" "patch"
|
|
333
|
+
fi
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# ─── Update VERSION in scripts ─────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
update_version_in_files() {
|
|
339
|
+
local new_version="$1"
|
|
340
|
+
local version_num="${new_version#v}" # Strip 'v' prefix
|
|
341
|
+
|
|
342
|
+
info "Updating VERSION variable in scripts..."
|
|
343
|
+
|
|
344
|
+
# Find all shell scripts with VERSION variable
|
|
345
|
+
while IFS= read -r file; do
|
|
346
|
+
if grep -q '^VERSION=' "$file"; then
|
|
347
|
+
# Use sed to update VERSION="x.y.z" pattern
|
|
348
|
+
# This is shell-safe: VERSION="1.11.0" → VERSION="1.12.0"
|
|
349
|
+
local tmp_file
|
|
350
|
+
tmp_file=$(mktemp)
|
|
351
|
+
sed 's/^VERSION="[^"]*"$/VERSION="'"$version_num"'"/' "$file" > "$tmp_file"
|
|
352
|
+
mv "$tmp_file" "$file"
|
|
353
|
+
success "Updated VERSION in $(basename "$file")"
|
|
354
|
+
fi
|
|
355
|
+
done < <(find "$REPO_DIR/scripts" -name "sw*.sh" -o -name "lib/*.sh" 2>/dev/null)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# ─── Command implementations ──────────────────────────────────────────────
|
|
359
|
+
|
|
360
|
+
cmd_prepare() {
|
|
361
|
+
local current_version
|
|
362
|
+
current_version="$(get_latest_tag)"
|
|
363
|
+
|
|
364
|
+
if [[ -z "$VERSION_TYPE" ]]; then
|
|
365
|
+
# Auto-detect from commits
|
|
366
|
+
info "Auto-detecting version bump from commits..."
|
|
367
|
+
local next_version
|
|
368
|
+
next_version="$(detect_next_version "$current_version" "$current_version" "HEAD")"
|
|
369
|
+
VERSION_TYPE="detected"
|
|
370
|
+
else
|
|
371
|
+
local next_version
|
|
372
|
+
next_version="$(bump_version "$current_version" "$VERSION_TYPE")"
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
echo ""
|
|
376
|
+
info "Release Preparation"
|
|
377
|
+
echo ""
|
|
378
|
+
echo -e " Current version: ${CYAN}${current_version}${RESET}"
|
|
379
|
+
echo -e " Next version: ${CYAN}${next_version}${RESET}"
|
|
380
|
+
echo -e " Bump type: ${CYAN}${VERSION_TYPE}${RESET}"
|
|
381
|
+
|
|
382
|
+
if $DRY_RUN; then
|
|
383
|
+
echo -e " Mode: ${YELLOW}DRY RUN${RESET}"
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
echo ""
|
|
387
|
+
|
|
388
|
+
if ! $DRY_RUN; then
|
|
389
|
+
echo -e "This will:"
|
|
390
|
+
echo -e " 1. Update VERSION variable in all scripts"
|
|
391
|
+
echo -e " 2. Generate changelog"
|
|
392
|
+
echo -e " 3. Create git tag: ${CYAN}${next_version}${RESET}"
|
|
393
|
+
echo ""
|
|
394
|
+
|
|
395
|
+
read -p "Continue? (y/n) " -n 1 -r
|
|
396
|
+
echo
|
|
397
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
398
|
+
warn "Aborted"
|
|
399
|
+
return 1
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
update_version_in_files "$next_version"
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
success "Preparation complete"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
cmd_changelog() {
|
|
409
|
+
local from_tag="$FROM_TAG"
|
|
410
|
+
local to_tag="$TO_TAG"
|
|
411
|
+
|
|
412
|
+
if [[ -z "$from_tag" ]]; then
|
|
413
|
+
from_tag="$(get_latest_tag)"
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
info "Generating changelog"
|
|
417
|
+
echo ""
|
|
418
|
+
|
|
419
|
+
echo -e "From: ${CYAN}${from_tag}${RESET}"
|
|
420
|
+
echo -e "To: ${CYAN}${to_tag}${RESET}"
|
|
421
|
+
echo ""
|
|
422
|
+
|
|
423
|
+
if $DRY_RUN; then
|
|
424
|
+
echo -e "${YELLOW}DRY RUN${RESET} — showing preview only"
|
|
425
|
+
echo ""
|
|
426
|
+
fi
|
|
427
|
+
|
|
428
|
+
local changelog
|
|
429
|
+
changelog="$(generate_changelog_md "$to_tag" "$from_tag" "$to_tag")"
|
|
430
|
+
|
|
431
|
+
echo "$changelog"
|
|
432
|
+
|
|
433
|
+
echo ""
|
|
434
|
+
success "Changelog generated"
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
cmd_tag() {
|
|
438
|
+
local tag_version="$1"
|
|
439
|
+
|
|
440
|
+
if [[ -z "$tag_version" ]]; then
|
|
441
|
+
error "Version required: shipwright release tag v1.2.3"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
|
|
445
|
+
info "Creating git tag: ${CYAN}${tag_version}${RESET}"
|
|
446
|
+
echo ""
|
|
447
|
+
|
|
448
|
+
# Validate format
|
|
449
|
+
if ! [[ $tag_version =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
450
|
+
error "Invalid version format. Expected: v1.2.3"
|
|
451
|
+
return 1
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
if $DRY_RUN; then
|
|
455
|
+
echo -e "${YELLOW}DRY RUN${RESET} — tag would be created"
|
|
456
|
+
echo ""
|
|
457
|
+
success "Tag validation passed"
|
|
458
|
+
return
|
|
459
|
+
fi
|
|
460
|
+
|
|
461
|
+
# Create annotated tag with message
|
|
462
|
+
local tag_msg="Release $tag_version"
|
|
463
|
+
git tag -a "$tag_version" -m "$tag_msg"
|
|
464
|
+
|
|
465
|
+
success "Tag created: ${CYAN}${tag_version}${RESET}"
|
|
466
|
+
|
|
467
|
+
# Show next steps
|
|
468
|
+
echo ""
|
|
469
|
+
info "Push tag with: ${DIM}git push origin $tag_version${RESET}"
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
cmd_publish() {
|
|
473
|
+
local current_version
|
|
474
|
+
current_version="$(get_latest_tag)"
|
|
475
|
+
|
|
476
|
+
local next_version
|
|
477
|
+
next_version="$(detect_next_version "$current_version" "$current_version" "HEAD")"
|
|
478
|
+
|
|
479
|
+
info "Full Release: ${CYAN}${next_version}${RESET}"
|
|
480
|
+
echo ""
|
|
481
|
+
|
|
482
|
+
if $DRY_RUN; then
|
|
483
|
+
echo -e "${YELLOW}DRY RUN${RESET} — showing what would happen:"
|
|
484
|
+
echo ""
|
|
485
|
+
echo " 1. Update all VERSION variables to ${CYAN}${next_version#v}${RESET}"
|
|
486
|
+
echo " 2. Generate changelog"
|
|
487
|
+
echo " 3. Create git tag: ${CYAN}${next_version}${RESET}"
|
|
488
|
+
echo " 4. Push tag to origin"
|
|
489
|
+
echo " 5. Create GitHub release"
|
|
490
|
+
echo ""
|
|
491
|
+
success "Dry run complete — no changes made"
|
|
492
|
+
return
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
read -p "Release ${next_version}? (y/n) " -n 1 -r
|
|
496
|
+
echo
|
|
497
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
498
|
+
warn "Aborted"
|
|
499
|
+
return 1
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
echo ""
|
|
503
|
+
|
|
504
|
+
# Step 1: Update versions
|
|
505
|
+
info "Step 1/5: Updating VERSION variables..."
|
|
506
|
+
update_version_in_files "$next_version"
|
|
507
|
+
echo ""
|
|
508
|
+
|
|
509
|
+
# Step 2: Generate changelog
|
|
510
|
+
info "Step 2/5: Generating changelog..."
|
|
511
|
+
local changelog
|
|
512
|
+
changelog="$(generate_changelog_md "$next_version" "$current_version" "HEAD")"
|
|
513
|
+
echo "$changelog" > /tmp/release-changelog.md
|
|
514
|
+
success "Changelog generated"
|
|
515
|
+
echo ""
|
|
516
|
+
|
|
517
|
+
# Step 3: Create git tag
|
|
518
|
+
info "Step 3/5: Creating git tag..."
|
|
519
|
+
git tag -a "$next_version" -m "Release $next_version"
|
|
520
|
+
success "Tag created"
|
|
521
|
+
echo ""
|
|
522
|
+
|
|
523
|
+
# Step 4: Push tag
|
|
524
|
+
info "Step 4/5: Pushing tag to origin..."
|
|
525
|
+
if git push origin "$next_version"; then
|
|
526
|
+
success "Tag pushed"
|
|
527
|
+
else
|
|
528
|
+
warn "Failed to push tag (may already exist)"
|
|
529
|
+
fi
|
|
530
|
+
echo ""
|
|
531
|
+
|
|
532
|
+
# Step 5: Create GitHub release
|
|
533
|
+
info "Step 5/5: Creating GitHub release..."
|
|
534
|
+
if command -v gh &>/dev/null; then
|
|
535
|
+
if gh release create "$next_version" --title "$next_version" --notes-file /tmp/release-changelog.md; then
|
|
536
|
+
success "GitHub release created"
|
|
537
|
+
else
|
|
538
|
+
warn "Failed to create GitHub release (may already exist)"
|
|
539
|
+
fi
|
|
540
|
+
else
|
|
541
|
+
warn "gh CLI not installed — skipping GitHub release creation"
|
|
542
|
+
echo -e " Manual create: ${DIM}gh release create $next_version --title '$next_version' --notes-file /tmp/release-changelog.md${RESET}"
|
|
543
|
+
fi
|
|
544
|
+
echo ""
|
|
545
|
+
|
|
546
|
+
success "Release ${next_version} complete!"
|
|
547
|
+
|
|
548
|
+
emit_event "release" "version=$next_version" "type=publish"
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
cmd_status() {
|
|
552
|
+
local current_version
|
|
553
|
+
current_version="$(get_latest_tag)"
|
|
554
|
+
|
|
555
|
+
info "Release Status"
|
|
556
|
+
echo ""
|
|
557
|
+
echo -e " Current version: ${CYAN}${current_version}${RESET}"
|
|
558
|
+
|
|
559
|
+
# Check for unreleased commits
|
|
560
|
+
local unreleased_count
|
|
561
|
+
unreleased_count="$(git rev-list "${current_version}..HEAD" --count 2>/dev/null || echo 0)"
|
|
562
|
+
|
|
563
|
+
if [[ $unreleased_count -gt 0 ]]; then
|
|
564
|
+
echo -e " Unreleased commits: ${YELLOW}${unreleased_count}${RESET}"
|
|
565
|
+
|
|
566
|
+
# Predict next version
|
|
567
|
+
local next_version
|
|
568
|
+
next_version="$(detect_next_version "$current_version" "$current_version" "HEAD")"
|
|
569
|
+
|
|
570
|
+
echo -e " Predicted next: ${CYAN}${next_version}${RESET}"
|
|
571
|
+
|
|
572
|
+
echo ""
|
|
573
|
+
info "Recent commits:"
|
|
574
|
+
git log "${current_version}..HEAD" --oneline | head -5
|
|
575
|
+
else
|
|
576
|
+
echo -e " Unreleased commits: ${GREEN}0${RESET}"
|
|
577
|
+
echo ""
|
|
578
|
+
success "All commits have been released"
|
|
579
|
+
fi
|
|
580
|
+
|
|
581
|
+
echo ""
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
cmd_help() {
|
|
585
|
+
cat << 'EOF'
|
|
586
|
+
shipwright release — Release train automation
|
|
587
|
+
|
|
588
|
+
USAGE
|
|
589
|
+
shipwright release <command> [options]
|
|
590
|
+
|
|
591
|
+
COMMANDS
|
|
592
|
+
prepare [--major|--minor|--patch]
|
|
593
|
+
Prepare a release: bump version, generate changelog
|
|
594
|
+
Auto-detects version type from conventional commits if not specified
|
|
595
|
+
|
|
596
|
+
changelog [--from TAG --to HEAD]
|
|
597
|
+
Generate markdown changelog from conventional commits
|
|
598
|
+
Default: from last tag to HEAD
|
|
599
|
+
|
|
600
|
+
tag [version]
|
|
601
|
+
Create and push a git tag (e.g., v1.2.3)
|
|
602
|
+
Validates semantic version format
|
|
603
|
+
|
|
604
|
+
publish
|
|
605
|
+
Full automated release: prepare + changelog + tag + GitHub release
|
|
606
|
+
Updates all VERSION variables, commits, tags, and creates release
|
|
607
|
+
|
|
608
|
+
status
|
|
609
|
+
Show current version, unreleased commits, and predicted next version
|
|
610
|
+
|
|
611
|
+
help
|
|
612
|
+
Show this help message
|
|
613
|
+
|
|
614
|
+
OPTIONS
|
|
615
|
+
--dry-run
|
|
616
|
+
Show what would happen without making changes
|
|
617
|
+
|
|
618
|
+
--major
|
|
619
|
+
Release with major version bump (for breaking changes)
|
|
620
|
+
|
|
621
|
+
--minor
|
|
622
|
+
Release with minor version bump (for new features)
|
|
623
|
+
|
|
624
|
+
--patch
|
|
625
|
+
Release with patch version bump (for bug fixes)
|
|
626
|
+
|
|
627
|
+
--from TAG
|
|
628
|
+
Start changelog from specific tag (changelog only)
|
|
629
|
+
|
|
630
|
+
--to REV
|
|
631
|
+
End changelog at specific revision (default: HEAD)
|
|
632
|
+
|
|
633
|
+
EXAMPLES
|
|
634
|
+
# Check current release status
|
|
635
|
+
shipwright release status
|
|
636
|
+
|
|
637
|
+
# Auto-detect and prepare next release
|
|
638
|
+
shipwright release prepare
|
|
639
|
+
|
|
640
|
+
# Force a minor version bump
|
|
641
|
+
shipwright release prepare --minor
|
|
642
|
+
|
|
643
|
+
# Generate changelog from v1.0.0 to HEAD
|
|
644
|
+
shipwright release changelog --from v1.0.0
|
|
645
|
+
|
|
646
|
+
# Dry run of full release
|
|
647
|
+
shipwright release publish --dry-run
|
|
648
|
+
|
|
649
|
+
# Publish full release
|
|
650
|
+
shipwright release publish
|
|
651
|
+
|
|
652
|
+
CONVENTIONAL COMMITS
|
|
653
|
+
|
|
654
|
+
The release tool parses commits to auto-detect version bumps:
|
|
655
|
+
|
|
656
|
+
feat: New feature → minor version bump
|
|
657
|
+
fix: Bug fix → patch version bump
|
|
658
|
+
BREAKING CHANGE: in message → major version bump
|
|
659
|
+
chore, docs, ci: Other → no bump (patch only)
|
|
660
|
+
|
|
661
|
+
Example commit messages:
|
|
662
|
+
- "feat: add authentication system"
|
|
663
|
+
- "fix: prevent data loss in sync"
|
|
664
|
+
- "feat: new API endpoint\n\nBREAKING CHANGE: old endpoint removed"
|
|
665
|
+
|
|
666
|
+
EOF
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
# ─── Main router ──────────────────────────────────────────────────────────
|
|
670
|
+
|
|
671
|
+
main() {
|
|
672
|
+
local cmd="${1:-status}"
|
|
673
|
+
shift 2>/dev/null || true
|
|
674
|
+
|
|
675
|
+
case "$cmd" in
|
|
676
|
+
prepare)
|
|
677
|
+
cmd_prepare "$@"
|
|
678
|
+
;;
|
|
679
|
+
changelog)
|
|
680
|
+
cmd_changelog "$@"
|
|
681
|
+
;;
|
|
682
|
+
tag)
|
|
683
|
+
cmd_tag "$@"
|
|
684
|
+
;;
|
|
685
|
+
publish)
|
|
686
|
+
cmd_publish "$@"
|
|
687
|
+
;;
|
|
688
|
+
status)
|
|
689
|
+
cmd_status "$@"
|
|
690
|
+
;;
|
|
691
|
+
help|--help|-h)
|
|
692
|
+
cmd_help
|
|
693
|
+
;;
|
|
694
|
+
*)
|
|
695
|
+
error "Unknown command: $cmd"
|
|
696
|
+
echo ""
|
|
697
|
+
cmd_help
|
|
698
|
+
exit 1
|
|
699
|
+
;;
|
|
700
|
+
esac
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
# Only run main if this script is executed directly (not sourced)
|
|
704
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
705
|
+
main "$@"
|
|
706
|
+
fi
|