@ranger1/dx 0.1.96 → 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/help.js CHANGED
@@ -1,319 +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
- export function showHelp() {
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|--name 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 / --name 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 unit backend apps/backend/src/modules/chat/chat.service.spec.ts --name "should create chat" # 运行单测中的特定用例',
98
- ' dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts # 运行 Quantify E2E 文件',
99
- ' dx test unit backend apps/backend/src/modules/chat/chat.service.spec.ts # 运行单个后端单测文件',
100
- ' dx test e2e all # 不受支持,必须指定 target 和 path',
101
- ' dx deploy front --staging # 部署前端到 Vercel(staging)',
102
- ' dx deploy backend --prod # 构建 backend 制品并上传/部署到远端主机',
103
- ' dx deploy backend --build-only # 仅构建 backend 制品,不执行远端部署',
104
- ' dx worktree make 88 # 为issue #88创建worktree',
105
- ' dx worktree del 88 # 删除issue #88的worktree',
106
- ' dx worktree del 88 89 90 -Y # 批量删除多个worktree(非交互式)',
107
- ' dx worktree list # 列出所有worktree',
108
- ' dx clean deps # 清理并重新安装依赖',
109
- ' dx cache clear # 清除 Nx 与依赖缓存',
110
- ' dx contracts # 导出 OpenAPI 并生成 Zod 合约',
111
- ' dx release version 1.2.3 # 同步 backend/front/admin-front 版本号',
112
- '',
113
- ' # Stagewise 桥接(固定端口,自动清理占用)',
114
- ' dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)',
115
- ' dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front)',
116
- '',
117
- ' # Start 用法示例',
118
- ' dx start backend --prod # 以生产环境变量启动后端',
119
- ' dx start backend --dev # 以开发环境变量启动后端',
120
- ' dx start backend --e2e # 以 E2E 环境变量启动后端',
121
- '',
122
- '',
123
- ]
9
+ const HIDDEN_COMMANDS = new Set(['help'])
124
10
 
125
- console.log(lines.join('\n'))
11
+ function isPlainObject(value) {
12
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
126
13
  }
127
14
 
