shipwright-cli 1.7.1 → 1.10.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 (115) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +45 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +118 -22
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +468 -0
  33. package/scripts/sw-cleanup.sh +359 -0
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/sw-daemon.sh +5574 -0
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/{cct-init.sh → sw-init.sh} +144 -11
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/sw-loop.sh +2217 -0
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +514 -36
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/sw-pipeline-vitals.sh +1096 -0
  58. package/scripts/sw-pipeline.sh +7593 -0
  59. package/scripts/sw-predictive.sh +820 -0
  60. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  61. package/scripts/{cct-ps.sh → sw-ps.sh} +9 -6
  62. package/scripts/{cct-reaper.sh → sw-reaper.sh} +10 -6
  63. package/scripts/sw-remote.sh +687 -0
  64. package/scripts/sw-self-optimize.sh +1048 -0
  65. package/scripts/sw-session.sh +541 -0
  66. package/scripts/sw-setup.sh +234 -0
  67. package/scripts/sw-status.sh +796 -0
  68. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  69. package/scripts/sw-tmux.sh +591 -0
  70. package/scripts/sw-tracker-jira.sh +277 -0
  71. package/scripts/sw-tracker-linear.sh +292 -0
  72. package/scripts/sw-tracker.sh +409 -0
  73. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  74. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  75. package/templates/pipelines/autonomous.json +35 -6
  76. package/templates/pipelines/cost-aware.json +21 -0
  77. package/templates/pipelines/deployed.json +40 -6
  78. package/templates/pipelines/enterprise.json +16 -2
  79. package/templates/pipelines/fast.json +19 -0
  80. package/templates/pipelines/full.json +28 -2
  81. package/templates/pipelines/hotfix.json +19 -0
  82. package/templates/pipelines/standard.json +31 -0
  83. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  84. package/tmux/templates/accessibility.json +34 -0
  85. package/tmux/templates/api-design.json +35 -0
  86. package/tmux/templates/architecture.json +1 -0
  87. package/tmux/templates/bug-fix.json +9 -0
  88. package/tmux/templates/code-review.json +1 -0
  89. package/tmux/templates/compliance.json +36 -0
  90. package/tmux/templates/data-pipeline.json +36 -0
  91. package/tmux/templates/debt-paydown.json +34 -0
  92. package/tmux/templates/devops.json +1 -0
  93. package/tmux/templates/documentation.json +1 -0
  94. package/tmux/templates/exploration.json +1 -0
  95. package/tmux/templates/feature-dev.json +1 -0
  96. package/tmux/templates/full-stack.json +8 -0
  97. package/tmux/templates/i18n.json +34 -0
  98. package/tmux/templates/incident-response.json +36 -0
  99. package/tmux/templates/migration.json +1 -0
  100. package/tmux/templates/observability.json +35 -0
  101. package/tmux/templates/onboarding.json +33 -0
  102. package/tmux/templates/performance.json +35 -0
  103. package/tmux/templates/refactor.json +1 -0
  104. package/tmux/templates/release.json +35 -0
  105. package/tmux/templates/security-audit.json +8 -0
  106. package/tmux/templates/spike.json +34 -0
  107. package/tmux/templates/testing.json +1 -0
  108. package/tmux/tmux.conf +98 -9
  109. package/scripts/cct-cleanup.sh +0 -172
  110. package/scripts/cct-daemon.sh +0 -3189
  111. package/scripts/cct-doctor.sh +0 -414
  112. package/scripts/cct-loop.sh +0 -1332
  113. package/scripts/cct-pipeline.sh +0 -3844
  114. package/scripts/cct-session.sh +0 -284
  115. package/scripts/cct-status.sh +0 -169
