@ranger1/dx 0.1.65 → 0.1.67
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/codex/skills/doctor/SKILL.md +45 -0
- package/codex/skills/doctor/agents/openai.yaml +4 -0
- package/codex/skills/doctor/scripts/doctor.sh +386 -0
- package/codex/skills/git-release/SKILL.md +11 -6
- package/codex/skills/online-debug-guard/SKILL.md +82 -0
- package/codex/skills/online-debug-guard/agents/openai.yaml +4 -0
- package/lib/vercel-deploy.js +15 -9
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doctor
|
|
3
|
+
description: Use when 需要在本机一次性体检并修复 Codex 开发环境,包括 python3/python 别名、dx 初始化、agent-browser+Chromium、ripgrep 与 multi_agent 特性状态。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Doctor
|
|
7
|
+
|
|
8
|
+
## 概览
|
|
9
|
+
|
|
10
|
+
执行本技能时,优先运行 `scripts/doctor.sh`,由脚本完成并行检测、自动修复、最多三轮重试与最终报告。
|
|
11
|
+
|
|
12
|
+
## 适用场景
|
|
13
|
+
|
|
14
|
+
- 新机器初始化 Codex 开发环境。
|
|
15
|
+
- 发现命令缺失或版本漂移,希望一次性修复。
|
|
16
|
+
- 需要确认 `codex features list` 中 `multi_agent` 为 `experimental true`。
|
|
17
|
+
|
|
18
|
+
## 执行步骤
|
|
19
|
+
|
|
20
|
+
1. 直接运行:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
24
|
+
bash "$CODEX_HOME/skills/doctor/scripts/doctor.sh"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. 若需限制轮次(默认 3):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
31
|
+
bash "$CODEX_HOME/skills/doctor/scripts/doctor.sh" --max-rounds 3
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 脚本职责
|
|
35
|
+
|
|
36
|
+
- 并行检测:`python3`、`python` 别名、`pnpm`、`dx`、`agent-browser`、`rg`、`multi_agent`。
|
|
37
|
+
- 自动修复:按平台选择安装器修复缺失项。
|
|
38
|
+
- 强制执行:每轮都运行 `pnpm add -g @ranger1/dx@latest && dx initial`。
|
|
39
|
+
- agent-browser:安装/升级并执行 Chromium 安装。
|
|
40
|
+
- 结果输出:展示每项状态、版本、关键信息;全部通过则退出 0,否则最多三轮后退出 1。
|
|
41
|
+
|
|
42
|
+
## 注意
|
|
43
|
+
|
|
44
|
+
- 某些安装步骤可能需要管理员权限(例如 `sudo` 或 Homebrew 写权限)。
|
|
45
|
+
- 若系统缺少包管理器,脚本会给出明确失败原因。
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
MAX_ROUNDS=3
|
|
5
|
+
while [[ $# -gt 0 ]]; do
|
|
6
|
+
case "$1" in
|
|
7
|
+
--max-rounds)
|
|
8
|
+
MAX_ROUNDS="${2:-3}"
|
|
9
|
+
shift 2
|
|
10
|
+
;;
|
|
11
|
+
*)
|
|
12
|
+
shift
|
|
13
|
+
;;
|
|
14
|
+
esac
|
|
15
|
+
done
|
|
16
|
+
|
|
17
|
+
if ! [[ "$MAX_ROUNDS" =~ ^[0-9]+$ ]] || [[ "$MAX_ROUNDS" -lt 1 ]]; then
|
|
18
|
+
echo "[doctor] --max-rounds 必须是正整数" >&2
|
|
19
|
+
exit 2
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
CACHE_ROOT="${PWD}/.cache/doctor"
|
|
23
|
+
mkdir -p "$CACHE_ROOT"
|
|
24
|
+
|
|
25
|
+
LAST_CHECK_DIR=""
|
|
26
|
+
DX_FORCE_OK=0
|
|
27
|
+
DX_FORCE_MSG=""
|
|
28
|
+
|
|
29
|
+
run_silent() {
|
|
30
|
+
"$@" >/dev/null 2>&1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
detect_pkg_manager() {
|
|
34
|
+
if command -v brew >/dev/null 2>&1; then echo "brew"; return; fi
|
|
35
|
+
if command -v apt-get >/dev/null 2>&1; then echo "apt"; return; fi
|
|
36
|
+
if command -v dnf >/dev/null 2>&1; then echo "dnf"; return; fi
|
|
37
|
+
if command -v yum >/dev/null 2>&1; then echo "yum"; return; fi
|
|
38
|
+
if command -v pacman >/dev/null 2>&1; then echo "pacman"; return; fi
|
|
39
|
+
echo "none"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
install_python3() {
|
|
43
|
+
local pm
|
|
44
|
+
pm="$(detect_pkg_manager)"
|
|
45
|
+
case "$pm" in
|
|
46
|
+
brew)
|
|
47
|
+
brew install python
|
|
48
|
+
;;
|
|
49
|
+
apt)
|
|
50
|
+
sudo apt-get update && sudo apt-get install -y python3 python3-venv python3-pip
|
|
51
|
+
;;
|
|
52
|
+
dnf)
|
|
53
|
+
sudo dnf install -y python3 python3-pip
|
|
54
|
+
;;
|
|
55
|
+
yum)
|
|
56
|
+
sudo yum install -y python3 python3-pip
|
|
57
|
+
;;
|
|
58
|
+
pacman)
|
|
59
|
+
sudo pacman -Sy --noconfirm python
|
|
60
|
+
;;
|
|
61
|
+
*)
|
|
62
|
+
echo "未检测到可用包管理器,无法安装 python3" >&2
|
|
63
|
+
return 1
|
|
64
|
+
;;
|
|
65
|
+
esac
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ensure_python_alias() {
|
|
69
|
+
local py3 py3_dir target_dir target
|
|
70
|
+
py3="$(command -v python3 || true)"
|
|
71
|
+
if [[ -z "$py3" ]]; then
|
|
72
|
+
echo "python3 不存在,无法创建 python 别名" >&2
|
|
73
|
+
return 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if command -v python >/dev/null 2>&1; then
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
py3_dir="$(dirname "$py3")"
|
|
81
|
+
if [[ -w "$py3_dir" ]]; then
|
|
82
|
+
target_dir="$py3_dir"
|
|
83
|
+
elif [[ -w "/usr/local/bin" ]]; then
|
|
84
|
+
target_dir="/usr/local/bin"
|
|
85
|
+
elif [[ -w "/opt/homebrew/bin" ]]; then
|
|
86
|
+
target_dir="/opt/homebrew/bin"
|
|
87
|
+
else
|
|
88
|
+
target_dir="$HOME/.local/bin"
|
|
89
|
+
mkdir -p "$target_dir"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
target="$target_dir/python"
|
|
93
|
+
ln -sf "$py3" "$target"
|
|
94
|
+
|
|
95
|
+
if [[ "$target_dir" == "$HOME/.local/bin" ]]; then
|
|
96
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
command -v python >/dev/null 2>&1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
install_rg() {
|
|
103
|
+
local pm
|
|
104
|
+
pm="$(detect_pkg_manager)"
|
|
105
|
+
case "$pm" in
|
|
106
|
+
brew)
|
|
107
|
+
brew install ripgrep
|
|
108
|
+
;;
|
|
109
|
+
apt)
|
|
110
|
+
sudo apt-get update && sudo apt-get install -y ripgrep
|
|
111
|
+
;;
|
|
112
|
+
dnf)
|
|
113
|
+
sudo dnf install -y ripgrep
|
|
114
|
+
;;
|
|
115
|
+
yum)
|
|
116
|
+
sudo yum install -y ripgrep
|
|
117
|
+
;;
|
|
118
|
+
pacman)
|
|
119
|
+
sudo pacman -Sy --noconfirm ripgrep
|
|
120
|
+
;;
|
|
121
|
+
*)
|
|
122
|
+
echo "未检测到可用包管理器,无法安装 rg" >&2
|
|
123
|
+
return 1
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
install_pnpm() {
|
|
129
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
130
|
+
return 0
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if command -v npm >/dev/null 2>&1; then
|
|
134
|
+
npm install -g pnpm@latest
|
|
135
|
+
elif command -v brew >/dev/null 2>&1; then
|
|
136
|
+
brew install pnpm
|
|
137
|
+
else
|
|
138
|
+
echo "缺少 npm/brew,无法安装 pnpm" >&2
|
|
139
|
+
return 1
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ensure_agent_browser() {
|
|
144
|
+
if command -v npm >/dev/null 2>&1; then
|
|
145
|
+
npm install -g agent-browser@latest
|
|
146
|
+
elif command -v pnpm >/dev/null 2>&1; then
|
|
147
|
+
pnpm add -g agent-browser@latest
|
|
148
|
+
elif command -v brew >/dev/null 2>&1; then
|
|
149
|
+
brew install agent-browser
|
|
150
|
+
else
|
|
151
|
+
echo "缺少 npm/pnpm/brew,无法安装 agent-browser" >&2
|
|
152
|
+
return 1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
if ! command -v agent-browser >/dev/null 2>&1; then
|
|
156
|
+
echo "agent-browser 安装后仍不可用" >&2
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
if ! run_silent agent-browser install; then
|
|
161
|
+
run_silent agent-browser install --with-deps || {
|
|
162
|
+
echo "agent-browser Chromium 安装失败" >&2
|
|
163
|
+
return 1
|
|
164
|
+
}
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
ensure_multi_agent() {
|
|
169
|
+
if ! command -v codex >/dev/null 2>&1; then
|
|
170
|
+
echo "codex 命令不存在" >&2
|
|
171
|
+
return 1
|
|
172
|
+
fi
|
|
173
|
+
codex features enable multi_agent >/dev/null 2>&1 || true
|
|
174
|
+
local line
|
|
175
|
+
line="$(codex features list 2>/dev/null | awk '$1=="multi_agent" {print $0}')"
|
|
176
|
+
[[ -n "$line" ]] || return 1
|
|
177
|
+
echo "$line" | grep -E "experimental[[:space:]]+true" >/dev/null 2>&1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
force_dx() {
|
|
181
|
+
if ! install_pnpm; then
|
|
182
|
+
DX_FORCE_OK=0
|
|
183
|
+
DX_FORCE_MSG="pnpm 不可用,无法执行 dx 强制初始化"
|
|
184
|
+
return 1
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
if pnpm add -g @ranger1/dx@latest >/dev/null 2>&1 && dx initial >/dev/null 2>&1; then
|
|
188
|
+
DX_FORCE_OK=1
|
|
189
|
+
DX_FORCE_MSG="已执行 pnpm add -g @ranger1/dx@latest && dx initial"
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
DX_FORCE_OK=0
|
|
194
|
+
DX_FORCE_MSG="强制命令执行失败:pnpm add -g @ranger1/dx@latest && dx initial"
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
write_check_file() {
|
|
199
|
+
local key="$1" ok="$2" ver="$3" msg="$4" file="$5"
|
|
200
|
+
printf '%s|%s|%s|%s\n' "$key" "$ok" "$ver" "$msg" >"$file"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
read_field() {
|
|
204
|
+
local key="$1" field="$2"
|
|
205
|
+
local f="$LAST_CHECK_DIR/${key}.res"
|
|
206
|
+
if [[ ! -f "$f" ]]; then
|
|
207
|
+
echo ""
|
|
208
|
+
return 0
|
|
209
|
+
fi
|
|
210
|
+
awk -F'|' -v idx="$field" '{print $idx}' "$f"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
check_ok() {
|
|
214
|
+
local key="$1"
|
|
215
|
+
[[ "$(read_field "$key" 2)" == "1" ]]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
run_parallel_checks() {
|
|
219
|
+
local round="$1"
|
|
220
|
+
local dir="$CACHE_ROOT/round-${round}"
|
|
221
|
+
mkdir -p "$dir"
|
|
222
|
+
|
|
223
|
+
(
|
|
224
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
225
|
+
write_check_file "python3" "1" "$(python3 --version 2>&1 | head -n1)" "ok" "$dir/python3.res"
|
|
226
|
+
else
|
|
227
|
+
write_check_file "python3" "0" "" "python3 未安装" "$dir/python3.res"
|
|
228
|
+
fi
|
|
229
|
+
) &
|
|
230
|
+
|
|
231
|
+
(
|
|
232
|
+
if command -v python >/dev/null 2>&1; then
|
|
233
|
+
write_check_file "python_alias" "1" "$(python --version 2>&1 | head -n1)" "ok" "$dir/python_alias.res"
|
|
234
|
+
else
|
|
235
|
+
write_check_file "python_alias" "0" "" "python 别名不可用" "$dir/python_alias.res"
|
|
236
|
+
fi
|
|
237
|
+
) &
|
|
238
|
+
|
|
239
|
+
(
|
|
240
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
241
|
+
write_check_file "pnpm" "1" "pnpm $(pnpm --version 2>/dev/null | head -n1)" "ok" "$dir/pnpm.res"
|
|
242
|
+
else
|
|
243
|
+
write_check_file "pnpm" "0" "" "pnpm 未安装" "$dir/pnpm.res"
|
|
244
|
+
fi
|
|
245
|
+
) &
|
|
246
|
+
|
|
247
|
+
(
|
|
248
|
+
if command -v dx >/dev/null 2>&1; then
|
|
249
|
+
local v
|
|
250
|
+
v="$(dx --version 2>/dev/null | head -n1 || true)"
|
|
251
|
+
if [[ -z "$v" ]]; then
|
|
252
|
+
v="$(dx -v 2>/dev/null | head -n1 || true)"
|
|
253
|
+
fi
|
|
254
|
+
write_check_file "dx" "1" "${v:-dx (版本未知)}" "ok" "$dir/dx.res"
|
|
255
|
+
else
|
|
256
|
+
write_check_file "dx" "0" "" "dx 未安装" "$dir/dx.res"
|
|
257
|
+
fi
|
|
258
|
+
) &
|
|
259
|
+
|
|
260
|
+
(
|
|
261
|
+
if command -v agent-browser >/dev/null 2>&1; then
|
|
262
|
+
write_check_file "agent_browser" "1" "$(agent-browser --version 2>/dev/null | head -n1)" "ok" "$dir/agent_browser.res"
|
|
263
|
+
else
|
|
264
|
+
write_check_file "agent_browser" "0" "" "agent-browser 未安装" "$dir/agent_browser.res"
|
|
265
|
+
fi
|
|
266
|
+
) &
|
|
267
|
+
|
|
268
|
+
(
|
|
269
|
+
if command -v rg >/dev/null 2>&1; then
|
|
270
|
+
write_check_file "rg" "1" "$(rg --version 2>/dev/null | head -n1)" "ok" "$dir/rg.res"
|
|
271
|
+
else
|
|
272
|
+
write_check_file "rg" "0" "" "rg 未安装" "$dir/rg.res"
|
|
273
|
+
fi
|
|
274
|
+
) &
|
|
275
|
+
|
|
276
|
+
(
|
|
277
|
+
if command -v codex >/dev/null 2>&1; then
|
|
278
|
+
local line
|
|
279
|
+
line="$(codex features list 2>/dev/null | awk '$1=="multi_agent" {print $0}')"
|
|
280
|
+
if [[ -n "$line" ]] && echo "$line" | grep -E "experimental[[:space:]]+true" >/dev/null 2>&1; then
|
|
281
|
+
write_check_file "multi_agent" "1" "$line" "ok" "$dir/multi_agent.res"
|
|
282
|
+
else
|
|
283
|
+
write_check_file "multi_agent" "0" "${line:-missing}" "multi_agent 不是 experimental true" "$dir/multi_agent.res"
|
|
284
|
+
fi
|
|
285
|
+
else
|
|
286
|
+
write_check_file "multi_agent" "0" "" "codex 命令不存在" "$dir/multi_agent.res"
|
|
287
|
+
fi
|
|
288
|
+
) &
|
|
289
|
+
|
|
290
|
+
wait
|
|
291
|
+
LAST_CHECK_DIR="$dir"
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
all_good() {
|
|
295
|
+
local keys="python3 python_alias pnpm dx agent_browser rg multi_agent"
|
|
296
|
+
local k
|
|
297
|
+
for k in $keys; do
|
|
298
|
+
if ! check_ok "$k"; then
|
|
299
|
+
return 1
|
|
300
|
+
fi
|
|
301
|
+
done
|
|
302
|
+
[[ "$DX_FORCE_OK" == "1" ]]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
print_report() {
|
|
306
|
+
local round="$1"
|
|
307
|
+
echo
|
|
308
|
+
echo "===== Doctor 报告(第 ${round} 轮)====="
|
|
309
|
+
printf '%-14s | %-4s | %-40s | %s\n' "检查项" "状态" "版本" "说明"
|
|
310
|
+
printf '%-14s-+-%-4s-+-%-40s-+-%s\n' "--------------" "----" "----------------------------------------" "------------------------------"
|
|
311
|
+
|
|
312
|
+
local keys="python3 python_alias pnpm dx agent_browser rg multi_agent"
|
|
313
|
+
local k ok txt ver msg
|
|
314
|
+
for k in $keys; do
|
|
315
|
+
ok="$(read_field "$k" 2)"
|
|
316
|
+
txt="FAIL"
|
|
317
|
+
[[ "$ok" == "1" ]] && txt="PASS"
|
|
318
|
+
ver="$(read_field "$k" 3)"
|
|
319
|
+
msg="$(read_field "$k" 4)"
|
|
320
|
+
printf '%-14s | %-4s | %-40s | %s\n' "$k" "$txt" "$ver" "$msg"
|
|
321
|
+
done
|
|
322
|
+
|
|
323
|
+
if [[ "$DX_FORCE_OK" == "1" ]]; then
|
|
324
|
+
printf '%-14s | %-4s | %-40s | %s\n' "dx_force" "PASS" "@ranger1/dx@latest" "$DX_FORCE_MSG"
|
|
325
|
+
else
|
|
326
|
+
printf '%-14s | %-4s | %-40s | %s\n' "dx_force" "FAIL" "@ranger1/dx@latest" "$DX_FORCE_MSG"
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
if command -v node >/dev/null 2>&1; then
|
|
330
|
+
echo "node: $(node -v 2>/dev/null | head -n1)"
|
|
331
|
+
fi
|
|
332
|
+
if command -v npm >/dev/null 2>&1; then
|
|
333
|
+
echo "npm: $(npm -v 2>/dev/null | head -n1)"
|
|
334
|
+
fi
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for round in $(seq 1 "$MAX_ROUNDS"); do
|
|
338
|
+
echo "[doctor] 第 ${round}/${MAX_ROUNDS} 轮:并行检测"
|
|
339
|
+
run_parallel_checks "$round"
|
|
340
|
+
|
|
341
|
+
echo "[doctor] 第 ${round}/${MAX_ROUNDS} 轮:修复阶段"
|
|
342
|
+
|
|
343
|
+
if ! check_ok "python3"; then
|
|
344
|
+
echo "[doctor] 安装 python3"
|
|
345
|
+
install_python3 || true
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if ! check_ok "python_alias"; then
|
|
349
|
+
echo "[doctor] 建立 python -> python3 调用能力"
|
|
350
|
+
ensure_python_alias || true
|
|
351
|
+
fi
|
|
352
|
+
|
|
353
|
+
if ! check_ok "rg"; then
|
|
354
|
+
echo "[doctor] 安装 rg"
|
|
355
|
+
install_rg || true
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
if ! check_ok "agent_browser"; then
|
|
359
|
+
echo "[doctor] 安装/升级 agent-browser 并安装 Chromium"
|
|
360
|
+
ensure_agent_browser || true
|
|
361
|
+
else
|
|
362
|
+
echo "[doctor] agent-browser 已存在,执行升级与 Chromium 安装"
|
|
363
|
+
ensure_agent_browser || true
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
if ! check_ok "multi_agent"; then
|
|
367
|
+
echo "[doctor] 修正 multi_agent 特性开关"
|
|
368
|
+
ensure_multi_agent || true
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
echo "[doctor] 强制执行 dx 安装与初始化"
|
|
372
|
+
force_dx || true
|
|
373
|
+
|
|
374
|
+
echo "[doctor] 第 ${round}/${MAX_ROUNDS} 轮:复检"
|
|
375
|
+
run_parallel_checks "${round}-post"
|
|
376
|
+
print_report "$round"
|
|
377
|
+
|
|
378
|
+
if all_good; then
|
|
379
|
+
echo "[doctor] 全部检查通过"
|
|
380
|
+
exit 0
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
done
|
|
384
|
+
|
|
385
|
+
echo "[doctor] 达到最大轮次 ${MAX_ROUNDS},仍有未通过项"
|
|
386
|
+
exit 1
|
|
@@ -7,7 +7,7 @@ description: 在 Git 仓库中执行标准化版本发布流程并自动生成
|
|
|
7
7
|
|
|
8
8
|
## 目标
|
|
9
9
|
|
|
10
|
-
在 `release/vX.Y.Z` 或 `release/vX.Y.Z-<prerelease>.N` 分支上,完成从发布前检查到 GitHub Release
|
|
10
|
+
在 `release/vX.Y.Z` 或 `release/vX.Y.Z-<prerelease>.N` 分支上,完成从发布前检查到 GitHub Release 创建的全流程;若当前不在 release 分支,则先从最新 `main` 自动创建目标 release 分支。
|
|
11
11
|
|
|
12
12
|
## 执行原则
|
|
13
13
|
|
|
@@ -23,12 +23,17 @@ description: 在 Git 仓库中执行标准化版本发布流程并自动生成
|
|
|
23
23
|
1. 检查工作区是否干净:`git status --porcelain`。
|
|
24
24
|
2. 若存在未提交变更,列出文件并终止流程。
|
|
25
25
|
3. 检查当前分支:`git branch --show-current`。
|
|
26
|
-
4.
|
|
27
|
-
|
|
26
|
+
4. 若当前分支不匹配 `^release/v\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$`,执行以下自动建分支流程(仅此场景执行):
|
|
27
|
+
- 同步远程与本地 `main`:`git fetch origin main --tags && git checkout main && git pull --ff-only origin main`。
|
|
28
|
+
- 获取上一个已发布版本(优先):`gh release list --limit 1 --json tagName,publishedAt --jq '.[0].tagName'`;若为空则回退 `git describe --tags --abbrev=0`。
|
|
29
|
+
- 解析版本号并将最后一位加一(例如 `v1.2.3 -> v1.2.4`),得到新分支版本 `<NEXT_VERSION>`。
|
|
30
|
+
- 创建并切换分支:`git checkout -b release/v<NEXT_VERSION>`。
|
|
31
|
+
5. 再次检查当前分支,必须匹配:`^release/v\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$`。
|
|
32
|
+
6. 从分支名提取版本号,例如:
|
|
28
33
|
- `release/v1.2.3` -> `v1.2.3` -> `1.2.3`
|
|
29
34
|
- `release/v1.2.3-beta.2` -> `v1.2.3-beta.2` -> `1.2.3-beta.2`
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
7. 检查目标 tag 是否已存在:`git tag -l "v<VERSION>"`。
|
|
36
|
+
8. 向用户确认版本号;若用户修改版本号,重新校验格式与 tag 冲突。
|
|
32
37
|
|
|
33
38
|
### 二、更新版本号
|
|
34
39
|
|
|
@@ -156,7 +161,7 @@ EOF
|
|
|
156
161
|
以下任一情况出现时终止流程并给出明确原因:
|
|
157
162
|
|
|
158
163
|
- 工作区存在未提交修改。
|
|
159
|
-
- 当前分支不符合 release
|
|
164
|
+
- 当前分支不符合 release 分支命名规则,且无法从 `main` 自动创建 release 分支。
|
|
160
165
|
- 版本号格式非法或与现有 tag 冲突。
|
|
161
166
|
- 自上次发布以来无新提交。
|
|
162
167
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: online-debug-guard
|
|
3
|
+
description: 在线调试安全护栏技能。用于排查数据库、Redis、服务配置等线上或准线上问题时,强制执行环境识别、模式校验和本地环境文件存在性检查,防止误连误操作。触发场景:用户提到在线调试、连库排查、查 Redis、生产环境排障、按指定环境排查问题。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 在线调试安全护栏
|
|
7
|
+
|
|
8
|
+
## 概览
|
|
9
|
+
|
|
10
|
+
执行在线调试前先做硬性门禁,未通过即停止。仅在门禁通过后再进行排查动作,默认使用只读方式获取信息。
|
|
11
|
+
|
|
12
|
+
## 执行流程(必须按顺序)
|
|
13
|
+
|
|
14
|
+
1. 识别目标环境
|
|
15
|
+
2. 校验协作模式与环境文件
|
|
16
|
+
3. 门禁通过后执行调试
|
|
17
|
+
|
|
18
|
+
任一步失败都必须立即终止,不得继续后续步骤。
|
|
19
|
+
|
|
20
|
+
## 步骤 1:识别目标环境
|
|
21
|
+
|
|
22
|
+
只接受 `development`、`staging`、`production` 三种值。
|
|
23
|
+
|
|
24
|
+
如果用户调用技能时未显式给出环境:
|
|
25
|
+
- 先询问用户当前环境(只给这三个选项)。
|
|
26
|
+
- 若用户未回复或信息仍不明确,则默认 `development`,并明确告知本次按 `development` 执行。
|
|
27
|
+
|
|
28
|
+
如果用户给了其他环境名(例如 `prod1`、`test`):
|
|
29
|
+
- 立即终止。
|
|
30
|
+
- 明确提示“环境无效,仅支持 development/staging/production”。
|
|
31
|
+
|
|
32
|
+
## 步骤 2:门禁校验
|
|
33
|
+
|
|
34
|
+
### 2.1 production 环境的模式限制
|
|
35
|
+
|
|
36
|
+
当环境是 `production` 时,必须先确认当前处于 `Plan` 模式。
|
|
37
|
+
|
|
38
|
+
如果不是 `Plan` 模式:
|
|
39
|
+
- 立即终止。
|
|
40
|
+
- 使用如下提示:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
已终止:当前为 production 环境,但会话不在 Plan 模式。
|
|
44
|
+
请切换到 Plan 模式后再继续在线调试。
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2.2 环境文件存在性校验
|
|
48
|
+
|
|
49
|
+
将项目根目录作为基准,检查以下文件:
|
|
50
|
+
- `.env.${ENV_NAME}`
|
|
51
|
+
- `.env.${ENV_NAME}.local`
|
|
52
|
+
|
|
53
|
+
校验规则:
|
|
54
|
+
- 两个文件都必须存在。
|
|
55
|
+
- 若 `.env.${ENV_NAME}.local` 不存在,视为环境指定有误,立即终止。
|
|
56
|
+
|
|
57
|
+
终止提示模板:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
已终止:未找到 .env.${ENV_NAME}.local,判定环境指定有误。
|
|
61
|
+
请确认环境仅为 development/staging/production,且对应本地覆盖文件存在。
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 步骤 3:在线调试执行规范
|
|
65
|
+
|
|
66
|
+
门禁通过后,才允许执行调试。
|
|
67
|
+
|
|
68
|
+
调试时遵循:
|
|
69
|
+
- 默认只读:优先使用查询、检查、对比,不做写入。
|
|
70
|
+
- 允许使用环境文件中的连接信息访问数据库、Redis 等依赖来定位问题。
|
|
71
|
+
- 在未获用户明确授权前,禁止执行会改变状态的操作(例如写库、删键、迁移、重启、发布)。
|
|
72
|
+
- 输出结论时必须包含:目标环境、已通过的门禁项、关键证据、下一步建议。
|
|
73
|
+
|
|
74
|
+
## 标准开场模板
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
开始在线调试前先执行安全门禁:
|
|
78
|
+
1) 确认环境(development/staging/production)
|
|
79
|
+
2) production 场景校验是否为 Plan 模式
|
|
80
|
+
3) 校验 .env.${ENV_NAME} 与 .env.${ENV_NAME}.local 是否存在
|
|
81
|
+
任一失败将立即终止,避免误操作。
|
|
82
|
+
```
|
package/lib/vercel-deploy.js
CHANGED
|
@@ -169,8 +169,13 @@ function readLinkedProjectContext(contextRoot) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
function clearLinkedProjectContext(contextRoot) {
|
|
172
|
-
const
|
|
173
|
-
rmSync(
|
|
172
|
+
const linkDir = join(contextRoot, '.vercel')
|
|
173
|
+
rmSync(linkDir, { force: true, recursive: true })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function clearVercelBuildOutput(contextRoot) {
|
|
177
|
+
const outputDir = join(contextRoot, '.vercel', 'output')
|
|
178
|
+
rmSync(outputDir, { force: true, recursive: true })
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
async function runVercel(args, options = {}) {
|
|
@@ -376,9 +381,8 @@ export async function deployToVercel(target, options = {}) {
|
|
|
376
381
|
`检测到 ${VERCEL_PROJECT_LINK_PATH} 但解析失败: ${linkedContext.parseError.message}`
|
|
377
382
|
)
|
|
378
383
|
if (strictContext) {
|
|
379
|
-
logger.
|
|
380
|
-
|
|
381
|
-
return
|
|
384
|
+
logger.warn('strictContext 已开启:自动清理 .vercel 后继续部署')
|
|
385
|
+
clearLinkedProjectContext(prebuiltCwd)
|
|
382
386
|
}
|
|
383
387
|
}
|
|
384
388
|
|
|
@@ -389,11 +393,11 @@ export async function deployToVercel(target, options = {}) {
|
|
|
389
393
|
` 本地链接: org=${maskIdentifier(linkedContext.orgId)} project=${maskIdentifier(linkedContext.projectId)}`
|
|
390
394
|
)
|
|
391
395
|
if (strictContext) {
|
|
392
|
-
logger.
|
|
393
|
-
|
|
394
|
-
|
|
396
|
+
logger.warn('strictContext 已开启:检测到冲突,自动清理 .vercel 后继续部署')
|
|
397
|
+
clearLinkedProjectContext(prebuiltCwd)
|
|
398
|
+
} else {
|
|
399
|
+
logger.warn('strictContext 已关闭,继续执行(可能存在误部署风险)')
|
|
395
400
|
}
|
|
396
|
-
logger.warn('strictContext 已关闭,继续执行(可能存在误部署风险)')
|
|
397
401
|
}
|
|
398
402
|
|
|
399
403
|
logger.info(
|
|
@@ -432,6 +436,8 @@ export async function deployToVercel(target, options = {}) {
|
|
|
432
436
|
if (strictContext && process.env.DX_VERCEL_KEEP_LINK !== '1') {
|
|
433
437
|
clearLinkedProjectContext(prebuiltCwd)
|
|
434
438
|
}
|
|
439
|
+
// 避免 Vercel 本地预构建产物残留导致 EEXIST(如 _global-error.func 符号链接冲突)
|
|
440
|
+
clearVercelBuildOutput(prebuiltCwd)
|
|
435
441
|
|
|
436
442
|
// 第一步:本地构建
|
|
437
443
|
logger.step(`本地构建 ${t} (${environment})`)
|