128
- export function showCommandHelp(command) {
129
- const name = String(command || '').toLowerCase()
130
-
131
- switch (name) {
132
- case 'build':
133
- console.log(`
134
- build 命令用法:
135
- dx build <target> [环境标志]
136
-
137
- 参数说明:
138
- target: backend, front, admin, shared, mobile, sdk, all, affected
139
- 环境标志: --dev、--staging、--prod、--test、--e2e(默认 --dev)
140
-
141
- 限制说明:
142
- 显式传入环境标志时,必须是该 target 实际支持的环境;不支持时会直接报错
143
-
144
- 常见示例:
145
- dx build backend --staging # 使用 staging 环境变量构建后端 (prod 流程)
146
- dx build front --prod # 强制以生产配置构建前端
147
- dx build mobile --staging # 构建移动端 APK (staging 环境)
148
- dx build mobile --prod # 构建移动端 APK (生产环境)
149
- dx build affected --dev # 针对受影响项目执行开发态构建
150
-
151
- 提示: 可通过 dx build <target> 分别构建受影响应用。
152
- `)
153
- return
154
-
155
- case 'db':
156
- console.log(`
157
- db 命令用法:
158
- dx db <action> [options]
159
-
160
- 可选 action:
161
- generate | migrate | deploy | reset | seed | format | script
162
-
163
- 环境说明:
164
- 通过 --dev、--staging、--prod、--test、--e2e 指定 APP_ENV(默认 --dev)
165
- --staging 会加载 .env.staging*. 文件,并复用 prod 的 Prisma / Nx 流程
166
-
167
- 附加参数:
168
- --name/-n <migration-name> # 开发环境执行 migrate 必填,禁止通过位置参数传递
169
-
170
- 帮助提示:
171
- - 未提供迁移名称时命令会直接报错退出,避免 Prisma 进入交互式输入
172
- - 仅允许在开发环境使用 \`dx db migrate --dev --name <migration-name>\` 创建迁移
173
- - 非开发环境请使用 dx db deploy
174
- - 使用模式示例: dx db migrate --dev --name init-user-table
175
- - 如需仅执行已有迁移(本地/CI/生产),请使用 dx db deploy(无需 --name)
176
-
177
- script 子命令:
178
- dx db script <script-name> [环境标志] [-- <脚本参数>...]
179
- 运行位于 apps/backend/prisma/scripts/ 目录下的数据库脚本
180
-
181
- 脚本参数说明:
182
- 使用 -- 分隔符后可传递任意参数给目标脚本
183
- 例如: dx db script my-script --dev -- --skip-cleanup --note="test"
184
-
185
- 示例:
186
- dx db migrate --dev --name init-user-table # 创建新迁移(开发环境)
187
- dx db deploy --dev # 应用开发环境已有迁移
188
- dx db deploy --staging # 复用生产命令,加载 staging 环境变量
189
- dx db reset --prod -Y # 生产环境重置 (需确认)
190
- dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)
191
- dx db script fix-pending-transfer-status --prod -Y # 运行数据库脚本(生产环境,跳过确认)
192
- dx db script guest-cleanup-verification --dev -- --help # 查看脚本帮助
193
- dx db script guest-cleanup-verification --dev -- --skip-cleanup --note="dry run" # 传递脚本参数
194
- `)
195
- return
196
-
197
- case 'deploy':
198
- console.log(`
199
- deploy 命令用法:
200
- dx deploy <target> [环境标志] [选项]
201
-
202
- 参数说明:
203
- target: front, admin, merchant, telegram-bot, all, backend
204
- 环境标志:
205
- - Vercel 目标默认 --staging
206
- - backend 制品发布目标默认 --dev
207
-
208
- backend 制品发布(target=backend):
209
- --build-only 仅本地构建并打包制品,不上传不远端部署
210
- --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')
211
18
 
212
- Telegram Webhook(仅 target=telegram-bot 生效):
213
- --webhook-path <path> 对外 webhook 路径(默认 /api/webhook)
214
- --webhook-dry-run 只打印将设置的 URL,不调用 Telegram API
215
- --strict-webhook 显式开启严格校验(默认已严格)
216
- --no-strict-webhook 关闭严格校验(仅告警)
19
+ try {
20
+ return JSON.parse(readFileSync(commandsPath, 'utf8'))
21
+ } catch {
22
+ return null
23
+ }
24
+ }
217
25
 
218
- 常见示例:
219
- dx deploy front --staging # 部署用户前端(staging)
220
- dx deploy admin --prod # 部署管理后台(生产)
221
- dx deploy merchant --staging # 部署商家前端(staging)
222
- dx deploy telegram-bot --staging # 部署 Telegram Bot + 自动配置 Webhook
223
- dx deploy telegram-bot --staging --webhook-path /webhook # 使用短路径(rewrite 到 /api/webhook)
224
- dx deploy telegram-bot --prod --webhook-dry-run # 仅打印,不实际调用 Telegram
225
- dx deploy telegram-bot --dev --no-strict-webhook # 开发环境显式降级为仅告警
226
- dx deploy all --staging # 串行部署 front + admin + merchant
227
- dx deploy backend --prod # 构建 backend 制品并部署到远端主机
228
- dx deploy backend --build-only # 仅构建 backend 制品
229
- `)
230
- 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
+ ]
231
34
 
232
- case 'start':
233
- console.log(`
234
- start 命令用法:
235
- dx start <service> [环境标志]
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
+ }
236
42
 
237
- 服务说明:
238
- service: backend, dev, stagewise-front, stagewise-admin
43
+ return ordered
44
+ }
239
45
 
240
- 环境说明:
241
- 支持 --dev、--staging、--prod、--test、--e2e。--staging 会注入 .env.staging*. 层并复用 prod 启动流程
46
+ function buildSyntheticCliContext(commands) {
47
+ const commandHandlers = Object.fromEntries(
48
+ deriveRegisteredCommands(commands).map(name => [name, () => {}]),
49
+ )
242
50
 
243
- 限制说明:
244
- 未指定 service 时默认使用 dev 套件,仅允许 --dev
245
- stagewise 等单层 start 目标默认视为开发态目标,仅允许 --dev
51
+ return {
52
+ invocation: 'dx',
53
+ commands,
54
+ commandHandlers,
55
+ flagDefinitions: FLAG_DEFINITIONS,
56
+ }
57
+ }
246
58
 
247
- 常见示例:
248
- dx start backend --staging # 使用 staging 配置启动后端 (生产模式流程)
249
- dx start stagewise-front # Stagewise 桥接用户前端,端口 3001 -> 3002
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
+ }
250
69
 
251
- 提示: service 省略时默认启动 dev 套件,可结合 --dev/--staging/--prod 标志使用。
252
- `)
253
- return
70
+ const commands = loadDefaultCommands()
71
+ if (!commands) return null
254
72
 
255
- case 'test':
256
- console.log(`
257
- test 命令用法:
258
- dx test [type] [target] [path] [-t pattern|--name pattern]
73
+ const syntheticCli = buildSyntheticCliContext(commands)
74
+ const runtimeContext = buildHelpRuntimeContext(syntheticCli)
75
+ validateHelpConfig(commands, buildStrictHelpValidationContext(syntheticCli))
259
76
 
260
- 参数说明:
261
- type: e2e, unit (默认: e2e)
262
- target: 由 commands.json 的 test.<type>.<target> 决定
263
- path: guarded e2e target 必须提供文件或目录路径
264
- -t pattern / --name pattern: 指定测试用例名称模式,需要和 path 一起使用
77
+ return {
78
+ invocation: syntheticCli.invocation,
79
+ commands,
80
+ runtimeContext,
81
+ }
82
+ }
265
83
 
266
- 限制说明:
267
- guarded E2E target 禁止无路径全量执行。
268
- guarded E2E target 也不支持把 path 写成 all。
269
- dx test e2e all 不受支持,必须显式指定 target 和 path。
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
+ }
270
94
 
271
- 常见示例:
272
- dx test e2e backend apps/backend/e2e/auth
273
- dx test e2e backend apps/backend/e2e/auth/auth.login.e2e-spec.ts
274
- dx test e2e backend apps/backend/e2e/auth/auth.login.e2e-spec.ts -t "should login"
275
- dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts
276
- dx test unit front
277
- dx test unit admin
278
- `)
279
- return
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
+ }
280
104
 
