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,704 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright changelog — Automated Release Notes & Migration Guides ║
|
|
4
|
+
# ║ Parse commits, categorize changes, generate markdown and stakeholder ║
|
|
5
|
+
# ║ announcements with version recommendations and migration instructions ║
|
|
6
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
9
|
+
|
|
10
|
+
VERSION="2.0.0"
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
15
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
16
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
17
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
18
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
19
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
20
|
+
RED='\033[38;2;248;113;113m' # error
|
|
21
|
+
DIM='\033[2m'
|
|
22
|
+
BOLD='\033[1m'
|
|
23
|
+
RESET='\033[0m'
|
|
24
|
+
|
|
25
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
26
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
27
|
+
|
|
28
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
29
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
30
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
31
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
32
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
33
|
+
|
|
34
|
+
emit_event() {
|
|
35
|
+
local event_type="$1"; shift
|
|
36
|
+
local events_file="${HOME}/.shipwright/events.jsonl"
|
|
37
|
+
mkdir -p "$(dirname "$events_file")"
|
|
38
|
+
local payload="{\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"type\":\"$event_type\""
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
local key="${1%%=*}" val="${1#*=}"
|
|
41
|
+
payload="${payload},\"${key}\":\"${val}\""
|
|
42
|
+
shift
|
|
43
|
+
done
|
|
44
|
+
payload="${payload}}"
|
|
45
|
+
echo "$payload" >> "$events_file"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ─── Commit Parsing ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
# Extract conventional commit type (feat, fix, perf, security, etc.)
|
|
51
|
+
get_commit_type() {
|
|
52
|
+
local msg="$1"
|
|
53
|
+
if [[ "$msg" =~ ^(feat|fix|perf|chore|docs|style|refactor|test|ci|security|breaking) ]]; then
|
|
54
|
+
echo "${BASH_REMATCH[1]}"
|
|
55
|
+
elif [[ "$msg" =~ ^BREAKING ]]; then
|
|
56
|
+
echo "breaking"
|
|
57
|
+
else
|
|
58
|
+
echo "other"
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Extract scope if present (e.g., "feat(auth):" → "auth")
|
|
63
|
+
get_commit_scope() {
|
|
64
|
+
local msg="$1"
|
|
65
|
+
# Use simpler pattern: extract text between parens after type
|
|
66
|
+
echo "$msg" | grep -oE '^[a-z]+\([^)]+\)' | sed 's/^[a-z]*(\(.*\))/\1/' || true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Clean commit message (remove type prefix and scope)
|
|
70
|
+
clean_commit_msg() {
|
|
71
|
+
local msg="$1"
|
|
72
|
+
msg="${msg#feat: }"
|
|
73
|
+
msg="${msg#fix: }"
|
|
74
|
+
msg="${msg#perf: }"
|
|
75
|
+
msg="${msg#chore: }"
|
|
76
|
+
msg="${msg#docs: }"
|
|
77
|
+
msg="${msg#style: }"
|
|
78
|
+
msg="${msg#refactor: }"
|
|
79
|
+
msg="${msg#test: }"
|
|
80
|
+
msg="${msg#ci: }"
|
|
81
|
+
msg="${msg#security: }"
|
|
82
|
+
msg="${msg#BREAKING: }"
|
|
83
|
+
msg="${msg#BREAKING CHANGE: }"
|
|
84
|
+
# Remove scope if present
|
|
85
|
+
msg="${msg#*()*: }"
|
|
86
|
+
msg=$(echo "$msg" | sed 's/^[a-z]*(\([^)]*\)): //')
|
|
87
|
+
echo "$msg"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Get commits since last release (tag) or from start
|
|
91
|
+
get_commits() {
|
|
92
|
+
local from_ref="${1:-}"
|
|
93
|
+
local to_ref="${2:-HEAD}"
|
|
94
|
+
|
|
95
|
+
if [[ -z "$from_ref" ]]; then
|
|
96
|
+
# Find last tag
|
|
97
|
+
from_ref=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "$(git -C "$REPO_DIR" rev-list --max-parents=0 HEAD)")
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [[ "$from_ref" == "HEAD" ]]; then
|
|
101
|
+
return
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
git -C "$REPO_DIR" log "${from_ref}..${to_ref}" --pretty=format:"%H|%an|%ae|%ai|%s|%b" 2>/dev/null || true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Parse commits into categorized structure
|
|
108
|
+
parse_commits() {
|
|
109
|
+
local from_ref="${1:-}"
|
|
110
|
+
local to_ref="${2:-HEAD}"
|
|
111
|
+
local commits_log
|
|
112
|
+
commits_log=$(get_commits "$from_ref" "$to_ref")
|
|
113
|
+
|
|
114
|
+
local features fixes perf_changes security_changes breaking_changes docs_changes chores
|
|
115
|
+
features=""
|
|
116
|
+
fixes=""
|
|
117
|
+
perf_changes=""
|
|
118
|
+
security_changes=""
|
|
119
|
+
breaking_changes=""
|
|
120
|
+
docs_changes=""
|
|
121
|
+
chores=""
|
|
122
|
+
local contributors=""
|
|
123
|
+
local pr_links=""
|
|
124
|
+
|
|
125
|
+
while IFS='|' read -r hash author email date subject body; do
|
|
126
|
+
[[ -z "$hash" ]] && continue
|
|
127
|
+
|
|
128
|
+
local type scope msg
|
|
129
|
+
type=$(get_commit_type "$subject")
|
|
130
|
+
scope=$(get_commit_scope "$subject")
|
|
131
|
+
msg=$(clean_commit_msg "$subject")
|
|
132
|
+
|
|
133
|
+
# Extract PR number if in body
|
|
134
|
+
local pr_num=""
|
|
135
|
+
if [[ "$body" =~ ([Pp][Rr]\s*#?([0-9]+)|#([0-9]+)) ]]; then
|
|
136
|
+
pr_num="${BASH_REMATCH[2]:-${BASH_REMATCH[3]}}"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Build entry
|
|
140
|
+
local entry="$msg"
|
|
141
|
+
[[ -n "$scope" ]] && entry="**${scope}**: $entry"
|
|
142
|
+
[[ -n "$pr_num" ]] && entry="$entry ([\#$pr_num](https://github.com/sethdford/shipwright/pull/$pr_num))"
|
|
143
|
+
|
|
144
|
+
# Categorize
|
|
145
|
+
case "$type" in
|
|
146
|
+
breaking)
|
|
147
|
+
breaking_changes="${breaking_changes}${entry}
|
|
148
|
+
"
|
|
149
|
+
;;
|
|
150
|
+
feat)
|
|
151
|
+
features="${features}${entry}
|
|
152
|
+
"
|
|
153
|
+
;;
|
|
154
|
+
fix)
|
|
155
|
+
fixes="${fixes}${entry}
|
|
156
|
+
"
|
|
157
|
+
;;
|
|
158
|
+
perf)
|
|
159
|
+
perf_changes="${perf_changes}${entry}
|
|
160
|
+
"
|
|
161
|
+
;;
|
|
162
|
+
security)
|
|
163
|
+
security_changes="${security_changes}${entry}
|
|
164
|
+
"
|
|
165
|
+
;;
|
|
166
|
+
docs)
|
|
167
|
+
docs_changes="${docs_changes}${entry}
|
|
168
|
+
"
|
|
169
|
+
;;
|
|
170
|
+
chore|style|refactor|test|ci)
|
|
171
|
+
chores="${chores}${entry}
|
|
172
|
+
"
|
|
173
|
+
;;
|
|
174
|
+
*)
|
|
175
|
+
# Treat as feature if breaking indicator present
|
|
176
|
+
if echo "$subject" | grep -qi "breaking"; then
|
|
177
|
+
breaking_changes="${breaking_changes}${msg}
|
|
178
|
+
"
|
|
179
|
+
else
|
|
180
|
+
features="${features}${entry}
|
|
181
|
+
"
|
|
182
|
+
fi
|
|
183
|
+
;;
|
|
184
|
+
esac
|
|
185
|
+
|
|
186
|
+
# Track contributors
|
|
187
|
+
contributors="${contributors}${author} (${email})
|
|
188
|
+
"
|
|
189
|
+
done <<< "$commits_log"
|
|
190
|
+
|
|
191
|
+
# Output as JSON
|
|
192
|
+
jq -n \
|
|
193
|
+
--arg features "$features" \
|
|
194
|
+
--arg fixes "$fixes" \
|
|
195
|
+
--arg perf "$perf_changes" \
|
|
196
|
+
--arg security "$security_changes" \
|
|
197
|
+
--arg breaking "$breaking_changes" \
|
|
198
|
+
--arg docs "$docs_changes" \
|
|
199
|
+
--arg chores "$chores" \
|
|
200
|
+
--arg contributors "$contributors" \
|
|
201
|
+
'{
|
|
202
|
+
features: ($features | split("\n") | map(select(length > 0))),
|
|
203
|
+
fixes: ($fixes | split("\n") | map(select(length > 0))),
|
|
204
|
+
perf: ($perf | split("\n") | map(select(length > 0))),
|
|
205
|
+
security: ($security | split("\n") | map(select(length > 0))),
|
|
206
|
+
breaking: ($breaking | split("\n") | map(select(length > 0))),
|
|
207
|
+
docs: ($docs | split("\n") | map(select(length > 0))),
|
|
208
|
+
chores: ($chores | split("\n") | map(select(length > 0))),
|
|
209
|
+
contributors: ($contributors | split("\n") | map(select(length > 0)) | unique)
|
|
210
|
+
}'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# ─── Version Recommendation ──────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
recommend_version() {
|
|
216
|
+
local changes_json="$1"
|
|
217
|
+
local current_version="${2:-0.1.0}"
|
|
218
|
+
|
|
219
|
+
# Parse current version
|
|
220
|
+
local major minor patch
|
|
221
|
+
IFS='.' read -r major minor patch <<< "$current_version"
|
|
222
|
+
patch="${patch%%[^0-9]*}" # Strip any pre-release/build metadata
|
|
223
|
+
|
|
224
|
+
# Check for breaking changes
|
|
225
|
+
local breaking_count
|
|
226
|
+
breaking_count=$(echo "$changes_json" | jq '.breaking | length')
|
|
227
|
+
|
|
228
|
+
# Check for features
|
|
229
|
+
local features_count
|
|
230
|
+
features_count=$(echo "$changes_json" | jq '.features | length')
|
|
231
|
+
|
|
232
|
+
# Check for fixes
|
|
233
|
+
local fixes_count
|
|
234
|
+
fixes_count=$(echo "$changes_json" | jq '.fixes | length')
|
|
235
|
+
|
|
236
|
+
if [[ "$breaking_count" -gt 0 ]]; then
|
|
237
|
+
echo "$((major + 1)).0.0"
|
|
238
|
+
elif [[ "$features_count" -gt 0 ]]; then
|
|
239
|
+
echo "${major}.$((minor + 1)).0"
|
|
240
|
+
elif [[ "$fixes_count" -gt 0 ]]; then
|
|
241
|
+
echo "${major}.${minor}.$((patch + 1))"
|
|
242
|
+
else
|
|
243
|
+
echo "${major}.${minor}.${patch}"
|
|
244
|
+
fi
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# ─── Release Notes Generation ────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
generate_markdown() {
|
|
250
|
+
local changes_json="$1"
|
|
251
|
+
local version="${2:-Unreleased}"
|
|
252
|
+
local date="${3:-$(date -u +%Y-%m-%d)}"
|
|
253
|
+
|
|
254
|
+
local output=""
|
|
255
|
+
output+="## [${version}] — ${date}
|
|
256
|
+
"
|
|
257
|
+
output+="
|
|
258
|
+
"
|
|
259
|
+
|
|
260
|
+
# Breaking Changes
|
|
261
|
+
local breaking_count
|
|
262
|
+
breaking_count=$(echo "$changes_json" | jq '.breaking | length')
|
|
263
|
+
if [[ "$breaking_count" -gt 0 ]]; then
|
|
264
|
+
output+="### ⚠️ Breaking Changes
|
|
265
|
+
"
|
|
266
|
+
output+="
|
|
267
|
+
"
|
|
268
|
+
echo "$changes_json" | jq -r '.breaking[]' | while read -r change; do
|
|
269
|
+
output+="- ${change}
|
|
270
|
+
"
|
|
271
|
+
done
|
|
272
|
+
output+="
|
|
273
|
+
"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# Features
|
|
277
|
+
local features_count
|
|
278
|
+
features_count=$(echo "$changes_json" | jq '.features | length')
|
|
279
|
+
if [[ "$features_count" -gt 0 ]]; then
|
|
280
|
+
output+="### ✨ Features
|
|
281
|
+
"
|
|
282
|
+
output+="
|
|
283
|
+
"
|
|
284
|
+
echo "$changes_json" | jq -r '.features[]' | while read -r change; do
|
|
285
|
+
output+="- ${change}
|
|
286
|
+
"
|
|
287
|
+
done
|
|
288
|
+
output+="
|
|
289
|
+
"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# Security
|
|
293
|
+
local security_count
|
|
294
|
+
security_count=$(echo "$changes_json" | jq '.security | length')
|
|
295
|
+
if [[ "$security_count" -gt 0 ]]; then
|
|
296
|
+
output+="### 🔒 Security
|
|
297
|
+
"
|
|
298
|
+
output+="
|
|
299
|
+
"
|
|
300
|
+
echo "$changes_json" | jq -r '.security[]' | while read -r change; do
|
|
301
|
+
output+="- ${change}
|
|
302
|
+
"
|
|
303
|
+
done
|
|
304
|
+
output+="
|
|
305
|
+
"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# Performance
|
|
309
|
+
local perf_count
|
|
310
|
+
perf_count=$(echo "$changes_json" | jq '.perf | length')
|
|
311
|
+
if [[ "$perf_count" -gt 0 ]]; then
|
|
312
|
+
output+="### 🚀 Performance
|
|
313
|
+
"
|
|
314
|
+
output+="
|
|
315
|
+
"
|
|
316
|
+
echo "$changes_json" | jq -r '.perf[]' | while read -r change; do
|
|
317
|
+
output+="- ${change}
|
|
318
|
+
"
|
|
319
|
+
done
|
|
320
|
+
output+="
|
|
321
|
+
"
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
# Bug Fixes
|
|
325
|
+
local fixes_count
|
|
326
|
+
fixes_count=$(echo "$changes_json" | jq '.fixes | length')
|
|
327
|
+
if [[ "$fixes_count" -gt 0 ]]; then
|
|
328
|
+
output+="### 🐛 Bug Fixes
|
|
329
|
+
"
|
|
330
|
+
output+="
|
|
331
|
+
"
|
|
332
|
+
echo "$changes_json" | jq -r '.fixes[]' | while read -r change; do
|
|
333
|
+
output+="- ${change}
|
|
334
|
+
"
|
|
335
|
+
done
|
|
336
|
+
output+="
|
|
337
|
+
"
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# Documentation
|
|
341
|
+
local docs_count
|
|
342
|
+
docs_count=$(echo "$changes_json" | jq '.docs | length')
|
|
343
|
+
if [[ "$docs_count" -gt 0 ]]; then
|
|
344
|
+
output+="### 📚 Documentation
|
|
345
|
+
"
|
|
346
|
+
output+="
|
|
347
|
+
"
|
|
348
|
+
echo "$changes_json" | jq -r '.docs[]' | while read -r change; do
|
|
349
|
+
output+="- ${change}
|
|
350
|
+
"
|
|
351
|
+
done
|
|
352
|
+
output+="
|
|
353
|
+
"
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
# Contributors
|
|
357
|
+
local contributors_count
|
|
358
|
+
contributors_count=$(echo "$changes_json" | jq '.contributors | length')
|
|
359
|
+
if [[ "$contributors_count" -gt 0 ]]; then
|
|
360
|
+
output+="### 👥 Contributors
|
|
361
|
+
"
|
|
362
|
+
output+="
|
|
363
|
+
"
|
|
364
|
+
echo "$changes_json" | jq -r '.contributors[]' | while read -r contrib; do
|
|
365
|
+
[[ -n "$contrib" ]] && output+="- ${contrib}
|
|
366
|
+
"
|
|
367
|
+
done
|
|
368
|
+
output+="
|
|
369
|
+
"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
echo -e "$output"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
# ─── Migration Guide Generation ──────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
generate_migration_guide() {
|
|
378
|
+
local changes_json="$1"
|
|
379
|
+
local version="${2:-Unreleased}"
|
|
380
|
+
|
|
381
|
+
local breaking_count
|
|
382
|
+
breaking_count=$(echo "$changes_json" | jq '.breaking | length')
|
|
383
|
+
|
|
384
|
+
if [[ "$breaking_count" -eq 0 ]]; then
|
|
385
|
+
echo "No breaking changes in this release."
|
|
386
|
+
return 0
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
local output=""
|
|
390
|
+
output+="# Migration Guide — Version ${version}
|
|
391
|
+
"
|
|
392
|
+
output+="
|
|
393
|
+
"
|
|
394
|
+
output+="This release includes breaking changes. Follow this guide to update your code.
|
|
395
|
+
"
|
|
396
|
+
output+="
|
|
397
|
+
"
|
|
398
|
+
|
|
399
|
+
local idx=1
|
|
400
|
+
echo "$changes_json" | jq -r '.breaking[]' | while read -r change; do
|
|
401
|
+
output+="## Change ${idx}: ${change}
|
|
402
|
+
"
|
|
403
|
+
output+="
|
|
404
|
+
"
|
|
405
|
+
output+="### Before
|
|
406
|
+
"
|
|
407
|
+
output+="
|
|
408
|
+
"
|
|
409
|
+
output+="\`\`\`bash
|
|
410
|
+
# Previous approach
|
|
411
|
+
old_command --flag value
|
|
412
|
+
\`\`\`
|
|
413
|
+
"
|
|
414
|
+
output+="
|
|
415
|
+
"
|
|
416
|
+
output+="### After
|
|
417
|
+
"
|
|
418
|
+
output+="
|
|
419
|
+
"
|
|
420
|
+
output+="\`\`\`bash
|
|
421
|
+
# New approach
|
|
422
|
+
new_command --new-flag value
|
|
423
|
+
\`\`\`
|
|
424
|
+
"
|
|
425
|
+
output+="
|
|
426
|
+
"
|
|
427
|
+
idx=$((idx + 1))
|
|
428
|
+
done
|
|
429
|
+
|
|
430
|
+
echo -e "$output"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
# ─── Stakeholder Announcement ────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
generate_announcement() {
|
|
436
|
+
local changes_json="$1"
|
|
437
|
+
local version="${2:-Unreleased}"
|
|
438
|
+
|
|
439
|
+
local features_count
|
|
440
|
+
features_count=$(echo "$changes_json" | jq '.features | length')
|
|
441
|
+
local fixes_count
|
|
442
|
+
fixes_count=$(echo "$changes_json" | jq '.fixes | length')
|
|
443
|
+
local breaking_count
|
|
444
|
+
breaking_count=$(echo "$changes_json" | jq '.breaking | length')
|
|
445
|
+
|
|
446
|
+
local output=""
|
|
447
|
+
output+="# 🎉 Release: Version ${version}
|
|
448
|
+
"
|
|
449
|
+
output+="
|
|
450
|
+
"
|
|
451
|
+
output+="We're excited to announce the release of Shipwright ${version}, packed with improvements to make your CI/CD pipeline even more powerful.
|
|
452
|
+
"
|
|
453
|
+
output+="
|
|
454
|
+
"
|
|
455
|
+
output+="## What's New
|
|
456
|
+
"
|
|
457
|
+
output+="
|
|
458
|
+
"
|
|
459
|
+
|
|
460
|
+
if [[ "$features_count" -gt 0 ]]; then
|
|
461
|
+
output+="**${features_count} new features** that streamline your workflow and improve productivity:
|
|
462
|
+
"
|
|
463
|
+
output+="
|
|
464
|
+
"
|
|
465
|
+
echo "$changes_json" | jq -r '.features[]' | head -3 | while read -r feature; do
|
|
466
|
+
output+="- ${feature}
|
|
467
|
+
"
|
|
468
|
+
done
|
|
469
|
+
output+="
|
|
470
|
+
"
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
if [[ "$fixes_count" -gt 0 ]]; then
|
|
474
|
+
output+="**${fixes_count} bug fixes** that make Shipwright more reliable:
|
|
475
|
+
"
|
|
476
|
+
output+="
|
|
477
|
+
"
|
|
478
|
+
echo "$changes_json" | jq -r '.fixes[]' | head -3 | while read -r fix; do
|
|
479
|
+
output+="- ${fix}
|
|
480
|
+
"
|
|
481
|
+
done
|
|
482
|
+
output+="
|
|
483
|
+
"
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
if [[ "$breaking_count" -gt 0 ]]; then
|
|
487
|
+
output+="⚠️ **Note:** This release includes breaking changes. Please review the migration guide before upgrading.
|
|
488
|
+
"
|
|
489
|
+
output+="
|
|
490
|
+
"
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
output+="## Get Started
|
|
494
|
+
"
|
|
495
|
+
output+="
|
|
496
|
+
"
|
|
497
|
+
output+="Upgrade to version ${version} to take advantage of these improvements. See our documentation for detailed information.
|
|
498
|
+
"
|
|
499
|
+
output+="
|
|
500
|
+
"
|
|
501
|
+
|
|
502
|
+
echo -e "$output"
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
# ─── Subcommands ────────────────────────────────────────────────────────────
|
|
506
|
+
|
|
507
|
+
cmd_generate() {
|
|
508
|
+
local from_ref="${1:-}"
|
|
509
|
+
local to_ref="HEAD"
|
|
510
|
+
|
|
511
|
+
if [[ "$from_ref" == "--from" ]] && [[ -n "${2:-}" ]]; then
|
|
512
|
+
from_ref="$2"
|
|
513
|
+
if [[ "${3:-}" == "--to" ]] && [[ -n "${4:-}" ]]; then
|
|
514
|
+
to_ref="$4"
|
|
515
|
+
fi
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
info "Parsing commits from ${from_ref:-last release} to ${to_ref}..."
|
|
519
|
+
local changes_json
|
|
520
|
+
changes_json=$(parse_commits "$from_ref" "$to_ref")
|
|
521
|
+
|
|
522
|
+
local current_version
|
|
523
|
+
current_version=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
524
|
+
current_version="${current_version#v}"
|
|
525
|
+
|
|
526
|
+
local next_version
|
|
527
|
+
next_version=$(recommend_version "$changes_json" "$current_version")
|
|
528
|
+
|
|
529
|
+
info "Recommended version: ${CYAN}${next_version}${RESET}"
|
|
530
|
+
|
|
531
|
+
local release_notes
|
|
532
|
+
release_notes=$(generate_markdown "$changes_json" "v${next_version}")
|
|
533
|
+
|
|
534
|
+
local output_file="${REPO_DIR}/CHANGELOG-${next_version}.md"
|
|
535
|
+
echo "$release_notes" > "$output_file"
|
|
536
|
+
success "Release notes generated: ${output_file}"
|
|
537
|
+
|
|
538
|
+
emit_event "changelog.generate" "version=$next_version" "commits=$(echo "$changes_json" | jq '.features | length')"
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
cmd_preview() {
|
|
542
|
+
local from_ref="${1:-}"
|
|
543
|
+
local to_ref="HEAD"
|
|
544
|
+
|
|
545
|
+
info "Parsing commits for preview..."
|
|
546
|
+
local changes_json
|
|
547
|
+
changes_json=$(parse_commits "$from_ref" "$to_ref")
|
|
548
|
+
|
|
549
|
+
local current_version
|
|
550
|
+
current_version=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
551
|
+
current_version="${current_version#v}"
|
|
552
|
+
|
|
553
|
+
local next_version
|
|
554
|
+
next_version=$(recommend_version "$changes_json" "$current_version")
|
|
555
|
+
|
|
556
|
+
echo ""
|
|
557
|
+
echo -e "${CYAN}${BOLD}Preview: Release v${next_version}${RESET}"
|
|
558
|
+
echo ""
|
|
559
|
+
|
|
560
|
+
generate_markdown "$changes_json" "v${next_version}"
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
cmd_version() {
|
|
564
|
+
local from_ref="${1:-}"
|
|
565
|
+
local to_ref="HEAD"
|
|
566
|
+
|
|
567
|
+
info "Analyzing commits for version recommendation..."
|
|
568
|
+
local changes_json
|
|
569
|
+
changes_json=$(parse_commits "$from_ref" "$to_ref")
|
|
570
|
+
|
|
571
|
+
local current_version
|
|
572
|
+
current_version=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
573
|
+
current_version="${current_version#v}"
|
|
574
|
+
|
|
575
|
+
local next_version
|
|
576
|
+
next_version=$(recommend_version "$changes_json" "$current_version")
|
|
577
|
+
|
|
578
|
+
echo ""
|
|
579
|
+
echo -e "${BOLD}Current version:${RESET} ${current_version}"
|
|
580
|
+
echo -e "${BOLD}Recommended version:${RESET} ${CYAN}${next_version}${RESET}"
|
|
581
|
+
|
|
582
|
+
local breaking_count
|
|
583
|
+
breaking_count=$(echo "$changes_json" | jq '.breaking | length')
|
|
584
|
+
local features_count
|
|
585
|
+
features_count=$(echo "$changes_json" | jq '.features | length')
|
|
586
|
+
local fixes_count
|
|
587
|
+
fixes_count=$(echo "$changes_json" | jq '.fixes | length')
|
|
588
|
+
|
|
589
|
+
echo ""
|
|
590
|
+
echo "Rationale:"
|
|
591
|
+
[[ "$breaking_count" -gt 0 ]] && echo " - ${RED}${breaking_count} breaking changes${RESET} → major version bump"
|
|
592
|
+
[[ "$features_count" -gt 0 ]] && echo " - ${GREEN}${features_count} new features${RESET} → minor version bump"
|
|
593
|
+
[[ "$fixes_count" -gt 0 ]] && echo " - ${GREEN}${fixes_count} bug fixes${RESET} → patch version bump"
|
|
594
|
+
echo ""
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
cmd_migrate() {
|
|
598
|
+
local from_ref="${1:-}"
|
|
599
|
+
local to_ref="HEAD"
|
|
600
|
+
|
|
601
|
+
info "Generating migration guide..."
|
|
602
|
+
local changes_json
|
|
603
|
+
changes_json=$(parse_commits "$from_ref" "$to_ref")
|
|
604
|
+
|
|
605
|
+
local current_version
|
|
606
|
+
current_version=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
607
|
+
current_version="${current_version#v}"
|
|
608
|
+
|
|
609
|
+
local next_version
|
|
610
|
+
next_version=$(recommend_version "$changes_json" "$current_version")
|
|
611
|
+
|
|
612
|
+
local migration
|
|
613
|
+
migration=$(generate_migration_guide "$changes_json" "v${next_version}")
|
|
614
|
+
|
|
615
|
+
local output_file="${REPO_DIR}/MIGRATION-${next_version}.md"
|
|
616
|
+
echo "$migration" > "$output_file"
|
|
617
|
+
success "Migration guide generated: ${output_file}"
|
|
618
|
+
|
|
619
|
+
emit_event "changelog.migrate" "version=$next_version"
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
cmd_announce() {
|
|
623
|
+
local from_ref="${1:-}"
|
|
624
|
+
local to_ref="HEAD"
|
|
625
|
+
|
|
626
|
+
info "Generating stakeholder announcement..."
|
|
627
|
+
local changes_json
|
|
628
|
+
changes_json=$(parse_commits "$from_ref" "$to_ref")
|
|
629
|
+
|
|
630
|
+
local current_version
|
|
631
|
+
current_version=$(git -C "$REPO_DIR" describe --tags --abbrev=0 2>/dev/null || echo "0.1.0")
|
|
632
|
+
current_version="${current_version#v}"
|
|
633
|
+
|
|
634
|
+
local next_version
|
|
635
|
+
next_version=$(recommend_version "$changes_json" "$current_version")
|
|
636
|
+
|
|
637
|
+
local announcement
|
|
638
|
+
announcement=$(generate_announcement "$changes_json" "v${next_version}")
|
|
639
|
+
|
|
640
|
+
local output_file="${REPO_DIR}/ANNOUNCE-${next_version}.md"
|
|
641
|
+
echo "$announcement" > "$output_file"
|
|
642
|
+
success "Announcement generated: ${output_file}"
|
|
643
|
+
|
|
644
|
+
emit_event "changelog.announce" "version=$next_version"
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
cmd_formats() {
|
|
648
|
+
echo ""
|
|
649
|
+
echo -e "${BOLD}Available Output Formats:${RESET}"
|
|
650
|
+
echo ""
|
|
651
|
+
echo -e " ${CYAN}markdown${RESET} Release notes in markdown format (default)"
|
|
652
|
+
echo -e " ${CYAN}json${RESET} Structured changes in JSON"
|
|
653
|
+
echo -e " ${CYAN}html${RESET} HTML-formatted release notes"
|
|
654
|
+
echo -e " ${CYAN}text${RESET} Plain text format"
|
|
655
|
+
echo ""
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
show_help() {
|
|
659
|
+
echo -e "${CYAN}${BOLD}shipwright changelog${RESET} — Automated Release Notes & Migration Guides"
|
|
660
|
+
echo ""
|
|
661
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
662
|
+
echo -e " ${CYAN}shipwright changelog${RESET} <command> [options]"
|
|
663
|
+
echo ""
|
|
664
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
665
|
+
echo -e " ${CYAN}generate${RESET} Generate changelog since last release"
|
|
666
|
+
echo -e " ${CYAN}generate --from TAG --to TAG Changelog between specific tags"
|
|
667
|
+
echo -e " ${CYAN}preview${RESET} Preview next release notes without committing"
|
|
668
|
+
echo -e " ${CYAN}version${RESET} Recommend next semantic version"
|
|
669
|
+
echo -e " ${CYAN}migrate${RESET} Generate migration guide for breaking changes"
|
|
670
|
+
echo -e " ${CYAN}announce${RESET} Generate stakeholder announcement"
|
|
671
|
+
echo -e " ${CYAN}formats${RESET} List available output formats"
|
|
672
|
+
echo -e " ${CYAN}help${RESET} Show this help message"
|
|
673
|
+
echo ""
|
|
674
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
675
|
+
echo -e " ${DIM}shipwright changelog generate${RESET}"
|
|
676
|
+
echo -e " ${DIM}shipwright changelog preview${RESET}"
|
|
677
|
+
echo -e " ${DIM}shipwright changelog version${RESET}"
|
|
678
|
+
echo -e " ${DIM}shipwright changelog generate --from v1.0.0 --to v1.1.0${RESET}"
|
|
679
|
+
echo ""
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
main() {
|
|
683
|
+
local cmd="${1:-help}"
|
|
684
|
+
shift 2>/dev/null || true
|
|
685
|
+
|
|
686
|
+
case "$cmd" in
|
|
687
|
+
generate) cmd_generate "$@" ;;
|
|
688
|
+
preview) cmd_preview "$@" ;;
|
|
689
|
+
version) cmd_version "$@" ;;
|
|
690
|
+
migrate) cmd_migrate "$@" ;;
|
|
691
|
+
announce) cmd_announce "$@" ;;
|
|
692
|
+
formats) cmd_formats "$@" ;;
|
|
693
|
+
help|--help|-h) show_help ;;
|
|
694
|
+
*)
|
|
695
|
+
error "Unknown command: $cmd"
|
|
696
|
+
show_help
|
|
697
|
+
exit 1
|
|
698
|
+
;;
|
|
699
|
+
esac
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
703
|
+
main "$@"
|
|
704
|
+
fi
|