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.
Files changed (108) hide show
  1. package/README.md +114 -36
  2. package/completions/_shipwright +212 -32
  3. package/completions/shipwright.bash +97 -25
  4. package/docs/strategy/01-market-research.md +619 -0
  5. package/docs/strategy/02-mission-and-brand.md +587 -0
  6. package/docs/strategy/03-gtm-and-roadmap.md +759 -0
  7. package/docs/strategy/QUICK-START.txt +289 -0
  8. package/docs/strategy/README.md +172 -0
  9. package/package.json +4 -2
  10. package/scripts/sw +208 -1
  11. package/scripts/sw-activity.sh +500 -0
  12. package/scripts/sw-adaptive.sh +925 -0
  13. package/scripts/sw-adversarial.sh +1 -1
  14. package/scripts/sw-architecture-enforcer.sh +1 -1
  15. package/scripts/sw-auth.sh +613 -0
  16. package/scripts/sw-autonomous.sh +664 -0
  17. package/scripts/sw-changelog.sh +704 -0
  18. package/scripts/sw-checkpoint.sh +1 -1
  19. package/scripts/sw-ci.sh +602 -0
  20. package/scripts/sw-cleanup.sh +1 -1
  21. package/scripts/sw-code-review.sh +637 -0
  22. package/scripts/sw-connect.sh +1 -1
  23. package/scripts/sw-context.sh +605 -0
  24. package/scripts/sw-cost.sh +1 -1
  25. package/scripts/sw-daemon.sh +432 -130
  26. package/scripts/sw-dashboard.sh +1 -1
  27. package/scripts/sw-db.sh +540 -0
  28. package/scripts/sw-decompose.sh +539 -0
  29. package/scripts/sw-deps.sh +551 -0
  30. package/scripts/sw-developer-simulation.sh +1 -1
  31. package/scripts/sw-discovery.sh +412 -0
  32. package/scripts/sw-docs-agent.sh +539 -0
  33. package/scripts/sw-docs.sh +1 -1
  34. package/scripts/sw-doctor.sh +59 -1
  35. package/scripts/sw-dora.sh +615 -0
  36. package/scripts/sw-durable.sh +710 -0
  37. package/scripts/sw-e2e-orchestrator.sh +535 -0
  38. package/scripts/sw-eventbus.sh +393 -0
  39. package/scripts/sw-feedback.sh +471 -0
  40. package/scripts/sw-fix.sh +1 -1
  41. package/scripts/sw-fleet-discover.sh +567 -0
  42. package/scripts/sw-fleet-viz.sh +404 -0
  43. package/scripts/sw-fleet.sh +8 -1
  44. package/scripts/sw-github-app.sh +596 -0
  45. package/scripts/sw-github-checks.sh +1 -1
  46. package/scripts/sw-github-deploy.sh +1 -1
  47. package/scripts/sw-github-graphql.sh +1 -1
  48. package/scripts/sw-guild.sh +569 -0
  49. package/scripts/sw-heartbeat.sh +1 -1
  50. package/scripts/sw-hygiene.sh +559 -0
  51. package/scripts/sw-incident.sh +617 -0
  52. package/scripts/sw-init.sh +88 -1
  53. package/scripts/sw-instrument.sh +699 -0
  54. package/scripts/sw-intelligence.sh +1 -1
  55. package/scripts/sw-jira.sh +1 -1
  56. package/scripts/sw-launchd.sh +363 -28
  57. package/scripts/sw-linear.sh +1 -1
  58. package/scripts/sw-logs.sh +1 -1
  59. package/scripts/sw-loop.sh +64 -3
  60. package/scripts/sw-memory.sh +1 -1
  61. package/scripts/sw-mission-control.sh +487 -0
  62. package/scripts/sw-model-router.sh +545 -0
  63. package/scripts/sw-otel.sh +596 -0
  64. package/scripts/sw-oversight.sh +689 -0
  65. package/scripts/sw-pipeline-composer.sh +1 -1
  66. package/scripts/sw-pipeline-vitals.sh +1 -1
  67. package/scripts/sw-pipeline.sh +687 -24
  68. package/scripts/sw-pm.sh +693 -0
  69. package/scripts/sw-pr-lifecycle.sh +522 -0
  70. package/scripts/sw-predictive.sh +1 -1
  71. package/scripts/sw-prep.sh +1 -1
  72. package/scripts/sw-ps.sh +1 -1
  73. package/scripts/sw-public-dashboard.sh +798 -0
  74. package/scripts/sw-quality.sh +595 -0
  75. package/scripts/sw-reaper.sh +1 -1
  76. package/scripts/sw-recruit.sh +573 -0
  77. package/scripts/sw-regression.sh +642 -0
  78. package/scripts/sw-release-manager.sh +736 -0
  79. package/scripts/sw-release.sh +706 -0
  80. package/scripts/sw-remote.sh +1 -1
  81. package/scripts/sw-replay.sh +520 -0
  82. package/scripts/sw-retro.sh +691 -0
  83. package/scripts/sw-scale.sh +444 -0
  84. package/scripts/sw-security-audit.sh +505 -0
  85. package/scripts/sw-self-optimize.sh +1 -1
  86. package/scripts/sw-session.sh +1 -1
  87. package/scripts/sw-setup.sh +1 -1
  88. package/scripts/sw-standup.sh +712 -0
  89. package/scripts/sw-status.sh +1 -1
  90. package/scripts/sw-strategic.sh +658 -0
  91. package/scripts/sw-stream.sh +450 -0
  92. package/scripts/sw-swarm.sh +583 -0
  93. package/scripts/sw-team-stages.sh +511 -0
  94. package/scripts/sw-templates.sh +1 -1
  95. package/scripts/sw-testgen.sh +515 -0
  96. package/scripts/sw-tmux-pipeline.sh +554 -0
  97. package/scripts/sw-tmux.sh +1 -1
  98. package/scripts/sw-trace.sh +485 -0
  99. package/scripts/sw-tracker-github.sh +188 -0
  100. package/scripts/sw-tracker-jira.sh +172 -0
  101. package/scripts/sw-tracker-linear.sh +251 -0
  102. package/scripts/sw-tracker.sh +117 -2
  103. package/scripts/sw-triage.sh +603 -0
  104. package/scripts/sw-upgrade.sh +1 -1
  105. package/scripts/sw-ux.sh +677 -0
  106. package/scripts/sw-webhook.sh +627 -0
  107. package/scripts/sw-widgets.sh +530 -0
  108. package/scripts/sw-worktree.sh +1 -1
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright e2e orchestrator — Test suite registry & execution ║
4
+ # ║ Smoke tests, integration tests, regression management, parallel exec ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+
8
+ VERSION="2.0.0"
9
+
10
+ # ─── Script directory resolution ────────────────────────────────────────────
11
+ SOURCE="${BASH_SOURCE[0]}"
12
+ while [[ -L "$SOURCE" ]]; do
13
+ DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
14
+ SOURCE="$(readlink "$SOURCE")"
15
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
16
+ done
17
+ SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
18
+
19
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
20
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
21
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
22
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
23
+ GREEN='\033[38;2;74;222;128m' # success
24
+ YELLOW='\033[38;2;250;204;21m' # warning
25
+ RED='\033[38;2;248;113;113m' # error
26
+ DIM='\033[2m'
27
+ BOLD='\033[1m'
28
+ RESET='\033[0m'
29
+
30
+ # ─── State directories ──────────────────────────────────────────────────────
31
+ E2E_DIR="${HOME}/.shipwright/e2e"
32
+ SUITE_REGISTRY="$E2E_DIR/suite-registry.json"
33
+ FLAKY_CACHE="$E2E_DIR/flaky-cache.json"
34
+ RESULTS_LOG="$E2E_DIR/results.jsonl"
35
+ LATEST_REPORT="$E2E_DIR/latest-report.json"
36
+
37
+ # ─── Helpers ─────────────────────────────────────────────────────────────────
38
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
39
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
40
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
41
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
42
+
43
+ emit_event() {
44
+ local event_type="$1"
45
+ shift
46
+ local data=""
47
+ for pair in "$@"; do
48
+ [[ -n "$data" ]] && data="$data "
49
+ data="$data$pair"
50
+ done
51
+ local timestamp=$(date -u +%s)
52
+ echo "{\"timestamp\":$timestamp,\"type\":\"$event_type\",$data}" >> "$RESULTS_LOG"
53
+ }
54
+
55
+ ensure_state_dir() {
56
+ mkdir -p "$E2E_DIR"
57
+ }
58
+
59
+ # ─── Initialize suite registry ──────────────────────────────────────────────
60
+ init_registry() {
61
+ ensure_state_dir
62
+ if [[ ! -f "$SUITE_REGISTRY" ]]; then
63
+ cat > "$SUITE_REGISTRY" <<'EOF'
64
+ {
65
+ "suites": [
66
+ {
67
+ "id": "smoke",
68
+ "name": "Smoke Tests",
69
+ "category": "smoke",
70
+ "description": "Quick validation (<30s): CLI routing, help text, basic commands",
71
+ "script": "sw-e2e-smoke-test.sh",
72
+ "features": ["cli-routing", "help-text", "basic-commands"],
73
+ "timeout_seconds": 30,
74
+ "enabled": true
75
+ },
76
+ {
77
+ "id": "integration",
78
+ "name": "Integration Tests",
79
+ "category": "integration",
80
+ "description": "Cross-component tests: pipeline→daemon, memory→pipeline, tracker→daemon",
81
+ "script": "sw-e2e-integration-test.sh",
82
+ "features": ["pipeline", "daemon", "memory", "tracker"],
83
+ "timeout_seconds": 600,
84
+ "enabled": true
85
+ },
86
+ {
87
+ "id": "regression",
88
+ "name": "Regression Suite",
89
+ "category": "regression",
90
+ "description": "Full regression suite: all known failures, edge cases",
91
+ "script": "sw-daemon-test.sh",
92
+ "features": ["daemon", "metrics", "health"],
93
+ "timeout_seconds": 300,
94
+ "enabled": true
95
+ }
96
+ ],
97
+ "flaky_tests": [],
98
+ "quarantine": [],
99
+ "last_updated": 0
100
+ }
101
+ EOF
102
+ success "Initialized suite registry at $SUITE_REGISTRY"
103
+ fi
104
+ }
105
+
106
+ # ─── Load and validate registry ─────────────────────────────────────────────
107
+ load_registry() {
108
+ ensure_state_dir
109
+ if [[ ! -f "$SUITE_REGISTRY" ]]; then
110
+ init_registry
111
+ fi
112
+ cat "$SUITE_REGISTRY"
113
+ }
114
+
115
+ # ─── Register a new test suite ──────────────────────────────────────────────
116
+ cmd_register() {
117
+ local suite_id="$1"
118
+ local suite_name="${2:-$suite_id}"
119
+ local category="${3:-custom}"
120
+ local features_str="${4:-}"
121
+
122
+ ensure_state_dir
123
+
124
+ if [[ ! -f "$SUITE_REGISTRY" ]]; then
125
+ init_registry
126
+ fi
127
+
128
+ # Parse existing registry
129
+ local registry=$(load_registry)
130
+ local new_suite
131
+
132
+ # Create feature array
133
+ local features="[]"
134
+ if [[ -n "$features_str" ]]; then
135
+ features=$(echo "$features_str" | jq -R 'split(",") | map(select(length > 0))')
136
+ fi
137
+
138
+ # Build new suite entry
139
+ new_suite=$(jq -n \
140
+ --arg id "$suite_id" \
141
+ --arg name "$suite_name" \
142
+ --arg cat "$category" \
143
+ --argjson feats "$features" \
144
+ '{
145
+ id: $id,
146
+ name: $name,
147
+ category: $cat,
148
+ description: "",
149
+ script: "sw-\($id)-test.sh",
150
+ features: $feats,
151
+ timeout_seconds: 300,
152
+ enabled: true
153
+ }')
154
+
155
+ # Check if suite already exists
156
+ if echo "$registry" | jq -e ".suites[] | select(.id == \"$suite_id\")" > /dev/null 2>&1; then
157
+ error "Suite '$suite_id' already registered"
158
+ return 1
159
+ fi
160
+
161
+ # Add to registry
162
+ registry=$(echo "$registry" | jq ".suites += [$new_suite] | .last_updated = $(date +%s)")
163
+
164
+ # Atomic write
165
+ local tmp_file
166
+ tmp_file=$(mktemp)
167
+ echo "$registry" | jq '.' > "$tmp_file"
168
+ mv "$tmp_file" "$SUITE_REGISTRY"
169
+
170
+ success "Registered suite: $suite_id"
171
+ }
172
+
173
+ # ─── Quarantine a flaky test ───────────────────────────────────────────────
174
+ cmd_quarantine() {
175
+ local test_name="$1"
176
+ local reason="${2:-Intermittent failures}"
177
+ local action="${3:-quarantine}" # quarantine or unquarantine
178
+
179
+ ensure_state_dir
180
+
181
+ if [[ ! -f "$SUITE_REGISTRY" ]]; then
182
+ init_registry
183
+ fi
184
+
185
+ local registry=$(load_registry)
186
+
187
+ if [[ "$action" == "quarantine" ]]; then
188
+ # Add to quarantine list if not already present
189
+ if ! echo "$registry" | jq -e ".quarantine[] | select(. == \"$test_name\")" > /dev/null 2>&1; then
190
+ registry=$(echo "$registry" | jq ".quarantine += [\"$test_name\"] | .last_updated = $(date +%s)")
191
+ local tmp_file
192
+ tmp_file=$(mktemp)
193
+ echo "$registry" | jq '.' > "$tmp_file"
194
+ mv "$tmp_file" "$SUITE_REGISTRY"
195
+ success "Quarantined: $test_name — $reason"
196
+ else
197
+ warn "$test_name already quarantined"
198
+ fi
199
+ else
200
+ # Remove from quarantine
201
+ registry=$(echo "$registry" | jq ".quarantine |= map(select(. != \"$test_name\")) | .last_updated = $(date +%s)")
202
+ local tmp_file
203
+ tmp_file=$(mktemp)
204
+ echo "$registry" | jq '.' > "$tmp_file"
205
+ mv "$tmp_file" "$SUITE_REGISTRY"
206
+ success "Unquarantined: $test_name"
207
+ fi
208
+ }
209
+
210
+ # ─── Run a single test suite ────────────────────────────────────────────────
211
+ run_suite() {
212
+ local suite_id="$1"
213
+ local registry=$(load_registry)
214
+
215
+ # Find suite
216
+ local suite=$(echo "$registry" | jq ".suites[] | select(.id == \"$suite_id\")")
217
+
218
+ if [[ -z "$suite" ]]; then
219
+ error "Suite not found: $suite_id"
220
+ return 1
221
+ fi
222
+
223
+ local suite_name=$(echo "$suite" | jq -r '.name')
224
+ local script=$(echo "$suite" | jq -r '.script')
225
+ local timeout=$(echo "$suite" | jq -r '.timeout_seconds')
226
+ local features=$(echo "$suite" | jq -r '.features | join(", ")')
227
+
228
+ local test_script="$SCRIPT_DIR/$script"
229
+
230
+ if [[ ! -f "$test_script" ]]; then
231
+ error "Test script not found: $test_script"
232
+ return 1
233
+ fi
234
+
235
+ info "Running: $suite_name ($features)"
236
+ local start_time=$(date +%s)
237
+
238
+ # Run with timeout
239
+ local exit_code=0
240
+ timeout "$timeout" bash "$test_script" || exit_code=$?
241
+
242
+ local end_time=$(date +%s)
243
+ local duration=$((end_time - start_time))
244
+
245
+ # Log result
246
+ emit_event "suite_complete" \
247
+ "\"suite_id\":\"$suite_id\"" \
248
+ "\"suite_name\":\"$suite_name\"" \
249
+ "\"exit_code\":$exit_code" \
250
+ "\"duration_seconds\":$duration"
251
+
252
+ return $exit_code
253
+ }
254
+
255
+ # ─── Run suites in parallel ─────────────────────────────────────────────────
256
+ run_parallel() {
257
+ local category="${1:-}"
258
+ local max_parallel=${2:-3}
259
+
260
+ ensure_state_dir
261
+ > "$RESULTS_LOG" # Clear results log
262
+
263
+ local registry=$(load_registry)
264
+
265
+ # Filter suites by category and enabled status
266
+ local suites
267
+ if [[ -n "$category" ]]; then
268
+ suites=$(echo "$registry" | jq -r ".suites[] | select(.category == \"$category\" and .enabled) | .id")
269
+ else
270
+ suites=$(echo "$registry" | jq -r ".suites[] | select(.enabled) | .id")
271
+ fi
272
+
273
+ local suite_array=()
274
+ while IFS= read -r suite; do
275
+ [[ -n "$suite" ]] && suite_array+=("$suite")
276
+ done <<< "$suites"
277
+
278
+ if [[ ${#suite_array[@]} -eq 0 ]]; then
279
+ warn "No suites to run"
280
+ return 0
281
+ fi
282
+
283
+ info "Running ${#suite_array[@]} test suite(s) in parallel (max $max_parallel workers)"
284
+
285
+ local pids=()
286
+ local running=0
287
+ local idx=0
288
+ local failed_suites=()
289
+
290
+ # Spawn initial batch
291
+ for (( i = 0; i < max_parallel && i < ${#suite_array[@]}; i++ )); do
292
+ run_suite "${suite_array[$i]}" &
293
+ pids+=($!)
294
+ ((running++))
295
+ ((idx++))
296
+ done
297
+
298
+ # Process remaining suites as workers finish
299
+ while [[ $running -gt 0 ]]; do
300
+ for i in "${!pids[@]}"; do
301
+ if ! kill -0 "${pids[$i]}" 2>/dev/null; then
302
+ # Process finished
303
+ wait "${pids[$i]}" || failed_suites+=("${suite_array[$((i + max_parallel - running))]}")
304
+
305
+ # Spawn next if available
306
+ if [[ $idx -lt ${#suite_array[@]} ]]; then
307
+ run_suite "${suite_array[$idx]}" &
308
+ pids[$i]=$!
309
+ ((idx++))
310
+ else
311
+ unset 'pids[$i]'
312
+ fi
313
+ ((running--))
314
+ fi
315
+ done
316
+ sleep 0.1
317
+ done
318
+
319
+ # Wait for all to finish
320
+ local exit_code=0
321
+ for pid in "${pids[@]}"; do
322
+ wait "$pid" || exit_code=1
323
+ done
324
+
325
+ return $exit_code
326
+ }
327
+
328
+ # ─── Generate test report ──────────────────────────────────────────────────
329
+ cmd_report() {
330
+ ensure_state_dir
331
+
332
+ if [[ ! -f "$RESULTS_LOG" ]]; then
333
+ warn "No test results found"
334
+ return 0
335
+ fi
336
+
337
+ info "Generating test report..."
338
+
339
+ local pass=0
340
+ local fail=0
341
+ local timeout=0
342
+ local skip=0
343
+
344
+ while IFS= read -r line; do
345
+ local exit_code=$(echo "$line" | jq -r '.exit_code // 0')
346
+ if [[ $exit_code -eq 0 ]]; then
347
+ ((pass++))
348
+ elif [[ $exit_code -eq 124 ]]; then
349
+ ((timeout++))
350
+ else
351
+ ((fail++))
352
+ fi
353
+ done < "$RESULTS_LOG"
354
+
355
+ local total=$((pass + fail + timeout + skip))
356
+
357
+ # Create report
358
+ local report=$(jq -n \
359
+ --arg ts "$(date -Iseconds)" \
360
+ --arg version "$VERSION" \
361
+ --argjson p "$pass" \
362
+ --argjson f "$fail" \
363
+ --argjson t "$timeout" \
364
+ --argjson s "$skip" \
365
+ --argjson total "$total" \
366
+ '{
367
+ timestamp: $ts,
368
+ version: $version,
369
+ summary: {
370
+ total: $total,
371
+ passed: $p,
372
+ failed: $f,
373
+ timeout: $t,
374
+ skipped: $s,
375
+ pass_rate: ($p / $total * 100 | round | tostring + "%")
376
+ },
377
+ details: input
378
+ }' < <(jq -s '.' "$RESULTS_LOG"))
379
+
380
+ # Atomic write
381
+ local tmp_file
382
+ tmp_file=$(mktemp)
383
+ echo "$report" | jq '.' > "$tmp_file"
384
+ mv "$tmp_file" "$LATEST_REPORT"
385
+
386
+ # Display
387
+ echo ""
388
+ echo -e "${CYAN}${BOLD}━━━ Test Report ━━━${RESET}"
389
+ echo "$report" | jq '.summary'
390
+ echo ""
391
+
392
+ if [[ $fail -gt 0 ]]; then
393
+ error "Tests failed: $fail/$total"
394
+ return 1
395
+ else
396
+ success "All tests passed: $total/$total"
397
+ return 0
398
+ fi
399
+ }
400
+
401
+ # ─── Show flaky test analysis ───────────────────────────────────────────────
402
+ cmd_flaky() {
403
+ ensure_state_dir
404
+
405
+ if [[ ! -f "$RESULTS_LOG" ]]; then
406
+ warn "No test history found"
407
+ return 0
408
+ fi
409
+
410
+ info "Analyzing flaky tests..."
411
+
412
+ # Group by test name, count passes/fails
413
+ local flaky_analysis=$(jq -s 'group_by(.suite_id) | map({
414
+ test: .[0].suite_id,
415
+ runs: length,
416
+ passes: (map(select(.exit_code == 0)) | length),
417
+ failures: (map(select(.exit_code != 0)) | length)
418
+ }) | map(select(.failures > 0 and .passes > 0))' "$RESULTS_LOG" 2>/dev/null || echo '[]')
419
+
420
+ echo ""
421
+ echo -e "${CYAN}${BOLD}━━━ Flaky Tests ━━━${RESET}"
422
+ echo "$flaky_analysis" | jq '.'
423
+
424
+ if [[ $(echo "$flaky_analysis" | jq 'length') -gt 0 ]]; then
425
+ warn "Found intermittent failures — consider quarantine"
426
+ else
427
+ success "No flaky tests detected"
428
+ fi
429
+ }
430
+
431
+ # ─── Main command routing ───────────────────────────────────────────────────
432
+ show_help() {
433
+ cat <<EOF
434
+ ${CYAN}${BOLD}shipwright e2e${RESET} — End-to-end test orchestrator
435
+
436
+ ${BOLD}USAGE${RESET}
437
+ shipwright e2e <command> [options]
438
+
439
+ ${BOLD}COMMANDS${RESET}
440
+ ${CYAN}run${RESET} [category] Run all test suites (or filtered by category)
441
+ ${CYAN}smoke${RESET} Run quick smoke test suite
442
+ ${CYAN}integration${RESET} Run integration test suite
443
+ ${CYAN}regression${RESET} Run full regression test suite
444
+ ${CYAN}register${RESET} <id> [...] Register a new test suite
445
+ ${CYAN}quarantine${RESET} <name> Quarantine a flaky test
446
+ ${CYAN}unquarantine${RESET} <name> Unquarantine a test
447
+ ${CYAN}report${RESET} Generate test result report
448
+ ${CYAN}flaky${RESET} Show flaky test analysis
449
+ ${CYAN}help${RESET} Show this help message
450
+
451
+ ${BOLD}EXAMPLES${RESET}
452
+ ${DIM}shipwright e2e run${RESET} # Run all suites
453
+ ${DIM}shipwright e2e run smoke${RESET} # Run only smoke tests
454
+ ${DIM}shipwright e2e smoke${RESET} # Quick validation
455
+ ${DIM}shipwright e2e register custom-test Custom${RESET}
456
+ ${DIM}shipwright e2e quarantine flaky_test${RESET}
457
+ ${DIM}shipwright e2e report${RESET} # Show latest results
458
+ ${DIM}shipwright e2e flaky${RESET} # Analyze intermittent failures
459
+
460
+ ${BOLD}ENVIRONMENT${RESET}
461
+ State directory: $E2E_DIR
462
+ Registry: $SUITE_REGISTRY
463
+ Results log: $RESULTS_LOG
464
+ Report: $LATEST_REPORT
465
+
466
+ ${DIM}Version: $VERSION${RESET}
467
+ EOF
468
+ }
469
+
470
+ main() {
471
+ local cmd="${1:-help}"
472
+ shift 2>/dev/null || true
473
+
474
+ ensure_state_dir
475
+
476
+ case "$cmd" in
477
+ run)
478
+ init_registry
479
+ run_parallel "$@"
480
+ cmd_report
481
+ ;;
482
+ smoke)
483
+ init_registry
484
+ run_suite "smoke"
485
+ ;;
486
+ integration)
487
+ init_registry
488
+ run_suite "integration"
489
+ ;;
490
+ regression)
491
+ init_registry
492
+ run_suite "regression"
493
+ ;;
494
+ register)
495
+ init_registry
496
+ cmd_register "$@"
497
+ ;;
498
+ quarantine)
499
+ quarantine_test="${1:-}"
500
+ if [[ -z "$quarantine_test" ]]; then
501
+ error "Usage: e2e quarantine <test-name>"
502
+ return 1
503
+ fi
504
+ cmd_quarantine "$quarantine_test" "$2" "quarantine"
505
+ ;;
506
+ unquarantine)
507
+ test_name="${1:-}"
508
+ if [[ -z "$test_name" ]]; then
509
+ error "Usage: e2e unquarantine <test-name>"
510
+ return 1
511
+ fi
512
+ cmd_quarantine "$test_name" "" "unquarantine"
513
+ ;;
514
+ report)
515
+ cmd_report
516
+ ;;
517
+ flaky)
518
+ cmd_flaky
519
+ ;;
520
+ help|--help|-h)
521
+ show_help
522
+ ;;
523
+ *)
524
+ error "Unknown command: $cmd"
525
+ echo ""
526
+ show_help
527
+ return 1
528
+ ;;
529
+ esac
530
+ }
531
+
532
+ # Source guard: allow sourcing this script
533
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
534
+ main "$@"
535
+ fi