shipwright-cli 1.7.1 → 1.9.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/.claude/agents/code-reviewer.md +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +38 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +109 -21
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +390 -0
- package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/{cct-init.sh → sw-init.sh} +144 -11
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
- package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +947 -0
- package/scripts/sw-session.sh +519 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +605 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +27 -5
- package/templates/pipelines/full.json +12 -0
- package/templates/pipelines/standard.json +12 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-doctor.sh +0 -414
- package/scripts/cct-session.sh +0 -284
- 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.9.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
|