@ranger1/dx 0.1.95 → 0.1.97
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/lib/cli/commands/core.js +88 -45
- package/lib/cli/commands/db.js +22 -32
- package/lib/cli/commands/export.js +7 -2
- package/lib/cli/commands/start.js +14 -33
- package/lib/cli/commands/worktree.js +0 -4
- package/lib/cli/dx-cli.js +46 -98
- package/lib/cli/flags.js +5 -7
- package/lib/cli/help-model.js +217 -0
- package/lib/cli/help-renderer.js +135 -0
- package/lib/cli/help-schema.js +549 -0
- package/lib/cli/help.js +114 -291
- package/lib/env.js +4 -5
- package/lib/worktree.js +37 -17
- package/package.json +1 -1
package/lib/cli/help.js
CHANGED
|
@@ -1,318 +1,141 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { buildHelpRuntimeContext, getCommandHelpModel, getGlobalHelpModel } from './help-model.js'
|
|
4
|
+
import { FLAG_DEFINITIONS } from './flags.js'
|
|
5
|
+
import { renderCommandHelp, renderGlobalHelp } from './help-renderer.js'
|
|
6
|
+
import { buildStrictHelpValidationContext, validateHelpConfig } from './help-schema.js'
|
|
1
7
|
import { getPackageVersion } from '../version.js'
|
|
2
8
|
|
|
3
|
-
|
|
4
|
-
const version = getPackageVersion()
|
|
5
|
-
const lines = [
|
|
6
|
-
'',
|
|
7
|
-
`DX CLI v${version} - 统一开发环境管理工具`,
|
|
8
|
-
'',
|
|
9
|
-
'用法:',
|
|
10
|
-
' dx <命令> [选项] [参数...]',
|
|
11
|
-
'',
|
|
12
|
-
'命令:',
|
|
13
|
-
' start [service] [环境标志] 启动/桥接服务',
|
|
14
|
-
' service: 由 dx/config/commands.json 的 start.* 决定(默认: dev)',
|
|
15
|
-
' stack: 需在 commands.json 配置 start.stack(internal: pm2-stack)',
|
|
16
|
-
' 环境标志: --dev, --staging, --prod, --test, --e2e(支持别名 --development、--production 等)',
|
|
17
|
-
" 说明: 传入 --staging 时会加载 '.env.staging(.local)' 层,同时复用生产构建/启动流程",
|
|
18
|
-
'',
|
|
19
|
-
' build [target] [环境标志] 构建应用',
|
|
20
|
-
' target: backend, shared, front, admin, mobile, all, sdk, affected (默认: all)',
|
|
21
|
-
' 环境标志: --dev, --staging, --prod, --test, --e2e(未指定时默认 --dev)',
|
|
22
|
-
'',
|
|
23
|
-
' deploy <target> [环境标志] 部署前端到 Vercel 或后端制品到远端主机',
|
|
24
|
-
' target: front, admin, merchant, telegram-bot, all, backend',
|
|
25
|
-
' 环境标志: --dev, --staging, --prod(默认: Vercel 目标为 --staging;backend 制品发布目标默认 --dev)',
|
|
26
|
-
'',
|
|
27
|
-
' install 安装依赖(使用 frozen-lockfile 确保版本一致)',
|
|
28
|
-
'',
|
|
29
|
-
' package backend [环境标志] 构建后端部署包(生成 backend-<version>-<sha>.tar.gz)',
|
|
30
|
-
' 环境标志: --dev, --staging, --prod, --test, --e2e(默认 --dev)',
|
|
31
|
-
' 产物位置: dist/backend/backend-*.tar.gz',
|
|
32
|
-
' 内含: dist/、node_modules(生产依赖)、prisma/、config/.env.runtime、bin/start.sh',
|
|
33
|
-
'',
|
|
34
|
-
' db [action] [环境标志] 数据库操作',
|
|
35
|
-
' action: generate, migrate, deploy, reset, seed, format, script',
|
|
36
|
-
' 用法示例:',
|
|
37
|
-
' dx db migrate --dev --name add_user_table # 创建新的迁移(开发环境需指定名称)',
|
|
38
|
-
' dx db deploy --dev # 应用开发环境已有迁移',
|
|
39
|
-
' dx db deploy --prod # 生产环境迁移(复用 deploy 流程,需确认)',
|
|
40
|
-
' dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)',
|
|
41
|
-
' dx db script fix-pending-transfer-status --prod # 运行数据库脚本(生产环境,需确认)',
|
|
42
|
-
' dx db script my-script --dev -- --arg1 --arg2 # 向脚本传递额外参数(-- 后面的部分)',
|
|
43
|
-
'',
|
|
44
|
-
' test [type] [target] [path] [-t pattern] 运行测试',
|
|
45
|
-
' type: e2e, unit (默认: e2e)',
|
|
46
|
-
' target: 由 commands.json 的 test.<type>.<target> 决定(e2e 默认会拒绝隐式 all)',
|
|
47
|
-
' path: 测试文件或目录路径 (guarded e2e target 必填,例如 backend/quantify)',
|
|
48
|
-
' -t pattern: 指定测试用例名称模式 (可选,需要和 path 一起使用)',
|
|
49
|
-
' 说明: guarded E2E target 禁止无路径或 all 全量执行,dx test e2e all 也不受支持',
|
|
50
|
-
'',
|
|
51
|
-
' worktree [action] [num...] Git Worktree管理',
|
|
52
|
-
' action: make, del, list, clean',
|
|
53
|
-
' num: issue编号 (make时需要1个,del时支持多个)',
|
|
54
|
-
' 支持批量删除: dx worktree del 123 456 789',
|
|
55
|
-
' 支持非交互式: dx worktree del 123 -Y',
|
|
56
|
-
' 注意:该封装与原生 git worktree 行为不同,勿混用',
|
|
57
|
-
'',
|
|
58
|
-
' lint 运行代码检查',
|
|
59
|
-
'',
|
|
60
|
-
' contracts [generate] 导出后端 OpenAPI 并生成 Zod 合约(packages/api-contracts)',
|
|
61
|
-
'',
|
|
62
|
-
' release version <semver> 统一同步 backend/front/admin-front 的版本号',
|
|
63
|
-
'',
|
|
64
|
-
' clean [target] 清理操作',
|
|
65
|
-
' target: all, deps (默认: all)',
|
|
66
|
-
'',
|
|
67
|
-
' cache [action] 缓存清理',
|
|
68
|
-
' action: clear (默认: clear)',
|
|
69
|
-
'',
|
|
70
|
-
' status 查看系统状态',
|
|
71
|
-
'',
|
|
72
|
-
' initial 同步根目录 skills 到 ~/.codex/skills 和 ~/.claude/skills(覆盖同名文件)',
|
|
73
|
-
'',
|
|
74
|
-
'选项:',
|
|
75
|
-
' --dev, --development 使用开发环境',
|
|
76
|
-
' --prod, --production 使用生产环境',
|
|
77
|
-
' --staging, --stage 使用预发环境(加载 .env.staging*.,复用生产流程)',
|
|
78
|
-
' --test 使用测试环境',
|
|
79
|
-
' --e2e 使用E2E测试环境',
|
|
80
|
-
' -Y, --yes 跳过所有确认提示',
|
|
81
|
-
' -v, --verbose 详细输出',
|
|
82
|
-
' -h, --help 显示此帮助信息',
|
|
83
|
-
' -V, --version 显示版本号',
|
|
84
|
-
'',
|
|
85
|
-
'示例:',
|
|
86
|
-
' dx start stack # PM2 交互式服务栈(需在 commands.json 配置 start.stack)',
|
|
87
|
-
' dx start backend --dev # 启动后端开发服务',
|
|
88
|
-
' dx start front --dev # 启动用户前端开发服务',
|
|
89
|
-
' dx start admin --dev # 启动管理后台开发服务',
|
|
90
|
-
' dx start all # 同时启动所有开发服务(默认 --dev)',
|
|
91
|
-
' dx build all --prod # 构建所有应用(生产环境)',
|
|
92
|
-
' dx db deploy --dev # 应用开发环境数据库迁移',
|
|
93
|
-
' dx db reset --prod -Y # 重置生产数据库(跳过确认)',
|
|
94
|
-
' dx test e2e backend apps/backend/e2e/auth # 按目录运行后端 E2E',
|
|
95
|
-
' dx test e2e backend apps/backend/e2e/activity/activity.admin.e2e-spec.ts # 运行单个E2E测试文件',
|
|
96
|
-
' dx test e2e backend apps/backend/e2e/activity/activity.admin.e2e-spec.ts -t "should list all activity definitions" # 运行特定测试用例',
|
|
97
|
-
' dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts # 运行 Quantify E2E 文件',
|
|
98
|
-
' dx test unit backend apps/backend/src/modules/chat/chat.service.spec.ts # 运行单个后端单测文件',
|
|
99
|
-
' dx test e2e all # 不受支持,必须指定 target 和 path',
|
|
100
|
-
' dx deploy front --staging # 部署前端到 Vercel(staging)',
|
|
101
|
-
' dx deploy backend --prod # 构建 backend 制品并上传/部署到远端主机',
|
|
102
|
-
' dx deploy backend --build-only # 仅构建 backend 制品,不执行远端部署',
|
|
103
|
-
' dx worktree make 88 # 为issue #88创建worktree',
|
|
104
|
-
' dx worktree del 88 # 删除issue #88的worktree',
|
|
105
|
-
' dx worktree del 88 89 90 -Y # 批量删除多个worktree(非交互式)',
|
|
106
|
-
' dx worktree list # 列出所有worktree',
|
|
107
|
-
' dx clean deps # 清理并重新安装依赖',
|
|
108
|
-
' dx cache clear # 清除 Nx 与依赖缓存',
|
|
109
|
-
' dx contracts # 导出 OpenAPI 并生成 Zod 合约',
|
|
110
|
-
' dx release version 1.2.3 # 同步 backend/front/admin-front 版本号',
|
|
111
|
-
'',
|
|
112
|
-
' # Stagewise 桥接(固定端口,自动清理占用)',
|
|
113
|
-
' dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)',
|
|
114
|
-
' dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front)',
|
|
115
|
-
'',
|
|
116
|
-
' # Start 用法示例',
|
|
117
|
-
' dx start backend --prod # 以生产环境变量启动后端',
|
|
118
|
-
' dx start backend --dev # 以开发环境变量启动后端',
|
|
119
|
-
' dx start backend --e2e # 以 E2E 环境变量启动后端',
|
|
120
|
-
'',
|
|
121
|
-
'',
|
|
122
|
-
]
|
|
9
|
+
const HIDDEN_COMMANDS = new Set(['help'])
|
|
123
10
|
|
|
124
|
-
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
125
13
|
}
|
|
126
14
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
switch (name) {
|
|
131
|
-
case 'build':
|
|
132
|
-
console.log(`
|
|
133
|
-
build 命令用法:
|
|
134
|
-
dx build <target> [环境标志]
|
|
135
|
-
|
|
136
|
-
参数说明:
|
|
137
|
-
target: backend, front, admin, shared, mobile, sdk, all, affected
|
|
138
|
-
环境标志: --dev、--staging、--prod、--test、--e2e(默认 --dev)
|
|
139
|
-
|
|
140
|
-
限制说明:
|
|
141
|
-
显式传入环境标志时,必须是该 target 实际支持的环境;不支持时会直接报错
|
|
142
|
-
|
|
143
|
-
常见示例:
|
|
144
|
-
dx build backend --staging # 使用 staging 环境变量构建后端 (prod 流程)
|
|
145
|
-
dx build front --prod # 强制以生产配置构建前端
|
|
146
|
-
dx build mobile --staging # 构建移动端 APK (staging 环境)
|
|
147
|
-
dx build mobile --prod # 构建移动端 APK (生产环境)
|
|
148
|
-
dx build affected --dev # 针对受影响项目执行开发态构建
|
|
149
|
-
|
|
150
|
-
提示: 可通过 dx build <target> 分别构建受影响应用。
|
|
151
|
-
`)
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
case 'db':
|
|
155
|
-
console.log(`
|
|
156
|
-
db 命令用法:
|
|
157
|
-
dx db <action> [options]
|
|
158
|
-
|
|
159
|
-
可选 action:
|
|
160
|
-
generate | migrate | deploy | reset | seed | format | script
|
|
161
|
-
|
|
162
|
-
环境说明:
|
|
163
|
-
通过 --dev、--staging、--prod、--test、--e2e 指定 APP_ENV(默认 --dev)
|
|
164
|
-
--staging 会加载 .env.staging*. 文件,并复用 prod 的 Prisma / Nx 流程
|
|
165
|
-
|
|
166
|
-
附加参数:
|
|
167
|
-
--name/-n <migration-name> # 开发环境执行 migrate 必填,禁止通过位置参数传递
|
|
168
|
-
|
|
169
|
-
帮助提示:
|
|
170
|
-
- 未提供迁移名称时命令会直接报错退出,避免 Prisma 进入交互式输入
|
|
171
|
-
- 仅允许在开发环境使用 \`dx db migrate --dev --name <migration-name>\` 创建迁移
|
|
172
|
-
- 非开发环境请使用 dx db deploy
|
|
173
|
-
- 使用模式示例: dx db migrate --dev --name init-user-table
|
|
174
|
-
- 如需仅执行已有迁移(本地/CI/生产),请使用 dx db deploy(无需 --name)
|
|
175
|
-
|
|
176
|
-
script 子命令:
|
|
177
|
-
dx db script <script-name> [环境标志] [-- <脚本参数>...]
|
|
178
|
-
运行位于 apps/backend/prisma/scripts/ 目录下的数据库脚本
|
|
179
|
-
|
|
180
|
-
脚本参数说明:
|
|
181
|
-
使用 -- 分隔符后可传递任意参数给目标脚本
|
|
182
|
-
例如: dx db script my-script --dev -- --skip-cleanup --note="test"
|
|
183
|
-
|
|
184
|
-
示例:
|
|
185
|
-
dx db migrate --dev --name init-user-table # 创建新迁移(开发环境)
|
|
186
|
-
dx db deploy --dev # 应用开发环境已有迁移
|
|
187
|
-
dx db deploy --staging # 复用生产命令,加载 staging 环境变量
|
|
188
|
-
dx db reset --prod -Y # 生产环境重置 (需确认)
|
|
189
|
-
dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)
|
|
190
|
-
dx db script fix-pending-transfer-status --prod -Y # 运行数据库脚本(生产环境,跳过确认)
|
|
191
|
-
dx db script guest-cleanup-verification --dev -- --help # 查看脚本帮助
|
|
192
|
-
dx db script guest-cleanup-verification --dev -- --skip-cleanup --note="dry run" # 传递脚本参数
|
|
193
|
-
`)
|
|
194
|
-
return
|
|
195
|
-
|
|
196
|
-
case 'deploy':
|
|
197
|
-
console.log(`
|
|
198
|
-
deploy 命令用法:
|
|
199
|
-
dx deploy <target> [环境标志] [选项]
|
|
200
|
-
|
|
201
|
-
参数说明:
|
|
202
|
-
target: front, admin, merchant, telegram-bot, all, backend
|
|
203
|
-
环境标志:
|
|
204
|
-
- Vercel 目标默认 --staging
|
|
205
|
-
- backend 制品发布目标默认 --dev
|
|
206
|
-
|
|
207
|
-
backend 制品发布(target=backend):
|
|
208
|
-
--build-only 仅本地构建并打包制品,不上传不远端部署
|
|
209
|
-
--skip-migration 远端部署时跳过 prisma migrate deploy
|
|
15
|
+
function loadDefaultCommands() {
|
|
16
|
+
const configDir = process.env.DX_CONFIG_DIR || join(process.cwd(), 'dx', 'config')
|
|
17
|
+
const commandsPath = join(configDir, 'commands.json')
|
|
210
18
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(commandsPath, 'utf8'))
|
|
21
|
+
} catch {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
216
25
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
dx deploy all --staging # 串行部署 front + admin + merchant
|
|
226
|
-
dx deploy backend --prod # 构建 backend 制品并部署到远端主机
|
|
227
|
-
dx deploy backend --build-only # 仅构建 backend 制品
|
|
228
|
-
`)
|
|
229
|
-
return
|
|
26
|
+
function deriveRegisteredCommands(commands = {}) {
|
|
27
|
+
const ordered = []
|
|
28
|
+
const seen = new Set()
|
|
29
|
+
const sources = [
|
|
30
|
+
Array.isArray(commands?.help?.commandOrder) ? commands.help.commandOrder : [],
|
|
31
|
+
Object.keys(commands?.help?.commands || {}),
|
|
32
|
+
Object.keys(commands).filter(name => !HIDDEN_COMMANDS.has(name) && name !== 'help'),
|
|
33
|
+
]
|
|
230
34
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
35
|
+
for (const entries of sources) {
|
|
36
|
+
for (const name of entries) {
|
|
37
|
+
if (typeof name !== 'string' || !name || seen.has(name)) continue
|
|
38
|
+
seen.add(name)
|
|
39
|
+
ordered.push(name)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
235
42
|
|
|
236
|
-
|
|
237
|
-
|
|
43
|
+
return ordered
|
|
44
|
+
}
|
|
238
45
|
|
|
239
|
-
|
|
240
|
-
|
|
46
|
+
function buildSyntheticCliContext(commands) {
|
|
47
|
+
const commandHandlers = Object.fromEntries(
|
|
48
|
+
deriveRegisteredCommands(commands).map(name => [name, () => {}]),
|
|
49
|
+
)
|
|
241
50
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
51
|
+
return {
|
|
52
|
+
invocation: 'dx',
|
|
53
|
+
commands,
|
|
54
|
+
commandHandlers,
|
|
55
|
+
flagDefinitions: FLAG_DEFINITIONS,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
245
58
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
59
|
+
function resolveCliContext(cliContext = null) {
|
|
60
|
+
if (isPlainObject(cliContext?.commands)) {
|
|
61
|
+
const runtimeContext = buildHelpRuntimeContext(cliContext)
|
|
62
|
+
validateHelpConfig(cliContext.commands, buildStrictHelpValidationContext(cliContext))
|
|
63
|
+
return {
|
|
64
|
+
invocation: cliContext.invocation || 'dx',
|
|
65
|
+
commands: cliContext.commands,
|
|
66
|
+
runtimeContext,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
249
69
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return
|
|
70
|
+
const commands = loadDefaultCommands()
|
|
71
|
+
if (!commands) return null
|
|
253
72
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
dx test [type] [target] [path] [-t pattern]
|
|
73
|
+
const syntheticCli = buildSyntheticCliContext(commands)
|
|
74
|
+
const runtimeContext = buildHelpRuntimeContext(syntheticCli)
|
|
75
|
+
validateHelpConfig(commands, buildStrictHelpValidationContext(syntheticCli))
|
|
258
76
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
77
|
+
return {
|
|
78
|
+
invocation: syntheticCli.invocation,
|
|
79
|
+
commands,
|
|
80
|
+
runtimeContext,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
264
83
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
84
|
+
function hasRenderableCommandModel(model = {}) {
|
|
85
|
+
return Boolean(
|
|
86
|
+
model?.usage ||
|
|
87
|
+
model?.summary ||
|
|
88
|
+
model?.targets?.length ||
|
|
89
|
+
model?.notes?.length ||
|
|
90
|
+
model?.examples?.length ||
|
|
91
|
+
model?.options?.length,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
269
94
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
95
|
+
function renderDynamicGlobalHelp(cliContext = null) {
|
|
96
|
+
const resolved = resolveCliContext(cliContext)
|
|
97
|
+
const version = getPackageVersion()
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
return renderGlobalHelp({
|
|
100
|
+
title: `DX CLI v${version}`,
|
|
101
|
+
invocation: 'dx',
|
|
102
|
+
})
|
|
103
|
+
}
|
|
279
104
|
|
|
280
|
-
|
|
281
|
-
console.log(`
|
|
282
|
-
contracts 命令用法:
|
|
283
|
-
dx contracts [generate|pull]
|
|
105
|
+
const model = getGlobalHelpModel(resolved.commands, resolved.runtimeContext)
|
|
284
106
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
107
|
+
return renderGlobalHelp({
|
|
108
|
+
title: `DX CLI v${version}`,
|
|
109
|
+
invocation: resolved.invocation,
|
|
110
|
+
...model,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
288
113
|
|
|
289
|
-
|
|
290
|
-
|
|
114
|
+
function renderDynamicCommandHelp(commandName, cliContext = null) {
|
|
115
|
+
const resolved = resolveCliContext(cliContext)
|
|
116
|
+
if (!resolved) return ''
|
|
291
117
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
OPENAPI_BASE_URL="https://api.example.com/api/v1" dx contracts
|
|
295
|
-
`)
|
|
296
|
-
return
|
|
118
|
+
const model = getCommandHelpModel(resolved.commands, commandName, resolved.runtimeContext)
|
|
119
|
+
if (!hasRenderableCommandModel(model)) return ''
|
|
297
120
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
121
|
+
return renderCommandHelp({
|
|
122
|
+
invocation: resolved.invocation,
|
|
123
|
+
...model,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
302
126
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
- apps/front/package.json
|
|
307
|
-
- apps/admin-front/package.json
|
|
127
|
+
export function showHelp(cliContext = null) {
|
|
128
|
+
console.log(renderDynamicGlobalHelp(cliContext))
|
|
129
|
+
}
|
|
308
130
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
`)
|
|
313
|
-
return
|
|
131
|
+
export function showCommandHelp(command, cliContext = null) {
|
|
132
|
+
const commandName = String(command || '').toLowerCase()
|
|
133
|
+
const dynamicOutput = renderDynamicCommandHelp(commandName, cliContext)
|
|
314
134
|
|
|
315
|
-
|
|
316
|
-
|
|
135
|
+
if (dynamicOutput) {
|
|
136
|
+
console.log(dynamicOutput)
|
|
137
|
+
return
|
|
317
138
|
}
|
|
139
|
+
|
|
140
|
+
showHelp(cliContext)
|
|
318
141
|
}
|
package/lib/env.js
CHANGED
|
@@ -21,7 +21,6 @@ export class EnvManager {
|
|
|
21
21
|
// 注意:'e2e' 在 dotenv 层使用独立层(.env.e2e),但在 NODE_ENV 上归并为 'test'
|
|
22
22
|
this.APP_TO_NODE_ENV = {
|
|
23
23
|
local: 'development',
|
|
24
|
-
dev: 'development',
|
|
25
24
|
development: 'development',
|
|
26
25
|
staging: 'production',
|
|
27
26
|
production: 'production',
|
|
@@ -51,8 +50,8 @@ export class EnvManager {
|
|
|
51
50
|
mapAppEnvToLayerEnv(appEnv) {
|
|
52
51
|
const env = String(appEnv || '').toLowerCase()
|
|
53
52
|
if (env === 'e2e') return 'e2e'
|
|
54
|
-
if (env === 'staging'
|
|
55
|
-
if (env === 'production'
|
|
53
|
+
if (env === 'staging') return 'staging'
|
|
54
|
+
if (env === 'production') return 'production'
|
|
56
55
|
if (env === 'test') return 'test'
|
|
57
56
|
return 'development'
|
|
58
57
|
}
|
|
@@ -79,9 +78,9 @@ export class EnvManager {
|
|
|
79
78
|
|
|
80
79
|
// 检测当前环境(用于选择 dotenv 层,如 .env.production/.env.e2e)
|
|
81
80
|
detectEnvironment(flags = {}) {
|
|
82
|
-
if (flags.prod
|
|
81
|
+
if (flags.prod) return 'production'
|
|
83
82
|
if (flags.staging) return 'staging'
|
|
84
|
-
if (flags.dev
|
|
83
|
+
if (flags.dev) return 'development'
|
|
85
84
|
if (flags.test) return 'test'
|
|
86
85
|
if (flags.e2e) return 'e2e'
|
|
87
86
|
|
package/lib/worktree.js
CHANGED
|
@@ -79,6 +79,7 @@ const shellEscape = value => {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const ensureTrailingSlash = value => (value.endsWith('/') ? value : `${value}/`)
|
|
82
|
+
const ISSUE_NUMBER_PATTERN = /^\d+$/
|
|
82
83
|
|
|
83
84
|
class WorktreeManager {
|
|
84
85
|
constructor() {
|
|
@@ -218,8 +219,27 @@ class WorktreeManager {
|
|
|
218
219
|
return path.join(this.baseDir, `${this.prefix}${issueNumber}`)
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
isStrictIssueNumber(issueNumber) {
|
|
223
|
+
return typeof issueNumber === 'string' && ISSUE_NUMBER_PATTERN.test(issueNumber)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
extractIssueNumberFromPath(worktreePath) {
|
|
227
|
+
const baseName = path.basename(worktreePath)
|
|
228
|
+
if (!baseName.startsWith(this.prefix)) {
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const issueNumber = baseName.slice(this.prefix.length)
|
|
233
|
+
return this.isStrictIssueNumber(issueNumber) ? issueNumber : null
|
|
234
|
+
}
|
|
235
|
+
|
|
221
236
|
// 创建 worktree
|
|
222
237
|
async make(issueNumber, options = {}) {
|
|
238
|
+
if (!this.isStrictIssueNumber(issueNumber)) {
|
|
239
|
+
logger.error('issue 编号必须为纯数字字符串')
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
|
|
223
243
|
const worktreePath = this.getWorktreePath(issueNumber)
|
|
224
244
|
const branchName = `issue-${issueNumber}`
|
|
225
245
|
const baseBranch = (options.baseBranch || 'main').trim()
|
|
@@ -567,13 +587,18 @@ class WorktreeManager {
|
|
|
567
587
|
|
|
568
588
|
// 删除 worktree(支持批量删除)
|
|
569
589
|
async del(issueNumbers, options = {}) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
590
|
+
if (!Array.isArray(issueNumbers)) {
|
|
591
|
+
logger.error('请提供 issue 编号数组')
|
|
592
|
+
return false
|
|
573
593
|
}
|
|
574
594
|
|
|
575
|
-
if (
|
|
576
|
-
logger.error('
|
|
595
|
+
if (issueNumbers.length === 0) {
|
|
596
|
+
logger.error('请至少提供一个 issue 编号')
|
|
597
|
+
return false
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (issueNumbers.some(issueNumber => !this.isStrictIssueNumber(issueNumber))) {
|
|
601
|
+
logger.error('issue 编号必须为纯数字字符串')
|
|
577
602
|
return false
|
|
578
603
|
}
|
|
579
604
|
|
|
@@ -854,9 +879,9 @@ class WorktreeManager {
|
|
|
854
879
|
const worktrees = this.parseWorktreeList(worktreeList)
|
|
855
880
|
|
|
856
881
|
// 过滤出 issue 相关的 worktree
|
|
857
|
-
const issueWorktrees = worktrees
|
|
858
|
-
|
|
859
|
-
|
|
882
|
+
const issueWorktrees = worktrees
|
|
883
|
+
.map(wt => ({ ...wt, issueNumber: this.extractIssueNumberFromPath(wt.path) }))
|
|
884
|
+
.filter(wt => wt.issueNumber)
|
|
860
885
|
|
|
861
886
|
if (issueWorktrees.length === 0) {
|
|
862
887
|
logger.info('没有找到 issue 相关的 worktree')
|
|
@@ -870,10 +895,6 @@ class WorktreeManager {
|
|
|
870
895
|
console.log('----\t----\t\t----\t\t\t----')
|
|
871
896
|
|
|
872
897
|
for (const wt of issueWorktrees) {
|
|
873
|
-
// 提取 issue 编号
|
|
874
|
-
const match = wt.path.match(/ai_monorepo_issue_(\d+)/)
|
|
875
|
-
const issueNum = match ? match[1] : '?'
|
|
876
|
-
|
|
877
898
|
// 检查状态
|
|
878
899
|
let status = '正常'
|
|
879
900
|
if (wt.locked) {
|
|
@@ -918,7 +939,7 @@ class WorktreeManager {
|
|
|
918
939
|
status = '无法访问'
|
|
919
940
|
}
|
|
920
941
|
|
|
921
|
-
console.log(`#${
|
|
942
|
+
console.log(`#${wt.issueNumber}\t${wt.branch || 'detached'}\t${wt.path}\t${status}`)
|
|
922
943
|
}
|
|
923
944
|
|
|
924
945
|
// 显示统计
|
|
@@ -990,10 +1011,9 @@ class WorktreeManager {
|
|
|
990
1011
|
}
|
|
991
1012
|
}
|
|
992
1013
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
issueNumbers.push(pathMatch[1])
|
|
1014
|
+
const issueNumber = this.extractIssueNumberFromPath(wt.path)
|
|
1015
|
+
if (issueNumber) {
|
|
1016
|
+
issueNumbers.push(issueNumber)
|
|
997
1017
|
}
|
|
998
1018
|
}
|
|
999
1019
|
|