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,530 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ sw-widgets.sh — Embeddable Status Widgets ║
|
|
4
|
+
# ║ ║
|
|
5
|
+
# ║ Generate badges, Slack messages, markdown blocks, and JSON exports ║
|
|
6
|
+
# ║ for embedding Shipwright status in external dashboards and README ║
|
|
7
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
10
|
+
|
|
11
|
+
VERSION="2.0.0"
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
14
|
+
|
|
15
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
16
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
17
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
18
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
19
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
20
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
21
|
+
RED='\033[38;2;248;113;113m' # error
|
|
22
|
+
DIM='\033[2m'
|
|
23
|
+
BOLD='\033[1m'
|
|
24
|
+
RESET='\033[0m'
|
|
25
|
+
|
|
26
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
27
|
+
_COMPAT="$SCRIPT_DIR/lib/compat.sh"
|
|
28
|
+
# shellcheck source=lib/compat.sh
|
|
29
|
+
[[ -f "$_COMPAT" ]] && source "$_COMPAT"
|
|
30
|
+
|
|
31
|
+
# ─── Output Helpers ────────────────────────────────────────────────────────
|
|
32
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
33
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
34
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
35
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
36
|
+
|
|
37
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
38
|
+
|
|
39
|
+
# ─── Configuration ─────────────────────────────────────────────────────────
|
|
40
|
+
CONFIG_DIR="${HOME}/.shipwright"
|
|
41
|
+
CONFIG_FILE="${CONFIG_DIR}/widgets-config.json"
|
|
42
|
+
EVENTS_FILE="${CONFIG_DIR}/events.jsonl"
|
|
43
|
+
PIPELINE_STATE="${REPO_DIR}/.claude/pipeline-state.md"
|
|
44
|
+
COSTS_FILE="${CONFIG_DIR}/costs.json"
|
|
45
|
+
|
|
46
|
+
# ─── Helpers ───────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
# Safely extract numeric values
|
|
49
|
+
_safe_num() {
|
|
50
|
+
local val="${1:-0}"
|
|
51
|
+
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
52
|
+
echo "$val"
|
|
53
|
+
else
|
|
54
|
+
echo "0"
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Safely extract string values from JSON
|
|
59
|
+
_safe_str() {
|
|
60
|
+
local val="${1:-unknown}"
|
|
61
|
+
echo "$val" | sed 's/"/\\"/g'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Get current pipeline status
|
|
65
|
+
_get_pipeline_status() {
|
|
66
|
+
if [[ ! -f "$PIPELINE_STATE" ]]; then
|
|
67
|
+
echo "unknown"
|
|
68
|
+
return
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Try to extract status from pipeline state markdown
|
|
72
|
+
if grep -qi "status.*passing" "$PIPELINE_STATE" 2>/dev/null; then
|
|
73
|
+
echo "passing"
|
|
74
|
+
elif grep -qi "status.*failing" "$PIPELINE_STATE" 2>/dev/null; then
|
|
75
|
+
echo "failing"
|
|
76
|
+
elif grep -qi "status.*running" "$PIPELINE_STATE" 2>/dev/null; then
|
|
77
|
+
echo "running"
|
|
78
|
+
else
|
|
79
|
+
echo "unknown"
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Get test pass rate
|
|
84
|
+
_get_test_stats() {
|
|
85
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
86
|
+
echo "0"
|
|
87
|
+
return
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Count test-related events
|
|
91
|
+
local pass_count
|
|
92
|
+
pass_count=$(grep -i "test.*passed" "$EVENTS_FILE" 2>/dev/null | wc -l || echo "0")
|
|
93
|
+
pass_count=$(_safe_num "$pass_count")
|
|
94
|
+
|
|
95
|
+
echo "$pass_count"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Get current version
|
|
99
|
+
_get_version() {
|
|
100
|
+
if [[ -f "$REPO_DIR/package.json" ]]; then
|
|
101
|
+
jq -r '.version // "unknown"' "$REPO_DIR/package.json" 2>/dev/null || echo "unknown"
|
|
102
|
+
else
|
|
103
|
+
echo "unknown"
|
|
104
|
+
fi
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Get health score (0-100)
|
|
108
|
+
_get_health_score() {
|
|
109
|
+
if [[ ! -f "$EVENTS_FILE" ]]; then
|
|
110
|
+
echo "50"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Simple health calculation: recent successful stages vs failed
|
|
115
|
+
local recent_events
|
|
116
|
+
recent_events=$(tail -n 100 "$EVENTS_FILE" 2>/dev/null || true)
|
|
117
|
+
|
|
118
|
+
local success_count
|
|
119
|
+
success_count=$(echo "$recent_events" | grep -i "stage.*completed" | wc -l || echo "0")
|
|
120
|
+
success_count=$(_safe_num "$success_count")
|
|
121
|
+
|
|
122
|
+
local fail_count
|
|
123
|
+
fail_count=$(echo "$recent_events" | grep -i "stage.*failed" | wc -l || echo "0")
|
|
124
|
+
fail_count=$(_safe_num "$fail_count")
|
|
125
|
+
|
|
126
|
+
local total=$((success_count + fail_count))
|
|
127
|
+
if [[ $total -eq 0 ]]; then
|
|
128
|
+
echo "50"
|
|
129
|
+
else
|
|
130
|
+
awk "BEGIN {printf \"%.0f\", ($success_count / $total) * 100}"
|
|
131
|
+
fi
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# ─── Badge Generation (shields.io) ─────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
badge_pipeline() {
|
|
137
|
+
local status
|
|
138
|
+
status=$(_get_pipeline_status)
|
|
139
|
+
|
|
140
|
+
local color
|
|
141
|
+
case "$status" in
|
|
142
|
+
passing) color="brightgreen" ;;
|
|
143
|
+
failing) color="red" ;;
|
|
144
|
+
running) color="blue" ;;
|
|
145
|
+
*) color="lightgrey" ;;
|
|
146
|
+
esac
|
|
147
|
+
|
|
148
|
+
echo "https://img.shields.io/badge/pipeline-${status}-${color}"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
badge_tests() {
|
|
152
|
+
local count
|
|
153
|
+
count=$(_get_test_stats)
|
|
154
|
+
|
|
155
|
+
echo "https://img.shields.io/badge/tests-${count}%2B%20passing-brightgreen"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
badge_version() {
|
|
159
|
+
local version
|
|
160
|
+
version=$(_get_version)
|
|
161
|
+
|
|
162
|
+
# URL-encode dots as %2E
|
|
163
|
+
version="${version//./%2E}"
|
|
164
|
+
echo "https://img.shields.io/badge/version-v${version}-blue"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
badge_health() {
|
|
168
|
+
local score
|
|
169
|
+
score=$(_get_health_score)
|
|
170
|
+
|
|
171
|
+
local color
|
|
172
|
+
if [[ $score -ge 80 ]]; then
|
|
173
|
+
color="brightgreen"
|
|
174
|
+
elif [[ $score -ge 60 ]]; then
|
|
175
|
+
color="yellow"
|
|
176
|
+
else
|
|
177
|
+
color="red"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
echo "https://img.shields.io/badge/health-${score}%25-${color}"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# ─── Command: badge ──────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
cmd_badge() {
|
|
186
|
+
local type="${1:-pipeline}"
|
|
187
|
+
|
|
188
|
+
case "$type" in
|
|
189
|
+
pipeline)
|
|
190
|
+
badge_pipeline
|
|
191
|
+
;;
|
|
192
|
+
tests)
|
|
193
|
+
badge_tests
|
|
194
|
+
;;
|
|
195
|
+
version)
|
|
196
|
+
badge_version
|
|
197
|
+
;;
|
|
198
|
+
health)
|
|
199
|
+
badge_health
|
|
200
|
+
;;
|
|
201
|
+
all)
|
|
202
|
+
echo "Pipeline: $(badge_pipeline)"
|
|
203
|
+
echo "Tests: $(badge_tests)"
|
|
204
|
+
echo "Version: $(badge_version)"
|
|
205
|
+
echo "Health: $(badge_health)"
|
|
206
|
+
;;
|
|
207
|
+
*)
|
|
208
|
+
error "Unknown badge type: $type"
|
|
209
|
+
echo " Valid: pipeline, tests, version, health, all"
|
|
210
|
+
exit 1
|
|
211
|
+
;;
|
|
212
|
+
esac
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# ─── Command: slack ───────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
cmd_slack() {
|
|
218
|
+
local webhook_url=""
|
|
219
|
+
local channel=""
|
|
220
|
+
|
|
221
|
+
while [[ $# -gt 0 ]]; do
|
|
222
|
+
case "$1" in
|
|
223
|
+
--webhook)
|
|
224
|
+
webhook_url="$2"
|
|
225
|
+
shift 2
|
|
226
|
+
;;
|
|
227
|
+
--channel)
|
|
228
|
+
channel="$2"
|
|
229
|
+
shift 2
|
|
230
|
+
;;
|
|
231
|
+
*)
|
|
232
|
+
error "Unknown option: $1"
|
|
233
|
+
exit 1
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
done
|
|
237
|
+
|
|
238
|
+
# Try to load webhook from config if not provided
|
|
239
|
+
if [[ -z "$webhook_url" ]] && [[ -f "$CONFIG_FILE" ]]; then
|
|
240
|
+
webhook_url=$(jq -r '.slack.webhook_url // empty' "$CONFIG_FILE" 2>/dev/null || true)
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
if [[ -z "$webhook_url" ]]; then
|
|
244
|
+
error "No webhook URL provided. Use --webhook or configure in $CONFIG_FILE"
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# Gather status data
|
|
249
|
+
local status
|
|
250
|
+
status=$(_get_pipeline_status)
|
|
251
|
+
local tests
|
|
252
|
+
tests=$(_get_test_stats)
|
|
253
|
+
local health
|
|
254
|
+
health=$(_get_health_score)
|
|
255
|
+
|
|
256
|
+
# Determine color based on status
|
|
257
|
+
local color
|
|
258
|
+
case "$status" in
|
|
259
|
+
passing) color="#4ade80" ;;
|
|
260
|
+
failing) color="#f87171" ;;
|
|
261
|
+
running) color="#60a5fa" ;;
|
|
262
|
+
*) color="#9ca3af" ;;
|
|
263
|
+
esac
|
|
264
|
+
|
|
265
|
+
# Build Slack message
|
|
266
|
+
local message_json
|
|
267
|
+
message_json=$(jq -n \
|
|
268
|
+
--arg channel "$channel" \
|
|
269
|
+
--arg status "$status" \
|
|
270
|
+
--arg tests "$tests" \
|
|
271
|
+
--arg health "$health" \
|
|
272
|
+
--arg color "$color" \
|
|
273
|
+
'{
|
|
274
|
+
channel: $channel,
|
|
275
|
+
attachments: [
|
|
276
|
+
{
|
|
277
|
+
color: $color,
|
|
278
|
+
title: "Shipwright Pipeline Status",
|
|
279
|
+
fields: [
|
|
280
|
+
{title: "Status", value: $status, short: true},
|
|
281
|
+
{title: "Tests Passing", value: $tests, short: true},
|
|
282
|
+
{title: "Health Score", value: ($health + "%"), short: true},
|
|
283
|
+
{title: "Updated", value: "'$(now_iso)'", short: true}
|
|
284
|
+
],
|
|
285
|
+
footer: "Shipwright Status Widget",
|
|
286
|
+
ts: '$(date +%s)'
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}' | sed 's/"channel":""/"channel": null/' \
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Send to webhook
|
|
293
|
+
if command -v curl &>/dev/null; then
|
|
294
|
+
response=$(curl -s -X POST "$webhook_url" \
|
|
295
|
+
-H 'Content-Type: application/json' \
|
|
296
|
+
-d "$message_json" 2>&1)
|
|
297
|
+
|
|
298
|
+
if echo "$response" | grep -qi "ok"; then
|
|
299
|
+
success "Slack message sent"
|
|
300
|
+
else
|
|
301
|
+
warn "Slack response: $response"
|
|
302
|
+
fi
|
|
303
|
+
else
|
|
304
|
+
error "curl is required for Slack integration"
|
|
305
|
+
exit 1
|
|
306
|
+
fi
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# ─── Command: markdown ──────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
cmd_markdown() {
|
|
312
|
+
local pipeline_badge
|
|
313
|
+
local tests_badge
|
|
314
|
+
local version_badge
|
|
315
|
+
local health_badge
|
|
316
|
+
|
|
317
|
+
pipeline_badge=$(badge_pipeline)
|
|
318
|
+
tests_badge=$(badge_tests)
|
|
319
|
+
version_badge=$(badge_version)
|
|
320
|
+
health_badge=$(badge_health)
|
|
321
|
+
|
|
322
|
+
cat <<EOF
|
|
323
|
+
<!-- Shipwright Status Widgets -->
|
|
324
|
+
|
|
325
|
+
## Status Badges
|
|
326
|
+
|
|
327
|
+
[](./PIPELINE.md)
|
|
328
|
+
[](./TEST_RESULTS.md)
|
|
329
|
+
[](./CHANGELOG.md)
|
|
330
|
+
[](./HEALTH.md)
|
|
331
|
+
|
|
332
|
+
### Pipeline Status
|
|
333
|
+
- **Current Status**: $(_get_pipeline_status)
|
|
334
|
+
- **Tests Passing**: $(_get_test_stats)+
|
|
335
|
+
- **Health Score**: $(_get_health_score)%
|
|
336
|
+
- **Last Updated**: $(now_iso)
|
|
337
|
+
|
|
338
|
+
### Getting Started
|
|
339
|
+
To add these badges to your README.md:
|
|
340
|
+
|
|
341
|
+
\`\`\`markdown
|
|
342
|
+
[](./PIPELINE.md)
|
|
343
|
+
[](./TEST_RESULTS.md)
|
|
344
|
+
[](./CHANGELOG.md)
|
|
345
|
+
[](./HEALTH.md)
|
|
346
|
+
\`\`\`
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
Generated by [Shipwright](https://github.com/sethdford/shipwright)
|
|
350
|
+
EOF
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# ─── Command: json ──────────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
cmd_json() {
|
|
356
|
+
local pipeline_status
|
|
357
|
+
local test_stats
|
|
358
|
+
local health_score
|
|
359
|
+
local version
|
|
360
|
+
|
|
361
|
+
pipeline_status=$(_get_pipeline_status)
|
|
362
|
+
test_stats=$(_get_test_stats)
|
|
363
|
+
health_score=$(_get_health_score)
|
|
364
|
+
version=$(_get_version)
|
|
365
|
+
|
|
366
|
+
# Extract last deploy time from events if available
|
|
367
|
+
local last_deploy="unknown"
|
|
368
|
+
if [[ -f "$EVENTS_FILE" ]]; then
|
|
369
|
+
last_deploy=$(grep -i "deploy.*completed" "$EVENTS_FILE" | tail -1 | jq -r '.ts // "unknown"' 2>/dev/null || echo "unknown")
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Build JSON status
|
|
373
|
+
jq -n \
|
|
374
|
+
--arg timestamp "$(now_iso)" \
|
|
375
|
+
--arg status "$pipeline_status" \
|
|
376
|
+
--arg tests "$test_stats" \
|
|
377
|
+
--argjson health "$health_score" \
|
|
378
|
+
--arg version "$version" \
|
|
379
|
+
--arg last_deploy "$last_deploy" \
|
|
380
|
+
--arg pipeline_badge "$(badge_pipeline)" \
|
|
381
|
+
--arg tests_badge "$(badge_tests)" \
|
|
382
|
+
--arg version_badge "$(badge_version)" \
|
|
383
|
+
--arg health_badge "$(badge_health)" \
|
|
384
|
+
'{
|
|
385
|
+
timestamp: $timestamp,
|
|
386
|
+
pipeline: {
|
|
387
|
+
status: $status,
|
|
388
|
+
tests_passing: ($tests | tonumber),
|
|
389
|
+
health_score: $health,
|
|
390
|
+
last_deploy: $last_deploy
|
|
391
|
+
},
|
|
392
|
+
version: $version,
|
|
393
|
+
badges: {
|
|
394
|
+
pipeline: $pipeline_badge,
|
|
395
|
+
tests: $tests_badge,
|
|
396
|
+
version: $version_badge,
|
|
397
|
+
health: $health_badge
|
|
398
|
+
}
|
|
399
|
+
}'
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
# ─── Command: notify ──────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
cmd_notify() {
|
|
405
|
+
local notify_on="${1:-always}"
|
|
406
|
+
local status
|
|
407
|
+
status=$(_get_pipeline_status)
|
|
408
|
+
|
|
409
|
+
case "$notify_on" in
|
|
410
|
+
success)
|
|
411
|
+
if [[ "$status" == "passing" ]]; then
|
|
412
|
+
success "Pipeline is passing!"
|
|
413
|
+
fi
|
|
414
|
+
;;
|
|
415
|
+
failure)
|
|
416
|
+
if [[ "$status" == "failing" ]]; then
|
|
417
|
+
error "Pipeline is failing!"
|
|
418
|
+
exit 1
|
|
419
|
+
fi
|
|
420
|
+
;;
|
|
421
|
+
always)
|
|
422
|
+
info "Pipeline status: $status"
|
|
423
|
+
;;
|
|
424
|
+
*)
|
|
425
|
+
error "Unknown notify type: $notify_on"
|
|
426
|
+
exit 1
|
|
427
|
+
;;
|
|
428
|
+
esac
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
# ─── Command: help ───────────────────────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
cmd_help() {
|
|
434
|
+
cat <<EOF
|
|
435
|
+
${CYAN}${BOLD}shipwright widgets${RESET} — Embeddable Status Widgets
|
|
436
|
+
|
|
437
|
+
${BOLD}USAGE${RESET}
|
|
438
|
+
shipwright widgets <command> [options]
|
|
439
|
+
|
|
440
|
+
${BOLD}COMMANDS${RESET}
|
|
441
|
+
${CYAN}badge${RESET} [type] Generate shields.io badge URLs
|
|
442
|
+
Types: pipeline, tests, version, health, all
|
|
443
|
+
${CYAN}slack${RESET} [options] Send pipeline status to Slack
|
|
444
|
+
--webhook URL Slack webhook URL
|
|
445
|
+
--channel #ch (optional) channel override
|
|
446
|
+
${CYAN}markdown${RESET} Generate markdown status block for README
|
|
447
|
+
${CYAN}json${RESET} Export current status as JSON
|
|
448
|
+
${CYAN}notify${RESET} [type] Send notifications based on status
|
|
449
|
+
Types: success, failure, always
|
|
450
|
+
${CYAN}help${RESET} Show this help message
|
|
451
|
+
|
|
452
|
+
${BOLD}EXAMPLES${RESET}
|
|
453
|
+
# Generate pipeline badge URL
|
|
454
|
+
shipwright widgets badge pipeline
|
|
455
|
+
|
|
456
|
+
# Get all badges
|
|
457
|
+
shipwright widgets badge all
|
|
458
|
+
|
|
459
|
+
# Send Slack notification
|
|
460
|
+
shipwright widgets slack --webhook https://hooks.slack.com/... --channel #ops
|
|
461
|
+
|
|
462
|
+
# Generate markdown block for README
|
|
463
|
+
shipwright widgets markdown > STATUS.md
|
|
464
|
+
|
|
465
|
+
# Get JSON status for dashboards
|
|
466
|
+
shipwright widgets json | jq
|
|
467
|
+
|
|
468
|
+
# Notify if pipeline is passing
|
|
469
|
+
shipwright widgets notify success
|
|
470
|
+
|
|
471
|
+
${BOLD}CONFIGURATION${RESET}
|
|
472
|
+
Slack webhooks can be stored in: ${CONFIG_FILE}
|
|
473
|
+
|
|
474
|
+
Example:
|
|
475
|
+
{
|
|
476
|
+
"slack": {
|
|
477
|
+
"webhook_url": "https://hooks.slack.com/services/..."
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
${BOLD}INTEGRATION${RESET}
|
|
482
|
+
Use in CI/CD pipelines:
|
|
483
|
+
- GitHub Actions: Add badge URLs to job summaries
|
|
484
|
+
- README.md: Embed markdown status block
|
|
485
|
+
- External dashboards: Query JSON endpoint
|
|
486
|
+
- Slack: Post status updates on workflow completion
|
|
487
|
+
|
|
488
|
+
EOF
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
# ─── Main ──────────────────────────────────────────────────────────────────
|
|
492
|
+
|
|
493
|
+
main() {
|
|
494
|
+
local cmd="${1:-help}"
|
|
495
|
+
shift 2>/dev/null || true
|
|
496
|
+
|
|
497
|
+
case "$cmd" in
|
|
498
|
+
badge)
|
|
499
|
+
cmd_badge "$@"
|
|
500
|
+
;;
|
|
501
|
+
slack)
|
|
502
|
+
cmd_slack "$@"
|
|
503
|
+
;;
|
|
504
|
+
markdown)
|
|
505
|
+
cmd_markdown "$@"
|
|
506
|
+
;;
|
|
507
|
+
json)
|
|
508
|
+
cmd_json "$@"
|
|
509
|
+
;;
|
|
510
|
+
notify)
|
|
511
|
+
cmd_notify "$@"
|
|
512
|
+
;;
|
|
513
|
+
help|--help|-h)
|
|
514
|
+
cmd_help
|
|
515
|
+
;;
|
|
516
|
+
version|--version|-v)
|
|
517
|
+
echo "Shipwright widgets v${VERSION}"
|
|
518
|
+
;;
|
|
519
|
+
*)
|
|
520
|
+
error "Unknown command: $cmd"
|
|
521
|
+
echo " Try: shipwright widgets help"
|
|
522
|
+
exit 1
|
|
523
|
+
;;
|
|
524
|
+
esac
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
# ─── Guard: allow sourcing ────────────────────────────────────────────────────
|
|
528
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
529
|
+
main "$@"
|
|
530
|
+
fi
|
package/scripts/sw-worktree.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ║ Each agent gets its own worktree so parallel agents don't clobber ║
|
|
6
6
|
# ║ each other's files. Worktrees live in .worktrees/ relative to root. ║
|
|
7
7
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
8
|
-
VERSION="
|
|
8
|
+
VERSION="2.0.0"
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
11
11
|
|