@@ -0,0 +1,533 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright github-deploy — Native GitHub Deployments API Integration ║
4
+ # ║ Environment tracking · Rollbacks · Pipeline deploy integration ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
8
+
9
+ VERSION="1.10.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
+ # ─── Output 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
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
35
+ now_epoch() { date +%s; }
36
+
37
+ # ─── Structured Event Log ────────────────────────────────────────────────────
38
+ EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
39
+
40
+ emit_event() {
41
+ local event_type="$1"
42
+ shift
43
+ local json_fields=""
44
+ for kv in "$@"; do
45
+ local key="${kv%%=*}"
46
+ local val="${kv#*=}"
47
+ if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
48
+ json_fields="${json_fields},\"${key}\":${val}"
49
+ else
50
+ val="${val//\"/\\\"}"
51
+ json_fields="${json_fields},\"${key}\":\"${val}\""
52
+ fi
53
+ done
54
+ mkdir -p "${HOME}/.shipwright"
55
+ echo "{\"ts\":\"$(now_iso)\",\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
56
+ }
57
+
58
+ # ─── Artifacts Directory ─────────────────────────────────────────────────────
59
+ ARTIFACTS_DIR="${REPO_DIR}/.claude/pipeline-artifacts"
60
+
61
+ # ─── Auto-detect owner/repo from git remote ──────────────────────────────────
62
+ _gh_detect_repo() {
63
+ local remote_url
64
+ remote_url=$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)
65
+ if [[ -z "$remote_url" ]]; then
66
+ echo ""
67
+ return 1
68
+ fi
69
+ local owner_repo
70
+ owner_repo=$(echo "$remote_url" | sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##')
71
+ echo "$owner_repo"
72
+ }
73
+
74
+ # ═══════════════════════════════════════════════════════════════════════════════
75
+ # DEPLOYMENTS API FUNCTIONS
76
+ # ═══════════════════════════════════════════════════════════════════════════════
77
+
78
+ # ─── Create a deployment ──────────────────────────────────────────────────────
79
+ gh_deploy_create() {
80
+ local owner="${1:-}"
81
+ local repo="${2:-}"
82
+ local ref="${3:-}"
83
+ local environment="${4:-production}"
84
+ local description="${5:-}"
85
+ local auto_merge="${6:-false}"
86
+
87
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
88
+ return 0
89
+ fi
90
+
91
+ if [[ -z "$owner" || -z "$repo" || -z "$ref" ]]; then
92
+ error "Usage: gh_deploy_create <owner> <repo> <ref> [environment] [description] [auto_merge]"
93
+ return 1
94
+ fi
95
+
96
+ local body
97
+ body=$(jq -n \
98
+ --arg ref "$ref" \
99
+ --arg env "$environment" \
100
+ --arg desc "$description" \
101
+ --argjson auto_merge "${auto_merge}" \
102
+ '{
103
+ ref: $ref,
104
+ environment: $env,
105
+ description: $desc,
106
+ auto_merge: $auto_merge,
107
+ required_contexts: []
108
+ }')
109
+
110
+ local response=""
111
+ local result=0
112
+ response=$(gh api "repos/${owner}/${repo}/deployments" \
113
+ --method POST \
114
+ --input - <<< "$body" 2>/dev/null) || result=$?
115
+
116
+ if [[ "$result" -ne 0 ]]; then
117
+ warn "Failed to create deployment for ref '${ref}' to '${environment}' (API returned ${result})" >&2
118
+ echo ""
119
+ return 0
120
+ fi
121
+
122
+ local deploy_id
123
+ deploy_id=$(echo "$response" | jq -r '.id // empty' 2>/dev/null || true)
124
+
125
+ if [[ -n "$deploy_id" && "$deploy_id" != "null" ]]; then
126
+ emit_event "deploy.create" "deploy_id=$deploy_id" "ref=$ref" "environment=$environment"
127
+ echo "$deploy_id"
128
+ else
129
+ warn "Deployment created but no ID returned" >&2
130
+ echo ""
131
+ fi
132
+ }
133
+
134
+ # ─── Update deployment status ─────────────────────────────────────────────────
135
+ gh_deploy_update_status() {
136
+ local owner="${1:-}"
137
+ local repo="${2:-}"
138
+ local deploy_id="${3:-}"
139
+ local state="${4:-}"
140
+ local environment_url="${5:-}"
141
+ local description="${6:-}"
142
+ local log_url="${7:-}"
143
+
144
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
145
+ return 0
146
+ fi
147
+
148
+ # Skip silently if deploy_id is empty
149
+ if [[ -z "$deploy_id" ]]; then
150
+ return 0
151
+ fi
152
+
153
+ if [[ -z "$owner" || -z "$repo" || -z "$state" ]]; then
154
+ error "Usage: gh_deploy_update_status <owner> <repo> <deploy_id> <state> [env_url] [description] [log_url]"
155
+ return 1
156
+ fi
157
+
158
+ local body
159
+ body=$(jq -n \
160
+ --arg state "$state" \
161
+ --arg env_url "$environment_url" \
162
+ --arg desc "$description" \
163
+ --arg log_url "$log_url" \
164
+ '{state: $state}
165
+ + (if $env_url != "" then {environment_url: $env_url} else {} end)
166
+ + (if $desc != "" then {description: $desc} else {} end)
167
+ + (if $log_url != "" then {log_url: $log_url} else {} end)')
168
+
169
+ local result=0
170
+ gh api "repos/${owner}/${repo}/deployments/${deploy_id}/statuses" \
171
+ --method POST \
172
+ --input - <<< "$body" --silent 2>/dev/null || result=$?
173
+
174
+ if [[ "$result" -ne 0 ]]; then
175
+ warn "Failed to update deployment status for ${deploy_id}"
176
+ else
177
+ emit_event "deploy.status" "deploy_id=$deploy_id" "state=$state"
178
+ fi
179
+ }
180
+
181
+ # ─── List deployments ─────────────────────────────────────────────────────────
182
+ gh_deploy_list() {
183
+ local owner="${1:-}"
184
+ local repo="${2:-}"
185
+ local environment="${3:-}"
186
+ local limit="${4:-10}"
187
+
188
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
189
+ echo "[]"
190
+ return 0
191
+ fi
192
+
193
+ if [[ -z "$owner" || -z "$repo" ]]; then
194
+ error "Usage: gh_deploy_list <owner> <repo> [environment] [limit]"
195
+ return 1
196
+ fi
197
+
198
+ local endpoint="repos/${owner}/${repo}/deployments?per_page=${limit}"
199
+ if [[ -n "$environment" ]]; then
200
+ endpoint="${endpoint}&environment=${environment}"
201
+ fi
202
+
203
+ local response=""
204
+ local result=0
205
+ response=$(gh api "$endpoint" 2>/dev/null) || result=$?
206
+
207
+ if [[ "$result" -ne 0 ]]; then
208
+ warn "Failed to list deployments" >&2
209
+ echo "[]"
210
+ return 0
211
+ fi
212
+
213
+ echo "$response" | jq '[.[] | {id, ref, environment, description, created_at}]' 2>/dev/null || echo "[]"
214
+ }
215
+
216
+ # ─── Get latest deployment ────────────────────────────────────────────────────
217
+ gh_deploy_latest() {
218
+ local owner="${1:-}"
219
+ local repo="${2:-}"
220
+ local environment="${3:-production}"
221
+
222
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
223
+ echo "{}"
224
+ return 0
225
+ fi
226
+
227
+ if [[ -z "$owner" || -z "$repo" ]]; then
228
+ error "Usage: gh_deploy_latest <owner> <repo> [environment]"
229
+ return 1
230
+ fi
231
+
232
+ local response=""
233
+ local result=0
234
+ response=$(gh api "repos/${owner}/${repo}/deployments?environment=${environment}&per_page=1" 2>/dev/null) || result=$?
235
+
236
+ if [[ "$result" -ne 0 ]]; then
237
+ warn "Failed to get latest deployment" >&2
238
+ echo "{}"
239
+ return 0
240
+ fi
241
+
242
+ local first
243
+ first=$(echo "$response" | jq '.[0] // {}' 2>/dev/null || echo "{}")
244
+ echo "$first"
245
+ }
246
+
247
+ # ─── Rollback to previous deployment ──────────────────────────────────────────
248
+ gh_deploy_rollback() {
249
+ local owner="${1:-}"
250
+ local repo="${2:-}"
251
+ local environment="${3:-production}"
252
+ local description="${4:-Rollback via Shipwright}"
253
+
254
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
255
+ return 0
256
+ fi
257
+
258
+ if [[ -z "$owner" || -z "$repo" ]]; then
259
+ error "Usage: gh_deploy_rollback <owner> <repo> [environment] [description]"
260
+ return 1
261
+ fi
262
+
263
+ # Get the two most recent deployments
264
+ local deployments=""
265
+ local result=0
266
+ deployments=$(gh api "repos/${owner}/${repo}/deployments?environment=${environment}&per_page=2" 2>/dev/null) || result=$?
267
+
268
+ if [[ "$result" -ne 0 ]]; then
269
+ error "Failed to fetch deployments for rollback"
270
+ return 1
271
+ fi
272
+
273
+ local count
274
+ count=$(echo "$deployments" | jq 'length' 2>/dev/null || echo "0")
275
+
276
+ if [[ "$count" -lt 2 ]]; then
277
+ error "Not enough deployments to rollback (found ${count}, need at least 2)"
278
+ return 1
279
+ fi
280
+
281
+ # Get the previous (second) deployment's ref
282
+ local prev_ref
283
+ prev_ref=$(echo "$deployments" | jq -r '.[1].ref // empty' 2>/dev/null || true)
284
+
285
+ if [[ -z "$prev_ref" || "$prev_ref" == "null" ]]; then
286
+ error "Could not determine previous deployment ref"
287
+ return 1
288
+ fi
289
+
290
+ info "Rolling back to ref: ${prev_ref}" >&2
291
+
292
+ # Mark current deployment as inactive
293
+ local current_id
294
+ current_id=$(echo "$deployments" | jq -r '.[0].id // empty' 2>/dev/null || true)
295
+ if [[ -n "$current_id" && "$current_id" != "null" ]]; then
296
+ gh_deploy_update_status "$owner" "$repo" "$current_id" "inactive" "" "Superseded by rollback"
297
+ fi
298
+
299
+ # Create new deployment with previous ref
300
+ local new_id
301
+ new_id=$(gh_deploy_create "$owner" "$repo" "$prev_ref" "$environment" "$description")
302
+
303
+ if [[ -n "$new_id" ]]; then
304
+ gh_deploy_update_status "$owner" "$repo" "$new_id" "success" "" "$description"
305
+ emit_event "deploy.rollback" "new_id=$new_id" "prev_ref=$prev_ref" "environment=$environment"
306
+ success "Rolled back to ${prev_ref} (deployment ${new_id})" >&2
307
+ echo "$new_id"
308
+ else
309
+ error "Failed to create rollback deployment"
310
+ return 1
311
+ fi
312
+ }
313
+
314
+ # ─── Start pipeline deployment ────────────────────────────────────────────────
315
+ gh_deploy_pipeline_start() {
316
+ local owner="${1:-}"
317
+ local repo="${2:-}"
318
+ local ref="${3:-}"
319
+ local environment="${4:-production}"
320
+
321
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
322
+ echo ""
323
+ return 0
324
+ fi
325
+
326
+ if [[ -z "$owner" || -z "$repo" || -z "$ref" ]]; then
327
+ error "Usage: gh_deploy_pipeline_start <owner> <repo> <ref> [environment]"
328
+ return 1
329
+ fi
330
+
331
+ local deploy_id
332
+ deploy_id=$(gh_deploy_create "$owner" "$repo" "$ref" "$environment" "Shipwright pipeline deployment")
333
+
334
+ if [[ -z "$deploy_id" ]]; then
335
+ warn "Could not create pipeline deployment" >&2
336
+ echo ""
337
+ return 0
338
+ fi
339
+
340
+ # Set status to in_progress
341
+ gh_deploy_update_status "$owner" "$repo" "$deploy_id" "in_progress" "" "Pipeline running"
342
+
343
+ # Store deployment ID atomically
344
+ mkdir -p "$ARTIFACTS_DIR"
345
+ local tmp_file
346
+ tmp_file=$(mktemp "${ARTIFACTS_DIR}/deployment.XXXXXX")
347
+ jq -n \
348
+ --arg id "$deploy_id" \
349
+ --arg env "$environment" \
350
+ --arg ref "$ref" \
351
+ --arg started_at "$(now_iso)" \
352
+ '{deploy_id: $id, environment: $env, ref: $ref, started_at: $started_at}' > "$tmp_file"
353
+ mv "$tmp_file" "${ARTIFACTS_DIR}/deployment.json"
354
+
355
+ emit_event "deploy.pipeline_start" "deploy_id=$deploy_id" "ref=$ref" "environment=$environment"
356
+ echo "$deploy_id"
357
+ }
358
+
359
+ # ─── Complete pipeline deployment ─────────────────────────────────────────────
360
+ gh_deploy_pipeline_complete() {
361
+ local owner="${1:-}"
362
+ local repo="${2:-}"
363
+ local environment="${3:-production}"
364
+ local is_success="${4:-true}"
365
+ local env_url="${5:-}"
366
+
367
+ if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
368
+ return 0
369
+ fi
370
+
371
+ local deploy_file="${ARTIFACTS_DIR}/deployment.json"
372
+ if [[ ! -f "$deploy_file" ]]; then
373
+ warn "No deployment.json found — skipping pipeline complete"
374
+ return 0
375
+ fi
376
+
377
+ local deploy_id
378
+ deploy_id=$(jq -r '.deploy_id // empty' "$deploy_file" 2>/dev/null || true)
379
+
380
+ if [[ -z "$deploy_id" || "$deploy_id" == "null" ]]; then
381
+ warn "No deployment ID in deployment.json"
382
+ return 0
383
+ fi
384
+
385
+ if [[ -z "$owner" || -z "$repo" ]]; then
386
+ local detected
387
+ detected=$(_gh_detect_repo || true)
388
+ if [[ -n "$detected" ]]; then
389
+ owner="${detected%%/*}"
390
+ repo="${detected##*/}"
391
+ fi
392
+ fi
393
+
394
+ if [[ -z "$owner" || -z "$repo" ]]; then
395
+ warn "Could not detect owner/repo — skipping pipeline complete"
396
+ return 0
397
+ fi
398
+
399
+ local state="success"
400
+ local desc="Pipeline completed successfully"
401
+ if [[ "$is_success" != "true" && "$is_success" != "1" ]]; then
402
+ state="failure"
403
+ desc="Pipeline failed"
404
+ fi
405
+
406
+ gh_deploy_update_status "$owner" "$repo" "$deploy_id" "$state" "$env_url" "$desc"
407
+
408
+ emit_event "deploy.pipeline_complete" "deploy_id=$deploy_id" "state=$state" "environment=$environment"
409
+ }
410
+
411
+ # ═══════════════════════════════════════════════════════════════════════════════
412
+ # CLI INTERFACE
413
+ # ═══════════════════════════════════════════════════════════════════════════════
414
+
415
+ show_help() {
416
+ echo ""
417
+ echo -e "${CYAN}${BOLD}shipwright github-deploy${RESET} — Native GitHub Deployments API Integration"
418
+ echo ""
419
+ echo -e "${BOLD}USAGE${RESET}"
420
+ echo -e " shipwright deploy <command> [options]"
421
+ echo ""
422
+ echo -e "${BOLD}COMMANDS${RESET}"
423
+ echo -e " ${CYAN}list${RESET} [environment] List deployments"
424
+ echo -e " ${CYAN}create${RESET} <ref> [environment] Create a new deployment"
425
+ echo -e " ${CYAN}rollback${RESET} [environment] Rollback to previous deployment"
426
+ echo -e " ${CYAN}help${RESET} Show this help"
427
+ echo ""
428
+ echo -e "${BOLD}FUNCTIONS (for sourcing)${RESET}"
429
+ echo -e " ${DIM}gh_deploy_create Create a deployment${RESET}"
430
+ echo -e " ${DIM}gh_deploy_update_status Update deployment status${RESET}"
431
+ echo -e " ${DIM}gh_deploy_list List deployments${RESET}"
432
+ echo -e " ${DIM}gh_deploy_latest Get latest deployment${RESET}"
433
+ echo -e " ${DIM}gh_deploy_rollback Rollback to previous ref${RESET}"
434
+ echo -e " ${DIM}gh_deploy_pipeline_start Start pipeline deployment${RESET}"
435
+ echo -e " ${DIM}gh_deploy_pipeline_complete Complete pipeline deployment${RESET}"
436
+ echo ""
437
+ echo -e "${DIM}Version ${VERSION}${RESET}"
438
+ }
439
+
440
+ _deploy_list_cli() {
441
+ local environment="${1:-}"
442
+
443
+ local detected
444
+ detected=$(_gh_detect_repo || true)
445
+ if [[ -z "$detected" ]]; then
446
+ error "Could not detect owner/repo from git remote"
447
+ exit 1
448
+ fi
449
+
450
+ local owner="${detected%%/*}"
451
+ local repo="${detected##*/}"
452
+
453
+ info "Listing deployments${environment:+ for ${environment}}..."
454
+ local deploys
455
+ deploys=$(gh_deploy_list "$owner" "$repo" "$environment" 10)
456
+
457
+ local count
458
+ count=$(echo "$deploys" | jq 'length' 2>/dev/null || echo "0")
459
+
460
+ if [[ "$count" -eq 0 ]]; then
461
+ info "No deployments found"
462
+ return 0
463
+ fi
464
+
465
+ echo ""
466
+ echo -e "${BOLD}Deployments (${count})${RESET}"
467
+ echo ""
468
+
469
+ echo "$deploys" | jq -r '.[] | " \(.id)\t\(.ref)\t\(.environment)\t\(.created_at)"' 2>/dev/null || true
470
+ echo ""
471
+ }
472
+
473
+ _deploy_create_cli() {
474
+ local ref="${1:-}"
475
+ local environment="${2:-production}"
476
+
477
+ if [[ -z "$ref" ]]; then
478
+ error "Usage: shipwright deploy create <ref> [environment]"
479
+ exit 1
480
+ fi
481
+
482
+ local detected
483
+ detected=$(_gh_detect_repo || true)
484
+ if [[ -z "$detected" ]]; then
485
+ error "Could not detect owner/repo from git remote"
486
+ exit 1
487
+ fi
488
+
489
+ local owner="${detected%%/*}"
490
+ local repo="${detected##*/}"
491
+
492
+ info "Creating deployment for '${ref}' to '${environment}'..."
493
+ local deploy_id
494
+ deploy_id=$(gh_deploy_create "$owner" "$repo" "$ref" "$environment" "Manual deployment")
495
+
496
+ if [[ -n "$deploy_id" ]]; then
497
+ success "Created deployment: ${deploy_id}"
498
+ else
499
+ error "Failed to create deployment"
500
+ exit 1
501
+ fi
502
+ }
503
+
504
+ _deploy_rollback_cli() {
505
+ local environment="${1:-production}"
506
+
507
+ local detected
508
+ detected=$(_gh_detect_repo || true)
509
+ if [[ -z "$detected" ]]; then
510
+ error "Could not detect owner/repo from git remote"
511
+ exit 1
512
+ fi
513
+
514
+ local owner="${detected%%/*}"
515
+ local repo="${detected##*/}"
516
+
517
+ gh_deploy_rollback "$owner" "$repo" "$environment"
518
+ }
519
+
520
+ main() {
521
+ case "${1:-help}" in
522
+ list) shift; _deploy_list_cli "$@" ;;
523
+ create) shift; _deploy_create_cli "$@" ;;
524
+ rollback) shift; _deploy_rollback_cli "$@" ;;
525
+ help|--help|-h) show_help ;;
526
+ *) error "Unknown command: $1"; show_help; exit 1 ;;
527
+ esac
528
+ }
529
+
530
+ # Only run main if executed directly (not sourced)
531
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
532
+ main "$@"
533
+ fi