281
- case 'contracts':
282
- console.log(`
283
- contracts 命令用法:
284
- dx contracts [generate|pull]
105
+ const model = getGlobalHelpModel(resolved.commands, resolved.runtimeContext)
285
106
 
286
- 说明:
287
- 1) 先运行: npx nx run backend:swagger
288
- 2) 再运行: openapi-zod-client 生成 packages/api-contracts/src/generated/backend.ts
107
+ return renderGlobalHelp({
108
+ title: `DX CLI v${version}`,
109
+ invocation: resolved.invocation,
110
+ ...model,
111
+ })
112
+ }
289
113
 
290
- 环境变量:
291
- OPENAPI_BASE_URL 默认: http://localhost:3000/api/v1
114
+ function renderDynamicCommandHelp(commandName, cliContext = null) {
115
+ const resolved = resolveCliContext(cliContext)
116
+ if (!resolved) return ''
292
117
 
293
- 示例:
294
- dx contracts
295
- OPENAPI_BASE_URL="https://api.example.com/api/v1" dx contracts
296
- `)
297
- return
118
+ const model = getCommandHelpModel(resolved.commands, commandName, resolved.runtimeContext)
119
+ if (!hasRenderableCommandModel(model)) return ''
298
120
 
299
- case 'release':
300
- console.log(`
301
- release 命令用法:
302
- dx release version <semver>
121
+ return renderCommandHelp({
122
+ invocation: resolved.invocation,
123
+ ...model,
124
+ })
125
+ }
303
126
 
304
- 说明:
305
- 同步更新以下 package.json 的 version 字段(存在则更新,不存在则跳过):
306
- - apps/backend/package.json
307
- - apps/front/package.json
308
- - apps/admin-front/package.json
127
+ export function showHelp(cliContext = null) {
128
+ console.log(renderDynamicGlobalHelp(cliContext))
129
+ }
309
130
 
310
- 示例:
311
- dx release version 1.2.3
312
- dx release version 1.2.3-beta.1
313
- `)
314
- return
131
+ export function showCommandHelp(command, cliContext = null) {
132
+ const commandName = String(command || '').toLowerCase()
133
+ const dynamicOutput = renderDynamicCommandHelp(commandName, cliContext)
315
134
 
316
- default:
317
- showHelp()
135
+ if (dynamicOutput) {
136
+ console.log(dynamicOutput)
137
+ return
318
138
  }
139
+
140
+ showHelp(cliContext)
319
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' || env === 'stage') return 'staging'
55
- if (env === 'production' || env === 'prod') return '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 || flags.production) return 'production'
81
+ if (flags.prod) return 'production'
83
82
  if (flags.staging) return 'staging'
84
- if (flags.dev || flags.development) return 'development'
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
- // 兼容单个 issue 编号的旧接口
571
- if (typeof issueNumbers === 'string' || typeof issueNumbers === 'number') {
572
- issueNumbers = [issueNumbers]
590
+ if (!Array.isArray(issueNumbers)) {
591
+ logger.error('请提供 issue 编号数组')
592
+ return false
573
593
  }
574
594
 
575
- if (!Array.isArray(issueNumbers) || issueNumbers.length === 0) {
576
- logger.error('请提供有效的 issue 编号')
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.filter(wt => {
858
- return wt.path.includes(this.prefix)
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(`#${issueNum}\t${wt.branch || 'detached'}\t${wt.path}\t${status}`)
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
- const pathMatch = path.basename(wt.path).match(/^ai_monorepo_issue_(\d+)$/)
995
- if (pathMatch && pathMatch[1]) {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.96",
3
+ "version": "0.1.97",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {