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,521 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright github-checks — Native GitHub Checks API Integration ║
|
|
4
|
+
# ║ Check runs per stage · Annotations · PR timeline 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
|
+
# ─── Session Cache for API Availability ───────────────────────────────────────
|
|
62
|
+
_GH_CHECKS_AVAILABLE=""
|
|
63
|
+
|
|
64
|
+
# ─── Auto-detect owner/repo from git remote ──────────────────────────────────
|
|
65
|
+
_gh_detect_repo() {
|
|
66
|
+
local remote_url
|
|
67
|
+
remote_url=$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)
|
|
68
|
+
if [[ -z "$remote_url" ]]; then
|
|
69
|
+
echo ""
|
|
70
|
+
return 1
|
|
71
|
+
fi
|
|
72
|
+
# Handle SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git)
|
|
73
|
+
local owner_repo
|
|
74
|
+
owner_repo=$(echo "$remote_url" | sed -E 's#^(https?://github\.com/|git@github\.com:)##; s#\.git$##')
|
|
75
|
+
echo "$owner_repo"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
79
|
+
# CHECKS API FUNCTIONS
|
|
80
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
81
|
+
|
|
82
|
+
# ─── Check if Checks API is accessible ────────────────────────────────────────
|
|
83
|
+
_gh_checks_available() {
|
|
84
|
+
# Return cached result if available
|
|
85
|
+
if [[ "$_GH_CHECKS_AVAILABLE" == "yes" ]]; then
|
|
86
|
+
return 0
|
|
87
|
+
elif [[ "$_GH_CHECKS_AVAILABLE" == "no" ]]; then
|
|
88
|
+
return 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
92
|
+
_GH_CHECKS_AVAILABLE="no"
|
|
93
|
+
return 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
local owner="${1:-}"
|
|
97
|
+
local repo="${2:-}"
|
|
98
|
+
|
|
99
|
+
if [[ -z "$owner" || -z "$repo" ]]; then
|
|
100
|
+
local detected
|
|
101
|
+
detected=$(_gh_detect_repo || true)
|
|
102
|
+
if [[ -n "$detected" ]]; then
|
|
103
|
+
owner="${detected%%/*}"
|
|
104
|
+
repo="${detected##*/}"
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
if [[ -z "$owner" || -z "$repo" ]]; then
|
|
109
|
+
_GH_CHECKS_AVAILABLE="no"
|
|
110
|
+
return 1
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
local result=0
|
|
114
|
+
gh api "repos/${owner}/${repo}/commits/HEAD/check-runs?per_page=1" --silent 2>/dev/null || result=$?
|
|
115
|
+
|
|
116
|
+
if [[ "$result" -eq 0 ]]; then
|
|
117
|
+
_GH_CHECKS_AVAILABLE="yes"
|
|
118
|
+
return 0
|
|
119
|
+
else
|
|
120
|
+
_GH_CHECKS_AVAILABLE="no"
|
|
121
|
+
return 1
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ─── Create a check run ──────────────────────────────────────────────────────
|
|
126
|
+
gh_checks_create_run() {
|
|
127
|
+
local owner="${1:-}"
|
|
128
|
+
local repo="${2:-}"
|
|
129
|
+
local head_sha="${3:-}"
|
|
130
|
+
local name="${4:-}"
|
|
131
|
+
local status="${5:-in_progress}"
|
|
132
|
+
local details_url="${6:-}"
|
|
133
|
+
|
|
134
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
135
|
+
return 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [[ -z "$owner" || -z "$repo" || -z "$head_sha" || -z "$name" ]]; then
|
|
139
|
+
error "Usage: gh_checks_create_run <owner> <repo> <sha> <name> [status] [details_url]"
|
|
140
|
+
return 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
local body
|
|
144
|
+
body=$(jq -n \
|
|
145
|
+
--arg sha "$head_sha" \
|
|
146
|
+
--arg name "$name" \
|
|
147
|
+
--arg status "$status" \
|
|
148
|
+
--arg started_at "$(now_iso)" \
|
|
149
|
+
--arg details_url "$details_url" \
|
|
150
|
+
'{
|
|
151
|
+
head_sha: $sha,
|
|
152
|
+
name: $name,
|
|
153
|
+
status: $status,
|
|
154
|
+
started_at: $started_at
|
|
155
|
+
} + (if $details_url != "" then {details_url: $details_url} else {} end)')
|
|
156
|
+
|
|
157
|
+
local response=""
|
|
158
|
+
local result=0
|
|
159
|
+
response=$(gh api "repos/${owner}/${repo}/check-runs" \
|
|
160
|
+
--method POST \
|
|
161
|
+
--input - <<< "$body" 2>/dev/null) || result=$?
|
|
162
|
+
|
|
163
|
+
if [[ "$result" -ne 0 ]]; then
|
|
164
|
+
warn "Failed to create check run '${name}' (API returned ${result})" >&2
|
|
165
|
+
echo ""
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
local run_id
|
|
170
|
+
run_id=$(echo "$response" | jq -r '.id // empty' 2>/dev/null || true)
|
|
171
|
+
|
|
172
|
+
if [[ -n "$run_id" && "$run_id" != "null" ]]; then
|
|
173
|
+
emit_event "checks.create" "run_id=$run_id" "name=$name" "status=$status"
|
|
174
|
+
echo "$run_id"
|
|
175
|
+
else
|
|
176
|
+
warn "Check run created but no ID returned" >&2
|
|
177
|
+
echo ""
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# ─── Update a check run ──────────────────────────────────────────────────────
|
|
182
|
+
gh_checks_update_run() {
|
|
183
|
+
local owner="${1:-}"
|
|
184
|
+
local repo="${2:-}"
|
|
185
|
+
local run_id="${3:-}"
|
|
186
|
+
local status="${4:-}"
|
|
187
|
+
local conclusion="${5:-}"
|
|
188
|
+
local output_title="${6:-}"
|
|
189
|
+
local output_summary="${7:-}"
|
|
190
|
+
local output_text="${8:-}"
|
|
191
|
+
|
|
192
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
193
|
+
return 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Skip silently if run_id is empty
|
|
197
|
+
if [[ -z "$run_id" ]]; then
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if [[ -z "$owner" || -z "$repo" ]]; then
|
|
202
|
+
error "Usage: gh_checks_update_run <owner> <repo> <run_id> <status> [conclusion] [title] [summary] [text]"
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
local body
|
|
207
|
+
body=$(jq -n \
|
|
208
|
+
--arg status "$status" \
|
|
209
|
+
--arg conclusion "$conclusion" \
|
|
210
|
+
--arg completed_at "$(now_iso)" \
|
|
211
|
+
--arg title "$output_title" \
|
|
212
|
+
--arg summary "$output_summary" \
|
|
213
|
+
--arg text "$output_text" \
|
|
214
|
+
'{status: $status}
|
|
215
|
+
+ (if $conclusion != "" then {conclusion: $conclusion, completed_at: $completed_at} else {} end)
|
|
216
|
+
+ (if $title != "" then {output: (
|
|
217
|
+
{title: $title, summary: (if $summary != "" then $summary else "No summary" end)}
|
|
218
|
+
+ (if $text != "" then {text: $text} else {} end)
|
|
219
|
+
)} else {} end)')
|
|
220
|
+
|
|
221
|
+
local result=0
|
|
222
|
+
gh api "repos/${owner}/${repo}/check-runs/${run_id}" \
|
|
223
|
+
--method PATCH \
|
|
224
|
+
--input - <<< "$body" --silent 2>/dev/null || result=$?
|
|
225
|
+
|
|
226
|
+
if [[ "$result" -ne 0 ]]; then
|
|
227
|
+
warn "Failed to update check run ${run_id}"
|
|
228
|
+
else
|
|
229
|
+
emit_event "checks.update" "run_id=$run_id" "status=$status" "conclusion=$conclusion"
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ─── Add annotations to a check run ──────────────────────────────────────────
|
|
234
|
+
gh_checks_annotate() {
|
|
235
|
+
local owner="${1:-}"
|
|
236
|
+
local repo="${2:-}"
|
|
237
|
+
local run_id="${3:-}"
|
|
238
|
+
local annotations_json="${4:-}"
|
|
239
|
+
|
|
240
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
241
|
+
return 0
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
if [[ -z "$run_id" ]]; then
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
if [[ -z "$owner" || -z "$repo" || -z "$annotations_json" ]]; then
|
|
249
|
+
error "Usage: gh_checks_annotate <owner> <repo> <run_id> <annotations_json>"
|
|
250
|
+
return 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# GitHub limits: max 50 annotations per request
|
|
254
|
+
local total
|
|
255
|
+
total=$(echo "$annotations_json" | jq 'length' 2>/dev/null || echo "0")
|
|
256
|
+
|
|
257
|
+
local offset=0
|
|
258
|
+
while [[ "$offset" -lt "$total" ]]; do
|
|
259
|
+
local batch
|
|
260
|
+
batch=$(echo "$annotations_json" | jq --argjson s "$offset" --argjson e 50 '.[$s:$s+$e]' 2>/dev/null)
|
|
261
|
+
|
|
262
|
+
local body
|
|
263
|
+
body=$(jq -n \
|
|
264
|
+
--argjson annotations "$batch" \
|
|
265
|
+
'{
|
|
266
|
+
output: {
|
|
267
|
+
title: "Shipwright Annotations",
|
|
268
|
+
summary: "Pipeline analysis annotations",
|
|
269
|
+
annotations: $annotations
|
|
270
|
+
}
|
|
271
|
+
}')
|
|
272
|
+
|
|
273
|
+
local result=0
|
|
274
|
+
gh api "repos/${owner}/${repo}/check-runs/${run_id}" \
|
|
275
|
+
--method PATCH \
|
|
276
|
+
--input - <<< "$body" --silent 2>/dev/null || result=$?
|
|
277
|
+
|
|
278
|
+
if [[ "$result" -ne 0 ]]; then
|
|
279
|
+
warn "Failed to add annotations batch at offset ${offset}"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
offset=$((offset + 50))
|
|
283
|
+
done
|
|
284
|
+
|
|
285
|
+
emit_event "checks.annotate" "run_id=$run_id" "count=$total"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# ─── List check runs for a commit ─────────────────────────────────────────────
|
|
289
|
+
gh_checks_list_runs() {
|
|
290
|
+
local owner="${1:-}"
|
|
291
|
+
local repo="${2:-}"
|
|
292
|
+
local head_sha="${3:-}"
|
|
293
|
+
|
|
294
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
295
|
+
echo "[]"
|
|
296
|
+
return 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
if [[ -z "$owner" || -z "$repo" || -z "$head_sha" ]]; then
|
|
300
|
+
error "Usage: gh_checks_list_runs <owner> <repo> <sha>"
|
|
301
|
+
return 1
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
local response=""
|
|
305
|
+
local result=0
|
|
306
|
+
response=$(gh api "repos/${owner}/${repo}/commits/${head_sha}/check-runs" 2>/dev/null) || result=$?
|
|
307
|
+
|
|
308
|
+
if [[ "$result" -ne 0 ]]; then
|
|
309
|
+
warn "Failed to list check runs for ${head_sha}" >&2
|
|
310
|
+
echo "[]"
|
|
311
|
+
return 0
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
echo "$response" | jq '[.check_runs[] | {id, name, status, conclusion, started_at, completed_at}]' 2>/dev/null || echo "[]"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# ─── Complete a check run (convenience) ───────────────────────────────────────
|
|
318
|
+
gh_checks_complete() {
|
|
319
|
+
local owner="${1:-}"
|
|
320
|
+
local repo="${2:-}"
|
|
321
|
+
local run_id="${3:-}"
|
|
322
|
+
local conclusion="${4:-success}"
|
|
323
|
+
local summary="${5:-}"
|
|
324
|
+
|
|
325
|
+
if [[ -z "$run_id" ]]; then
|
|
326
|
+
return 0
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
gh_checks_update_run "$owner" "$repo" "$run_id" "completed" "$conclusion" \
|
|
330
|
+
"Shipwright: ${conclusion}" "${summary:-Stage completed with ${conclusion}}" ""
|
|
331
|
+
|
|
332
|
+
emit_event "checks.complete" "run_id=$run_id" "conclusion=$conclusion"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# ─── Create check runs for all pipeline stages ───────────────────────────────
|
|
336
|
+
gh_checks_pipeline_start() {
|
|
337
|
+
local owner="${1:-}"
|
|
338
|
+
local repo="${2:-}"
|
|
339
|
+
local head_sha="${3:-}"
|
|
340
|
+
local stages_json="${4:-}"
|
|
341
|
+
|
|
342
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
343
|
+
echo "{}"
|
|
344
|
+
return 0
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
if [[ -z "$owner" || -z "$repo" || -z "$head_sha" || -z "$stages_json" ]]; then
|
|
348
|
+
error "Usage: gh_checks_pipeline_start <owner> <repo> <sha> <stages_json>"
|
|
349
|
+
return 1
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
mkdir -p "$ARTIFACTS_DIR"
|
|
353
|
+
|
|
354
|
+
local run_ids="{}"
|
|
355
|
+
local stage=""
|
|
356
|
+
while IFS= read -r stage; do
|
|
357
|
+
[[ -z "$stage" ]] && continue
|
|
358
|
+
|
|
359
|
+
local run_id
|
|
360
|
+
run_id=$(gh_checks_create_run "$owner" "$repo" "$head_sha" "shipwright/${stage}" "queued" "")
|
|
361
|
+
|
|
362
|
+
if [[ -n "$run_id" ]]; then
|
|
363
|
+
run_ids=$(echo "$run_ids" | jq --arg stage "$stage" --arg id "$run_id" '. + {($stage): $id}')
|
|
364
|
+
fi
|
|
365
|
+
done < <(echo "$stages_json" | jq -r '.[]' 2>/dev/null)
|
|
366
|
+
|
|
367
|
+
# Store run IDs atomically
|
|
368
|
+
local tmp_file
|
|
369
|
+
tmp_file=$(mktemp "${ARTIFACTS_DIR}/check-run-ids.XXXXXX")
|
|
370
|
+
echo "$run_ids" > "$tmp_file"
|
|
371
|
+
mv "$tmp_file" "${ARTIFACTS_DIR}/check-run-ids.json"
|
|
372
|
+
|
|
373
|
+
emit_event "checks.pipeline_start" "stages=$(echo "$stages_json" | jq -r 'length')"
|
|
374
|
+
echo "$run_ids"
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
# ─── Update a pipeline stage check run ────────────────────────────────────────
|
|
378
|
+
gh_checks_stage_update() {
|
|
379
|
+
local stage_name="${1:-}"
|
|
380
|
+
local status="${2:-}"
|
|
381
|
+
local conclusion="${3:-}"
|
|
382
|
+
local summary="${4:-}"
|
|
383
|
+
|
|
384
|
+
if [[ "${NO_GITHUB:-}" == "true" || "${NO_GITHUB:-}" == "1" ]]; then
|
|
385
|
+
return 0
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
local ids_file="${ARTIFACTS_DIR}/check-run-ids.json"
|
|
389
|
+
if [[ ! -f "$ids_file" ]]; then
|
|
390
|
+
warn "No check-run-ids.json found — skipping stage update"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
local run_id
|
|
395
|
+
run_id=$(jq -r --arg s "$stage_name" '.[$s] // empty' "$ids_file" 2>/dev/null || true)
|
|
396
|
+
|
|
397
|
+
if [[ -z "$run_id" || "$run_id" == "null" ]]; then
|
|
398
|
+
warn "No check run ID found for stage '${stage_name}'"
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Detect owner/repo
|
|
403
|
+
local detected
|
|
404
|
+
detected=$(_gh_detect_repo || true)
|
|
405
|
+
if [[ -z "$detected" ]]; then
|
|
406
|
+
warn "Could not detect owner/repo — skipping stage update"
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
local owner="${detected%%/*}"
|
|
411
|
+
local repo="${detected##*/}"
|
|
412
|
+
|
|
413
|
+
gh_checks_update_run "$owner" "$repo" "$run_id" "$status" "$conclusion" \
|
|
414
|
+
"Shipwright: ${stage_name}" "${summary:-Stage ${stage_name}: ${status}}" ""
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
418
|
+
# CLI INTERFACE
|
|
419
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
420
|
+
|
|
421
|
+
show_help() {
|
|
422
|
+
echo ""
|
|
423
|
+
echo -e "${CYAN}${BOLD}shipwright github-checks${RESET} — Native GitHub Checks API Integration"
|
|
424
|
+
echo ""
|
|
425
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
426
|
+
echo -e " shipwright checks <command> [options]"
|
|
427
|
+
echo ""
|
|
428
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
429
|
+
echo -e " ${CYAN}list${RESET} <sha> List check runs for a commit"
|
|
430
|
+
echo -e " ${CYAN}create${RESET} <sha> <name> Create a new check run"
|
|
431
|
+
echo -e " ${CYAN}help${RESET} Show this help"
|
|
432
|
+
echo ""
|
|
433
|
+
echo -e "${BOLD}FUNCTIONS (for sourcing)${RESET}"
|
|
434
|
+
echo -e " ${DIM}gh_checks_create_run Create a check run${RESET}"
|
|
435
|
+
echo -e " ${DIM}gh_checks_update_run Update check run status/conclusion${RESET}"
|
|
436
|
+
echo -e " ${DIM}gh_checks_annotate Add annotations to a check run${RESET}"
|
|
437
|
+
echo -e " ${DIM}gh_checks_list_runs List check runs for a commit${RESET}"
|
|
438
|
+
echo -e " ${DIM}gh_checks_complete Mark a check run as completed${RESET}"
|
|
439
|
+
echo -e " ${DIM}gh_checks_pipeline_start Create runs for all pipeline stages${RESET}"
|
|
440
|
+
echo -e " ${DIM}gh_checks_stage_update Update a stage's check run${RESET}"
|
|
441
|
+
echo ""
|
|
442
|
+
echo -e "${DIM}Version ${VERSION}${RESET}"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
_checks_list_cli() {
|
|
446
|
+
local sha="${1:-HEAD}"
|
|
447
|
+
|
|
448
|
+
local detected
|
|
449
|
+
detected=$(_gh_detect_repo || true)
|
|
450
|
+
if [[ -z "$detected" ]]; then
|
|
451
|
+
error "Could not detect owner/repo from git remote"
|
|
452
|
+
exit 1
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
local owner="${detected%%/*}"
|
|
456
|
+
local repo="${detected##*/}"
|
|
457
|
+
|
|
458
|
+
info "Listing check runs for ${sha}..."
|
|
459
|
+
local runs
|
|
460
|
+
runs=$(gh_checks_list_runs "$owner" "$repo" "$sha")
|
|
461
|
+
|
|
462
|
+
local count
|
|
463
|
+
count=$(echo "$runs" | jq 'length' 2>/dev/null || echo "0")
|
|
464
|
+
|
|
465
|
+
if [[ "$count" -eq 0 ]]; then
|
|
466
|
+
info "No check runs found"
|
|
467
|
+
return 0
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
echo ""
|
|
471
|
+
echo -e "${BOLD}Check Runs (${count})${RESET}"
|
|
472
|
+
echo ""
|
|
473
|
+
|
|
474
|
+
echo "$runs" | jq -r '.[] | " \(.name)\t\(.status)\t\(.conclusion // "-")"' 2>/dev/null || true
|
|
475
|
+
echo ""
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
_checks_create_cli() {
|
|
479
|
+
local sha="${1:-}"
|
|
480
|
+
local name="${2:-}"
|
|
481
|
+
|
|
482
|
+
if [[ -z "$sha" || -z "$name" ]]; then
|
|
483
|
+
error "Usage: shipwright checks create <sha> <name>"
|
|
484
|
+
exit 1
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
local detected
|
|
488
|
+
detected=$(_gh_detect_repo || true)
|
|
489
|
+
if [[ -z "$detected" ]]; then
|
|
490
|
+
error "Could not detect owner/repo from git remote"
|
|
491
|
+
exit 1
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
local owner="${detected%%/*}"
|
|
495
|
+
local repo="${detected##*/}"
|
|
496
|
+
|
|
497
|
+
info "Creating check run '${name}'..."
|
|
498
|
+
local run_id
|
|
499
|
+
run_id=$(gh_checks_create_run "$owner" "$repo" "$sha" "$name" "in_progress")
|
|
500
|
+
|
|
501
|
+
if [[ -n "$run_id" ]]; then
|
|
502
|
+
success "Created check run: ${run_id}"
|
|
503
|
+
else
|
|
504
|
+
error "Failed to create check run"
|
|
505
|
+
exit 1
|
|
506
|
+
fi
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
main() {
|
|
510
|
+
case "${1:-help}" in
|
|
511
|
+
list) shift; _checks_list_cli "$@" ;;
|
|
512
|
+
create) shift; _checks_create_cli "$@" ;;
|
|
513
|
+
help|--help|-h) show_help ;;
|
|
514
|
+
*) error "Unknown command: $1"; show_help; exit 1 ;;
|
|
515
|
+
esac
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# Only run main if executed directly (not sourced)
|
|
519
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
520
|
+
main "$@"
|
|
521
|
+
fi
|