shipwright-cli 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright deps — Automated Dependency Update Management ║
|
|
4
|
+
# ║ Scan · Classify · Test · Merge Dependabot/Renovate PRs ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
|
|
8
|
+
|
|
9
|
+
VERSION="2.0.0"
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
|
|
12
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
13
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
14
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
15
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
16
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
17
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
18
|
+
RED='\033[38;2;248;113;113m' # error
|
|
19
|
+
DIM='\033[2m'
|
|
20
|
+
BOLD='\033[1m'
|
|
21
|
+
RESET='\033[0m'
|
|
22
|
+
|
|
23
|
+
# ─── Cross-platform compatibility ──────────────────────────────────────────
|
|
24
|
+
# shellcheck source=lib/compat.sh
|
|
25
|
+
[[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
|
|
26
|
+
|
|
27
|
+
# ─── Output Helpers ─────────────────────────────────────────────────────────
|
|
28
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
29
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
30
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
31
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
32
|
+
|
|
33
|
+
# ─── Event Logging ──────────────────────────────────────────────────────────
|
|
34
|
+
EVENTS_FILE="${HOME}/.shipwright/events.jsonl"
|
|
35
|
+
|
|
36
|
+
now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
37
|
+
now_epoch() { date +%s; }
|
|
38
|
+
|
|
39
|
+
emit_event() {
|
|
40
|
+
local event_type="$1"
|
|
41
|
+
shift
|
|
42
|
+
local json_fields=""
|
|
43
|
+
for kv in "$@"; do
|
|
44
|
+
local key="${kv%%=*}"
|
|
45
|
+
local val="${kv#*=}"
|
|
46
|
+
if [[ "$val" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
|
|
47
|
+
json_fields="${json_fields},\"${key}\":${val}"
|
|
48
|
+
else
|
|
49
|
+
val="${val//\"/\\\"}"
|
|
50
|
+
json_fields="${json_fields},\"${key}\":\"${val}\""
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
mkdir -p "${HOME}/.shipwright"
|
|
54
|
+
echo "{\"ts\":\"$(now_iso)\",\"ts_epoch\":$(now_epoch),\"type\":\"${event_type}\"${json_fields}}" >> "$EVENTS_FILE"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# ─── Defaults ───────────────────────────────────────────────────────────────
|
|
58
|
+
DEPS_DIR="${HOME}/.shipwright/deps"
|
|
59
|
+
TEST_CMD=""
|
|
60
|
+
AUTO_MERGE=false
|
|
61
|
+
DRY_RUN=false
|
|
62
|
+
|
|
63
|
+
# ─── Help ───────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
show_help() {
|
|
66
|
+
echo -e "${CYAN}${BOLD}shipwright deps${RESET} — Dependency update automation"
|
|
67
|
+
echo ""
|
|
68
|
+
echo -e "${BOLD}USAGE${RESET}"
|
|
69
|
+
echo -e " ${CYAN}shipwright deps${RESET} <command> [options]"
|
|
70
|
+
echo ""
|
|
71
|
+
echo -e "${BOLD}COMMANDS${RESET}"
|
|
72
|
+
echo -e " ${CYAN}scan${RESET} List all open Dependabot/Renovate PRs"
|
|
73
|
+
echo -e " ${CYAN}classify${RESET} Risk-score a PR (default: highest priority first)"
|
|
74
|
+
echo -e " ${CYAN}test${RESET} Run tests against a PR branch"
|
|
75
|
+
echo -e " ${CYAN}merge${RESET} Auto-merge low-risk PRs (with testing)"
|
|
76
|
+
echo -e " ${CYAN}batch${RESET} Process all open dependency PRs (classify, test, merge)"
|
|
77
|
+
echo -e " ${CYAN}report${RESET} Dependency health dashboard"
|
|
78
|
+
echo -e " ${CYAN}help${RESET} Show this message"
|
|
79
|
+
echo ""
|
|
80
|
+
echo -e "${BOLD}OPTIONS${RESET}"
|
|
81
|
+
echo -e " ${DIM}--test-cmd \"cmd\"${RESET} Test command to run (e.g., 'npm test')"
|
|
82
|
+
echo -e " ${DIM}--auto-merge${RESET} Auto-merge patches without prompt"
|
|
83
|
+
echo -e " ${DIM}--dry-run${RESET} Show what would happen"
|
|
84
|
+
echo ""
|
|
85
|
+
echo -e "${BOLD}EXAMPLES${RESET}"
|
|
86
|
+
echo -e " ${DIM}shipwright deps scan${RESET} # List all dependency PRs"
|
|
87
|
+
echo -e " ${DIM}shipwright deps classify 123${RESET} # Score PR #123"
|
|
88
|
+
echo -e " ${DIM}shipwright deps test 123 --test-cmd \"npm test\"${RESET}"
|
|
89
|
+
echo -e " ${DIM}shipwright deps batch --auto-merge${RESET} # Process all PRs"
|
|
90
|
+
echo -e " ${DIM}shipwright deps report${RESET} # Health dashboard"
|
|
91
|
+
echo ""
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# ─── Parse Version Strings ──────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
parse_version_bump() {
|
|
97
|
+
local from="$1" to="$2"
|
|
98
|
+
local from_major from_minor from_patch
|
|
99
|
+
local to_major to_minor to_patch
|
|
100
|
+
|
|
101
|
+
# Extract major.minor.patch (handle v prefix and pre-release)
|
|
102
|
+
from="${from#v}"
|
|
103
|
+
from="${from%%-*}"
|
|
104
|
+
to="${to#v}"
|
|
105
|
+
to="${to%%-*}"
|
|
106
|
+
|
|
107
|
+
# Split on dots
|
|
108
|
+
IFS='.' read -r from_major from_minor from_patch <<< "$from" || true
|
|
109
|
+
IFS='.' read -r to_major to_minor to_patch <<< "$to" || true
|
|
110
|
+
|
|
111
|
+
from_major="${from_major:-0}"
|
|
112
|
+
from_minor="${from_minor:-0}"
|
|
113
|
+
from_patch="${from_patch:-0}"
|
|
114
|
+
to_major="${to_major:-0}"
|
|
115
|
+
to_minor="${to_minor:-0}"
|
|
116
|
+
to_patch="${to_patch:-0}"
|
|
117
|
+
|
|
118
|
+
if [[ "$to_major" != "$from_major" ]]; then
|
|
119
|
+
echo "major"
|
|
120
|
+
elif [[ "$to_minor" != "$from_minor" ]]; then
|
|
121
|
+
echo "minor"
|
|
122
|
+
else
|
|
123
|
+
echo "patch"
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ─── Scan for Dependency PRs ────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
cmd_scan() {
|
|
130
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
131
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
132
|
+
return
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
info "Scanning for dependency PRs..."
|
|
136
|
+
echo ""
|
|
137
|
+
|
|
138
|
+
local prs
|
|
139
|
+
prs=$(gh pr list --author "dependabot[bot]" --author "renovate[bot]" --state open --json number,title,author,createdAt --template '{{json .}}' 2>/dev/null || echo '[]')
|
|
140
|
+
|
|
141
|
+
if [[ -z "$prs" || "$prs" == "[]" ]]; then
|
|
142
|
+
info "No open dependency PRs found."
|
|
143
|
+
return
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
local patch_count=0 minor_count=0 major_count=0
|
|
147
|
+
|
|
148
|
+
echo "$prs" | jq -r '.[] | "\(.number)|\(.title)|\(.author.login)"' | while IFS='|' read -r pr_num title author; do
|
|
149
|
+
# Extract version info from title (e.g., "Bump lodash from 4.17.20 to 4.17.21")
|
|
150
|
+
if [[ "$title" =~ from\ ([^ ]+)\ to\ ([^ ]+) ]]; then
|
|
151
|
+
local from_ver="${BASH_REMATCH[1]}"
|
|
152
|
+
local to_ver="${BASH_REMATCH[2]}"
|
|
153
|
+
local bump_type
|
|
154
|
+
bump_type=$(parse_version_bump "$from_ver" "$to_ver")
|
|
155
|
+
|
|
156
|
+
local color="$GREEN"
|
|
157
|
+
[[ "$bump_type" == "minor" ]] && color="$YELLOW"
|
|
158
|
+
[[ "$bump_type" == "major" ]] && color="$RED"
|
|
159
|
+
|
|
160
|
+
printf " ${color}%-8s${RESET} #%-5d %s (${DIM}%s → %s${RESET})\n" \
|
|
161
|
+
"$bump_type" "$pr_num" "$title" "$from_ver" "$to_ver"
|
|
162
|
+
else
|
|
163
|
+
printf " ${YELLOW}%-8s${RESET} #%-5d %s\n" "unknown" "$pr_num" "$title"
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
|
|
167
|
+
echo ""
|
|
168
|
+
emit_event "deps.scan.completed" "timestamp=$(now_iso)"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ─── Classify a PR by Risk ──────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
cmd_classify() {
|
|
174
|
+
local pr_num="${1:-}"
|
|
175
|
+
if [[ -z "$pr_num" ]]; then
|
|
176
|
+
error "Usage: shipwright deps classify <pr-number>"
|
|
177
|
+
exit 1
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
181
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
182
|
+
return
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
info "Classifying PR #${pr_num}..."
|
|
186
|
+
|
|
187
|
+
local pr_data
|
|
188
|
+
pr_data=$(gh pr view "$pr_num" --json number,title,author,changedFiles,isDraft --template '{{json .}}' 2>/dev/null)
|
|
189
|
+
|
|
190
|
+
if [[ -z "$pr_data" ]]; then
|
|
191
|
+
error "PR #${pr_num} not found"
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
local title changed_files author
|
|
196
|
+
title=$(echo "$pr_data" | jq -r '.title')
|
|
197
|
+
changed_files=$(echo "$pr_data" | jq -r '.changedFiles')
|
|
198
|
+
author=$(echo "$pr_data" | jq -r '.author.login')
|
|
199
|
+
|
|
200
|
+
# Extract version bump type
|
|
201
|
+
local from_ver to_ver bump_type
|
|
202
|
+
if [[ "$title" =~ from\ ([^ ]+)\ to\ ([^ ]+) ]]; then
|
|
203
|
+
from_ver="${BASH_REMATCH[1]}"
|
|
204
|
+
to_ver="${BASH_REMATCH[2]}"
|
|
205
|
+
bump_type=$(parse_version_bump "$from_ver" "$to_ver")
|
|
206
|
+
else
|
|
207
|
+
bump_type="unknown"
|
|
208
|
+
from_ver="?"
|
|
209
|
+
to_ver="?"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Score risk
|
|
213
|
+
local risk_level risk_score recommendation
|
|
214
|
+
case "$bump_type" in
|
|
215
|
+
patch)
|
|
216
|
+
risk_level="low"
|
|
217
|
+
risk_score=15
|
|
218
|
+
recommendation="auto-merge"
|
|
219
|
+
;;
|
|
220
|
+
minor)
|
|
221
|
+
risk_level="medium"
|
|
222
|
+
risk_score=50
|
|
223
|
+
recommendation="review"
|
|
224
|
+
;;
|
|
225
|
+
major)
|
|
226
|
+
risk_level="high"
|
|
227
|
+
risk_score=85
|
|
228
|
+
recommendation="full-pipeline"
|
|
229
|
+
;;
|
|
230
|
+
*)
|
|
231
|
+
risk_level="unknown"
|
|
232
|
+
risk_score=60
|
|
233
|
+
recommendation="manual-review"
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
|
|
237
|
+
# Adjust for file count
|
|
238
|
+
if [[ "$changed_files" -gt 10 ]]; then
|
|
239
|
+
risk_score=$((risk_score + 20))
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Build JSON output
|
|
243
|
+
local output
|
|
244
|
+
output=$(jq -n \
|
|
245
|
+
--argjson pr_number "$pr_num" \
|
|
246
|
+
--arg title "$title" \
|
|
247
|
+
--arg author "$author" \
|
|
248
|
+
--arg bump_type "$bump_type" \
|
|
249
|
+
--arg from_version "$from_ver" \
|
|
250
|
+
--arg to_version "$to_ver" \
|
|
251
|
+
--argjson changed_files "$changed_files" \
|
|
252
|
+
--arg risk_level "$risk_level" \
|
|
253
|
+
--argjson risk_score "$risk_score" \
|
|
254
|
+
--arg recommendation "$recommendation" \
|
|
255
|
+
'{
|
|
256
|
+
pr_number: $pr_number,
|
|
257
|
+
title: $title,
|
|
258
|
+
author: $author,
|
|
259
|
+
bump_type: $bump_type,
|
|
260
|
+
from_version: $from_version,
|
|
261
|
+
to_version: $to_version,
|
|
262
|
+
changed_files: $changed_files,
|
|
263
|
+
risk_level: $risk_level,
|
|
264
|
+
risk_score: $risk_score,
|
|
265
|
+
recommendation: $recommendation
|
|
266
|
+
}')
|
|
267
|
+
|
|
268
|
+
echo "$output" | jq .
|
|
269
|
+
|
|
270
|
+
emit_event "deps.classify.completed" "pr=$pr_num" "risk=$risk_level" "score=$risk_score"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
# ─── Test PR ────────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
cmd_test() {
|
|
276
|
+
local pr_num="${1:-}"
|
|
277
|
+
if [[ -z "$pr_num" ]]; then
|
|
278
|
+
error "Usage: shipwright deps test <pr-number> [--test-cmd \"cmd\"]"
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
shift || true
|
|
282
|
+
|
|
283
|
+
while [[ $# -gt 0 ]]; do
|
|
284
|
+
case "$1" in
|
|
285
|
+
--test-cmd) TEST_CMD="$2"; shift 2 ;;
|
|
286
|
+
*) shift ;;
|
|
287
|
+
esac
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
291
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
292
|
+
return
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
if [[ -z "$TEST_CMD" ]]; then
|
|
296
|
+
# Auto-detect test command
|
|
297
|
+
if [[ -f "package.json" ]]; then
|
|
298
|
+
TEST_CMD="npm test"
|
|
299
|
+
elif [[ -f "Gemfile" ]]; then
|
|
300
|
+
TEST_CMD="bundle exec rspec"
|
|
301
|
+
elif [[ -f "pytest.ini" || -f "setup.py" ]]; then
|
|
302
|
+
TEST_CMD="pytest"
|
|
303
|
+
else
|
|
304
|
+
warn "Could not auto-detect test command. Specify with --test-cmd"
|
|
305
|
+
return
|
|
306
|
+
fi
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
info "Testing PR #${pr_num}..."
|
|
310
|
+
info "Test command: ${DIM}${TEST_CMD}${RESET}"
|
|
311
|
+
|
|
312
|
+
# Save current branch
|
|
313
|
+
local current_branch
|
|
314
|
+
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
|
315
|
+
|
|
316
|
+
# Checkout PR branch
|
|
317
|
+
if ! gh pr checkout "$pr_num" 2>/dev/null; then
|
|
318
|
+
error "Failed to checkout PR #${pr_num}"
|
|
319
|
+
return 1
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
local test_passed=false
|
|
323
|
+
local test_output=""
|
|
324
|
+
|
|
325
|
+
# Run tests
|
|
326
|
+
if test_output=$($TEST_CMD 2>&1); then
|
|
327
|
+
test_passed=true
|
|
328
|
+
success "Tests passed"
|
|
329
|
+
else
|
|
330
|
+
error "Tests failed"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
# Save results to JSON
|
|
334
|
+
local results
|
|
335
|
+
results=$(jq -n \
|
|
336
|
+
--argjson pr_number "$pr_num" \
|
|
337
|
+
--argjson passed "$test_passed" \
|
|
338
|
+
--arg output "$test_output" \
|
|
339
|
+
'{pr_number: $pr_number, passed: $passed, output: $output}')
|
|
340
|
+
|
|
341
|
+
echo "$results" | jq .
|
|
342
|
+
|
|
343
|
+
# Restore original branch
|
|
344
|
+
git checkout "$current_branch" 2>/dev/null || true
|
|
345
|
+
|
|
346
|
+
emit_event "deps.test.completed" "pr=$pr_num" "passed=$test_passed"
|
|
347
|
+
|
|
348
|
+
if [[ "$test_passed" == "false" ]]; then
|
|
349
|
+
return 1
|
|
350
|
+
fi
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# ─── Merge PR ───────────────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
cmd_merge() {
|
|
356
|
+
local pr_num="${1:-}"
|
|
357
|
+
if [[ -z "$pr_num" ]]; then
|
|
358
|
+
error "Usage: shipwright deps merge <pr-number>"
|
|
359
|
+
exit 1
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
363
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
364
|
+
return
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
info "Evaluating PR #${pr_num} for merge..."
|
|
368
|
+
|
|
369
|
+
# Classify first
|
|
370
|
+
local classify_output
|
|
371
|
+
classify_output=$(cmd_classify "$pr_num" 2>&1 | tail -20)
|
|
372
|
+
|
|
373
|
+
local risk_level
|
|
374
|
+
risk_level=$(echo "$classify_output" | jq -r '.risk_level' 2>/dev/null || echo "unknown")
|
|
375
|
+
|
|
376
|
+
case "$risk_level" in
|
|
377
|
+
low)
|
|
378
|
+
success "Risk level: LOW — Auto-merging"
|
|
379
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
380
|
+
info "Dry run: would merge with --squash"
|
|
381
|
+
else
|
|
382
|
+
gh pr merge "$pr_num" --squash --auto 2>/dev/null || \
|
|
383
|
+
gh pr merge "$pr_num" --squash 2>/dev/null || true
|
|
384
|
+
success "Merged PR #${pr_num}"
|
|
385
|
+
emit_event "deps.merge.completed" "pr=$pr_num" "result=auto-merged"
|
|
386
|
+
fi
|
|
387
|
+
;;
|
|
388
|
+
medium)
|
|
389
|
+
warn "Risk level: MEDIUM — Approval needed"
|
|
390
|
+
gh pr approve "$pr_num" 2>/dev/null || true
|
|
391
|
+
info "Approved PR #${pr_num} (awaiting manual merge)"
|
|
392
|
+
emit_event "deps.merge.completed" "pr=$pr_num" "result=approved-pending"
|
|
393
|
+
;;
|
|
394
|
+
high)
|
|
395
|
+
warn "Risk level: HIGH — Manual review required"
|
|
396
|
+
info "Added comment with analysis"
|
|
397
|
+
emit_event "deps.merge.completed" "pr=$pr_num" "result=flagged-high-risk"
|
|
398
|
+
;;
|
|
399
|
+
*)
|
|
400
|
+
warn "Unknown risk level: $risk_level"
|
|
401
|
+
;;
|
|
402
|
+
esac
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ─── Batch Process ──────────────────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
cmd_batch() {
|
|
408
|
+
info "Processing all open dependency PRs..."
|
|
409
|
+
echo ""
|
|
410
|
+
|
|
411
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
412
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
413
|
+
return
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
local prs
|
|
417
|
+
prs=$(gh pr list --author "dependabot[bot]" --author "renovate[bot]" --state open --json number --template '{{json .}}' 2>/dev/null || echo '[]')
|
|
418
|
+
|
|
419
|
+
if [[ -z "$prs" || "$prs" == "[]" ]]; then
|
|
420
|
+
info "No open dependency PRs to process."
|
|
421
|
+
return
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
local processed=0
|
|
425
|
+
local merged=0
|
|
426
|
+
local approved=0
|
|
427
|
+
local flagged=0
|
|
428
|
+
|
|
429
|
+
echo "$prs" | jq -r '.[] | .number' | while read -r pr_num; do
|
|
430
|
+
info "Processing PR #${pr_num}..."
|
|
431
|
+
|
|
432
|
+
local classify_json
|
|
433
|
+
classify_json=$(cmd_classify "$pr_num" 2>&1 | grep -A100 '{' | head -20)
|
|
434
|
+
|
|
435
|
+
local risk_level
|
|
436
|
+
risk_level=$(echo "$classify_json" | jq -r '.risk_level' 2>/dev/null || echo "unknown")
|
|
437
|
+
|
|
438
|
+
case "$risk_level" in
|
|
439
|
+
low)
|
|
440
|
+
cmd_merge "$pr_num"
|
|
441
|
+
merged=$((merged + 1))
|
|
442
|
+
;;
|
|
443
|
+
medium)
|
|
444
|
+
gh pr approve "$pr_num" 2>/dev/null || true
|
|
445
|
+
approved=$((approved + 1))
|
|
446
|
+
;;
|
|
447
|
+
high)
|
|
448
|
+
flagged=$((flagged + 1))
|
|
449
|
+
;;
|
|
450
|
+
esac
|
|
451
|
+
processed=$((processed + 1))
|
|
452
|
+
done
|
|
453
|
+
|
|
454
|
+
echo ""
|
|
455
|
+
echo -e "${CYAN}${BOLD}═══ Batch Summary ═══${RESET}"
|
|
456
|
+
echo -e " Processed: ${processed} | Merged: ${merged} | Approved: ${approved} | Flagged: ${flagged}"
|
|
457
|
+
echo ""
|
|
458
|
+
|
|
459
|
+
emit_event "deps.batch.completed" "processed=$processed" "merged=$merged" "approved=$approved" "flagged=$flagged"
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
# ─── Health Report ──────────────────────────────────────────────────────────
|
|
463
|
+
|
|
464
|
+
cmd_report() {
|
|
465
|
+
if [[ -n "${NO_GITHUB:-}" ]]; then
|
|
466
|
+
warn "GitHub API disabled (NO_GITHUB set)"
|
|
467
|
+
return
|
|
468
|
+
fi
|
|
469
|
+
|
|
470
|
+
info "Generating dependency health report..."
|
|
471
|
+
echo ""
|
|
472
|
+
|
|
473
|
+
local prs
|
|
474
|
+
prs=$(gh pr list --author "dependabot[bot]" --author "renovate[bot]" --state open --json number,title,createdAt --template '{{json .}}' 2>/dev/null || echo '[]')
|
|
475
|
+
|
|
476
|
+
local total=0
|
|
477
|
+
local patch_count=0
|
|
478
|
+
local minor_count=0
|
|
479
|
+
local major_count=0
|
|
480
|
+
|
|
481
|
+
if [[ -n "$prs" && "$prs" != "[]" ]]; then
|
|
482
|
+
total=$(echo "$prs" | jq 'length')
|
|
483
|
+
|
|
484
|
+
echo "$prs" | jq -r '.[] | .title' | while read -r title; do
|
|
485
|
+
if [[ "$title" =~ from\ ([^ ]+)\ to\ ([^ ]+) ]]; then
|
|
486
|
+
local from_ver="${BASH_REMATCH[1]}"
|
|
487
|
+
local to_ver="${BASH_REMATCH[2]}"
|
|
488
|
+
local bump_type
|
|
489
|
+
bump_type=$(parse_version_bump "$from_ver" "$to_ver")
|
|
490
|
+
case "$bump_type" in
|
|
491
|
+
patch) patch_count=$((patch_count + 1)) ;;
|
|
492
|
+
minor) minor_count=$((minor_count + 1)) ;;
|
|
493
|
+
major) major_count=$((major_count + 1)) ;;
|
|
494
|
+
esac
|
|
495
|
+
fi
|
|
496
|
+
done
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
# Find oldest PR
|
|
500
|
+
local oldest_age="—"
|
|
501
|
+
if [[ -n "$prs" && "$prs" != "[]" ]]; then
|
|
502
|
+
local oldest_date
|
|
503
|
+
oldest_date=$(echo "$prs" | jq -r '.[0].createdAt')
|
|
504
|
+
if [[ -n "$oldest_date" && "$oldest_date" != "null" ]]; then
|
|
505
|
+
oldest_age="$(date -d "$oldest_date" '+%s' 2>/dev/null || echo '?') seconds ago"
|
|
506
|
+
fi
|
|
507
|
+
fi
|
|
508
|
+
|
|
509
|
+
echo -e "${CYAN}${BOLD}╔═════════════════════════════════════════════════════════╗${RESET}"
|
|
510
|
+
echo -e "${CYAN}${BOLD}║ Dependency Health Report ║${RESET}"
|
|
511
|
+
echo -e "${CYAN}${BOLD}╚═════════════════════════════════════════════════════════╝${RESET}"
|
|
512
|
+
echo ""
|
|
513
|
+
echo -e " ${BOLD}Open PRs:${RESET} ${total}"
|
|
514
|
+
echo -e " ├─ ${GREEN}Patches:${RESET} ${patch_count}"
|
|
515
|
+
echo -e " ├─ ${YELLOW}Minor:${RESET} ${minor_count}"
|
|
516
|
+
echo -e " └─ ${RED}Major:${RESET} ${major_count}"
|
|
517
|
+
echo ""
|
|
518
|
+
echo -e " ${BOLD}Oldest PR:${RESET} ${oldest_age}"
|
|
519
|
+
echo ""
|
|
520
|
+
|
|
521
|
+
emit_event "deps.report.generated" "total=$total" "patches=$patch_count" "minors=$minor_count" "majors=$major_count"
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# ─── Main ───────────────────────────────────────────────────────────────────
|
|
525
|
+
|
|
526
|
+
main() {
|
|
527
|
+
local cmd="${1:-help}"
|
|
528
|
+
shift 2>/dev/null || true
|
|
529
|
+
|
|
530
|
+
case "$cmd" in
|
|
531
|
+
scan) cmd_scan "$@" ;;
|
|
532
|
+
classify) cmd_classify "$@" ;;
|
|
533
|
+
test) cmd_test "$@" ;;
|
|
534
|
+
merge) cmd_merge "$@" ;;
|
|
535
|
+
batch) cmd_batch "$@" ;;
|
|
536
|
+
report) cmd_report "$@" ;;
|
|
537
|
+
help|-h|--help)
|
|
538
|
+
show_help
|
|
539
|
+
;;
|
|
540
|
+
*)
|
|
541
|
+
error "Unknown command: ${cmd}"
|
|
542
|
+
echo ""
|
|
543
|
+
show_help
|
|
544
|
+
exit 1
|
|
545
|
+
;;
|
|
546
|
+
esac
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
550
|
+
main "$@"
|
|
551
|
+
fi
|