@weotro/dx 0.1.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +755 -0
  3. package/bin/dx-with-version-env.js +8 -0
  4. package/bin/dx.js +187 -0
  5. package/lib/artifact-deploy/artifact-builder.js +144 -0
  6. package/lib/artifact-deploy/config.js +180 -0
  7. package/lib/artifact-deploy/remote-script.js +301 -0
  8. package/lib/artifact-deploy/remote-transport.js +86 -0
  9. package/lib/artifact-deploy.js +70 -0
  10. package/lib/backend-artifact-deploy/artifact-builder.js +267 -0
  11. package/lib/backend-artifact-deploy/config.js +218 -0
  12. package/lib/backend-artifact-deploy/path-utils.js +18 -0
  13. package/lib/backend-artifact-deploy/remote-phases.js +14 -0
  14. package/lib/backend-artifact-deploy/remote-result.js +44 -0
  15. package/lib/backend-artifact-deploy/remote-script.js +507 -0
  16. package/lib/backend-artifact-deploy/remote-transport.js +123 -0
  17. package/lib/backend-artifact-deploy/rollback.js +5 -0
  18. package/lib/backend-artifact-deploy/runtime-package.js +46 -0
  19. package/lib/backend-artifact-deploy.js +91 -0
  20. package/lib/backend-package.js +674 -0
  21. package/lib/cli/args.js +38 -0
  22. package/lib/cli/command-result.js +1 -0
  23. package/lib/cli/commands/contracts.js +60 -0
  24. package/lib/cli/commands/core.js +533 -0
  25. package/lib/cli/commands/db.js +231 -0
  26. package/lib/cli/commands/deploy.js +175 -0
  27. package/lib/cli/commands/env.js +120 -0
  28. package/lib/cli/commands/export.js +39 -0
  29. package/lib/cli/commands/package.js +22 -0
  30. package/lib/cli/commands/release.js +55 -0
  31. package/lib/cli/commands/stack.js +427 -0
  32. package/lib/cli/commands/start.js +58 -0
  33. package/lib/cli/commands/worktree.js +145 -0
  34. package/lib/cli/dx-cli.js +1072 -0
  35. package/lib/cli/flags.js +123 -0
  36. package/lib/cli/help-model.js +222 -0
  37. package/lib/cli/help-renderer.js +137 -0
  38. package/lib/cli/help-schema.js +552 -0
  39. package/lib/cli/help.js +141 -0
  40. package/lib/cli/index.js +4 -0
  41. package/lib/cli/nx-command.js +13 -0
  42. package/lib/codex-initial.js +271 -0
  43. package/lib/confirm.js +213 -0
  44. package/lib/env-policy.js +134 -0
  45. package/lib/env-profile.js +435 -0
  46. package/lib/env.js +261 -0
  47. package/lib/exec.js +692 -0
  48. package/lib/logger.js +239 -0
  49. package/lib/nx-ignore.js +45 -0
  50. package/lib/run-with-version-env.js +163 -0
  51. package/lib/sdk-build.js +424 -0
  52. package/lib/start-dev.js +401 -0
  53. package/lib/telegram-webhook.js +431 -0
  54. package/lib/validate-env.js +317 -0
  55. package/lib/vercel-deploy.js +549 -0
  56. package/lib/version.js +14 -0
  57. package/lib/worktree.js +1052 -0
  58. package/package.json +45 -0
  59. package/skills/create-issue/SKILL.md +90 -0
  60. package/skills/delivering-design-handoff/SKILL.md +290 -0
  61. package/skills/doctor/SKILL.md +76 -0
  62. package/skills/gh-dependabot-cleanup/SKILL.md +54 -0
  63. package/skills/gh-dependabot-cleanup/agents/openai.yaml +7 -0
  64. package/skills/git-release/SKILL.md +194 -0
  65. package/skills/git-release/agents/openai.yaml +7 -0
  66. package/skills/online-debug-guard/SKILL.md +111 -0
  67. package/skills/ship-issue-pr/SKILL.md +676 -0
  68. package/skills/stagewise-ui-debugging/SKILL.md +48 -0
@@ -0,0 +1 @@
1
+ export const COMMAND_NOT_HANDLED = Symbol('COMMAND_NOT_HANDLED')
@@ -0,0 +1,60 @@
1
+ import { existsSync, mkdirSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { logger } from '../../logger.js'
4
+ import { execManager } from '../../exec.js'
5
+
6
+ export async function handleContracts(cli, args = []) {
7
+ const action = args[0] || 'generate'
8
+ if (!['generate', 'pull'].includes(action)) {
9
+ logger.error(`不支持的 contracts 子命令: ${action}`)
10
+ logger.info(`用法: ${cli.invocation} contracts [generate|pull]`)
11
+ process.exitCode = 1
12
+ return
13
+ }
14
+
15
+ cli.ensureRepoRoot()
16
+
17
+ // starmomo/ai-monorepo compatibility: packages/api-contracts is expected
18
+ const contractsRoot = join(process.cwd(), 'packages', 'api-contracts')
19
+ if (!existsSync(contractsRoot)) {
20
+ logger.error(`未找到 contracts 目录: ${contractsRoot}`)
21
+ logger.info('期望存在 packages/api-contracts,用于输出生成的 Zod 合约。')
22
+ process.exitCode = 1
23
+ return
24
+ }
25
+
26
+ logger.step('导出 OpenAPI 并生成 Zod 合约')
27
+
28
+ // 1) Export OpenAPI spec to dist/openapi/backend.json
29
+ await execManager.executeCommand('npx nx run backend:swagger', {
30
+ app: 'backend',
31
+ flags: cli.flags,
32
+ env: { NX_CACHE: 'false', SKIP_PRISMA_CONNECT: 'true' },
33
+ })
34
+
35
+ // 2) Generate zod client
36
+ const outputDir = join(contractsRoot, 'src', 'generated')
37
+ mkdirSync(outputDir, { recursive: true })
38
+
39
+ const baseUrl = process.env.OPENAPI_BASE_URL || 'http://localhost:3000/api/v1'
40
+ logger.info(`使用 API 基地址: ${baseUrl}`)
41
+
42
+ const generatorCommand = [
43
+ 'pnpm exec openapi-zod-client',
44
+ 'dist/openapi/backend.json',
45
+ '--output packages/api-contracts/src/generated/backend.ts',
46
+ '--api-client-name aiBackendClient',
47
+ `--base-url "${baseUrl}"`,
48
+ '--with-alias',
49
+ '--with-docs',
50
+ '--with-deprecated',
51
+ '--export-schemas',
52
+ '--prettier prettier.config.js',
53
+ ].join(' ')
54
+
55
+ await execManager.executeCommand(generatorCommand, {
56
+ flags: cli.flags,
57
+ })
58
+
59
+ logger.success('Zod 合约已更新(packages/api-contracts)')
60
+ }
@@ -0,0 +1,533 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, join, relative } from 'node:path'
3
+ import { logger } from '../../logger.js'
4
+ import { confirmManager } from '../../confirm.js'
5
+ import { execManager } from '../../exec.js'
6
+ import { showHelp, showCommandHelp } from '../help.js'
7
+
8
+ const DEFAULT_TEST_WORKERS = 8
9
+
10
+ export function handleHelp(cli, args = []) {
11
+ if (args[0]) showCommandHelp(args[0], cli)
12
+ else showHelp(cli)
13
+ }
14
+
15
+ export async function handleBuild(cli, args) {
16
+ const target = args[0] || 'all'
17
+ const environment = cli.determineEnvironment()
18
+ const envKey = cli.normalizeEnvKey(environment)
19
+ const explicitEnv =
20
+ Boolean(cli.flags.dev || cli.flags.prod || cli.flags.staging || cli.flags.test || cli.flags.e2e)
21
+
22
+ const buildConfig = cli.commands.build[target]
23
+ if (!buildConfig) {
24
+ logger.error(`未找到构建目标: ${target}`)
25
+ process.exitCode = 1
26
+ return
27
+ }
28
+
29
+ logger.step(`构建 ${target} (${environment})`)
30
+
31
+ // 处理嵌套配置
32
+ let config = buildConfig
33
+ if (typeof config === 'object' && !config.command) {
34
+ const supportsCurrentEnv = Boolean(config[envKey])
35
+ if (explicitEnv && !supportsCurrentEnv) {
36
+ const envFlag = cli.getEnvironmentFlagExample(envKey) || `--${envKey}`
37
+ logger.error(`构建目标 ${target} 不支持 ${envFlag} 环境`)
38
+ logger.info('显式传入环境标志时,必须是该 target 实际支持的环境。')
39
+ const available = ['development', 'staging', 'production', 'test', 'e2e']
40
+ .filter(key => key in config)
41
+ .map(key => cli.getEnvironmentFlagExample(key) || `--${key}`)
42
+ if (available.length > 0) {
43
+ logger.info(`支持的环境: ${available.join(', ')}`)
44
+ logger.info(`示例: ${cli.invocation} build ${target} ${available[0]}`)
45
+ if (available.length > 1) {
46
+ logger.info(`示例: ${cli.invocation} build ${target} ${available[1]}`)
47
+ }
48
+ }
49
+ process.exitCode = 1
50
+ return
51
+ }
52
+
53
+ // 严格按显式环境选择配置,不做环境回退
54
+ if (config[envKey]) config = config[envKey]
55
+ else config = null
56
+ }
57
+
58
+ if (!config) {
59
+ logger.error(`构建目标 ${target} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置`)
60
+ process.exitCode = 1
61
+ return
62
+ }
63
+
64
+ if (config.concurrent) {
65
+ await cli.handleConcurrentCommands(config.commands, 'build', envKey)
66
+ } else if (config.sequential) {
67
+ await cli.handleSequentialCommands(config.commands, envKey)
68
+ } else {
69
+ await cli.executeCommand(config)
70
+ }
71
+ }
72
+
73
+ export async function handleTest(cli, args) {
74
+ const type = args[0] || 'e2e'
75
+ const target = args[1] || 'all'
76
+ const testPaths = args.slice(2) // unit 可选多个测试文件路径;e2e 只使用第一个
77
+ const testPath = testPaths[0]
78
+
79
+ // 解析 -t 参数用于指定特定测试用例(使用原始参数列表)
80
+ const allArgs = cli.args // 使用原始参数列表包含所有标志
81
+ const testNamePattern = resolveTestNamePattern(allArgs)
82
+ const passthroughArgs = resolvePassthroughArgs(allArgs)
83
+
84
+ // 根据测试类型自动设置环境标志
85
+ if (type === 'e2e' && !cli.flags.e2e) {
86
+ cli.flags.e2e = true
87
+ } else if (type === 'unit' && !cli.flags.test) {
88
+ cli.flags.test = true
89
+ }
90
+
91
+ const typeConfig = cli.commands.test[type]
92
+ let testConfig = typeConfig?.[target]
93
+ if (!testConfig && typeConfig?.command) {
94
+ testConfig = typeConfig
95
+ }
96
+
97
+ if (!testConfig) {
98
+ logger.error(`未找到测试配置: ${type}.${target}`)
99
+ process.exit(1)
100
+ return
101
+ }
102
+
103
+ if (type === 'e2e' && testConfig.requiresPath && testPath) {
104
+ if (!testConfig.fileCommand) {
105
+ logger.error(`测试配置错误: test.${type}.${target} 已启用 requiresPath,必须配置 fileCommand`)
106
+ process.exit(1)
107
+ }
108
+
109
+ const fileCommand = String(testConfig.fileCommand)
110
+ if (!fileCommand.includes('{TEST_PATH}')) {
111
+ logger.error(`测试配置错误: test.${type}.${target} 的 fileCommand 必须包含 {TEST_PATH}`)
112
+ process.exit(1)
113
+ }
114
+
115
+ let directTargetCommand = fileCommand
116
+ let directTarget = resolveNxTargetDirectCommand(cli, directTargetCommand, ['test:e2e'])
117
+ if (!directTarget && hasTrailingWorkerComment(fileCommand)) {
118
+ directTargetCommand = String(testConfig.command || '')
119
+ directTarget = resolveNxTargetDirectCommand(cli, directTargetCommand, ['test:e2e'])
120
+ }
121
+ const normalizedTestPath = normalizeE2eTestPathForCommand(
122
+ cli,
123
+ directTarget ? directTargetCommand : fileCommand,
124
+ testPath,
125
+ )
126
+ let command = directTarget
127
+ ? `${directTarget.command} ${shellEscape(normalizedTestPath)}`
128
+ : fileCommand.replace('{TEST_PATH}', shellEscape(normalizedTestPath))
129
+
130
+ if (testNamePattern) {
131
+ command += ` -t ${shellEscape(testNamePattern)}`
132
+ }
133
+
134
+ if (passthroughArgs.length > 0) {
135
+ command += ` ${passthroughArgs.map(shellEscape).join(' ')}`
136
+ }
137
+
138
+ testConfig = {
139
+ ...testConfig,
140
+ command: command,
141
+ ...(directTarget?.cwd ? { cwd: directTarget.cwd } : {}),
142
+ description: testNamePattern
143
+ ? `运行单个E2E测试文件的特定用例: ${testPath} -> ${testNamePattern}`
144
+ : `运行单个E2E测试文件: ${testPath}`
145
+ }
146
+
147
+ if (testNamePattern) {
148
+ logger.step(`运行 ${type} 测试用例: ${testNamePattern} (文件: ${testPath})`)
149
+ } else {
150
+ logger.step(`运行单个 ${type} 测试: ${testPath}`)
151
+ }
152
+ } else if (type === 'unit' && testPaths.length > 0) {
153
+ let command = String(testConfig.command).trim()
154
+ const useDirectPathArg = shouldUseDirectPathArg(command)
155
+ const normalizedTestPaths = testPaths.map(path =>
156
+ useDirectPathArg
157
+ ? normalizeUnitTestPathForCommand(cli, command, path)
158
+ : path
159
+ )
160
+ const forwardedArgs = useDirectPathArg
161
+ ? normalizedTestPaths.map(shellEscape)
162
+ : [`--runTestsByPath ${normalizedTestPaths.map(shellEscape).join(' ')}`]
163
+
164
+ if (testNamePattern) {
165
+ forwardedArgs.push(`-t ${shellEscape(testNamePattern)}`)
166
+ }
167
+
168
+ if (passthroughArgs.length > 0) {
169
+ forwardedArgs.push(...passthroughArgs.map(shellEscape))
170
+ }
171
+
172
+ command += ` ${forwardedArgs.join(' ')}`
173
+
174
+ testConfig = {
175
+ ...testConfig,
176
+ command,
177
+ description: testNamePattern
178
+ ? `运行单元测试文件的特定用例: ${testPaths.join(', ')} -> ${testNamePattern}`
179
+ : `运行单元测试文件: ${testPaths.join(', ')}`,
180
+ }
181
+
182
+ if (testNamePattern) {
183
+ logger.step(`运行 ${type} 测试用例: ${testNamePattern} (文件: ${testPaths.join(', ')})`)
184
+ } else {
185
+ logger.step(`运行 ${type} 测试: ${testPaths.join(', ')}`)
186
+ }
187
+ } else {
188
+ logger.step(`运行 ${type} 测试`)
189
+ }
190
+
191
+ testConfig = {
192
+ ...testConfig,
193
+ command: appendDefaultTestWorkers(cli, testConfig.command, type),
194
+ }
195
+
196
+ await cli.executeCommand(testConfig)
197
+ }
198
+
199
+ function shellEscape(value) {
200
+ return `'${String(value).replace(/'/g, `'\\''`)}'`
201
+ }
202
+
203
+ function resolveTestNamePattern(args = []) {
204
+ const aliases = ['-t', '--name', '--test-name-pattern']
205
+ for (let i = 0; i < args.length; i++) {
206
+ if (!aliases.includes(args[i])) continue
207
+ if (i + 1 < args.length) return args[i + 1]
208
+ }
209
+ return null
210
+ }
211
+
212
+ function resolvePassthroughArgs(args = []) {
213
+ const index = args.indexOf('--')
214
+ if (index === -1) return []
215
+ return args.slice(index + 1)
216
+ }
217
+
218
+ function shouldUseDirectPathArg(command) {
219
+ const text = String(command || '')
220
+ return (
221
+ /\bnx\s+test\b/.test(text) ||
222
+ /\bnx\.js\s+test\b/.test(text) ||
223
+ /\bvitest\s+run\b/.test(text)
224
+ )
225
+ }
226
+
227
+ function appendDefaultTestWorkers(cli, command, type) {
228
+ const text = String(command || '').trim()
229
+ const flag = getDefaultWorkerFlag(type, text)
230
+ if (!flag) return command
231
+ if (!text || hasWorkerFlag(text, flag)) return command
232
+ if (type === 'unit' && commandUsesRunInBand(cli, text)) return command
233
+
234
+ if (isNodeEvalCommandWithoutArgSeparator(text)) {
235
+ return `${text} -- ${flag}=${DEFAULT_TEST_WORKERS}`
236
+ }
237
+
238
+ return `${text} ${flag}=${DEFAULT_TEST_WORKERS}`
239
+ }
240
+
241
+ function getDefaultWorkerFlag(type, command = '') {
242
+ if (type === 'e2e') {
243
+ return /\bvitest\s+run\b/.test(String(command || '')) ? '--maxWorkers' : '--workers'
244
+ }
245
+ if (type === 'unit') return '--maxWorkers'
246
+ return null
247
+ }
248
+
249
+ function hasWorkerFlag(command, flag) {
250
+ const escapedFlag = flag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
251
+ return new RegExp(`(^|[\\s'"])${escapedFlag}(=|\\s|['"]|$)`).test(String(command || ''))
252
+ }
253
+
254
+ function hasTrailingWorkerComment(command) {
255
+ return /\s+#\s*--(?:maxWorkers|workers)(?:=\S+)?\s*$/.test(String(command || ''))
256
+ }
257
+
258
+ function hasRunInBandFlag(command) {
259
+ return /(^|[\s'"])(--runInBand|--run-in-band|-i)(=|\s|['"]|$)/.test(String(command || ''))
260
+ }
261
+
262
+ function commandUsesRunInBand(cli, command) {
263
+ if (hasRunInBandFlag(command)) return true
264
+
265
+ const script = resolveNxUnitPackageTestScript(cli, command)
266
+ return hasRunInBandFlag(script)
267
+ }
268
+
269
+ function resolveNxUnitPackageTestScript(cli, command) {
270
+ const projectRoot = cli?.projectRoot || process.cwd()
271
+ const nxResolution = extractNxTarget(command, ['test'])
272
+ if (!nxResolution?.project) return null
273
+
274
+ const packagePath = join(projectRoot, 'apps', nxResolution.project, 'package.json')
275
+ if (!existsSync(packagePath)) return null
276
+
277
+ try {
278
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'))
279
+ return typeof pkg?.scripts?.test === 'string' ? pkg.scripts.test : null
280
+ } catch {
281
+ return null
282
+ }
283
+ }
284
+
285
+ function isNodeEvalCommandWithoutArgSeparator(command) {
286
+ return /(^|\s)node\s+-e\s/.test(String(command || '')) && !/\s--(\s|$)/.test(String(command || ''))
287
+ }
288
+
289
+ function normalizeUnitTestPathForCommand(cli, command, testPath) {
290
+ const rawPath = String(testPath || '')
291
+ if (!rawPath) return rawPath
292
+
293
+ if (/\bvitest\s+run\b/.test(String(command || ''))) {
294
+ return rawPath
295
+ }
296
+
297
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test'])
298
+ if (!projectCwd) return rawPath
299
+
300
+ const projectRoot = cli?.projectRoot || process.cwd()
301
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
302
+ const absoluteTestPath = join(projectRoot, rawPath)
303
+ const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
304
+
305
+ if (!relativePath || relativePath.startsWith('..')) {
306
+ return rawPath
307
+ }
308
+
309
+ return relativePath
310
+ }
311
+
312
+ function normalizeE2eTestPathForCommand(cli, command, testPath) {
313
+ const rawPath = String(testPath || '')
314
+ if (!rawPath) return rawPath
315
+
316
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test:e2e'])
317
+ if (!projectCwd) return rawPath
318
+
319
+ const projectRoot = cli?.projectRoot || process.cwd()
320
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
321
+ const absoluteTestPath = join(projectRoot, rawPath)
322
+ const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
323
+
324
+ if (!relativePath || relativePath.startsWith('..')) {
325
+ return rawPath
326
+ }
327
+
328
+ return relativePath
329
+ }
330
+
331
+ function resolveNxTargetDirectCommand(cli, command, targetNames = []) {
332
+ const projectRoot = cli?.projectRoot || process.cwd()
333
+ const nxResolution = extractNxTarget(command, targetNames)
334
+ if (!nxResolution) return null
335
+
336
+ const projectDir = join(projectRoot, 'apps', nxResolution.project)
337
+ const projectConfigPath = join(projectDir, 'project.json')
338
+ if (!existsSync(projectConfigPath)) return null
339
+
340
+ try {
341
+ const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8'))
342
+ const resolvedTarget = projectConfig?.targets?.[nxResolution.target]
343
+ const directCommand = resolvedTarget?.options?.command
344
+ if (typeof directCommand !== 'string' || directCommand.trim().length === 0) {
345
+ return null
346
+ }
347
+ const cwd = resolvedTarget?.options?.cwd
348
+ return {
349
+ command: directCommand.trim(),
350
+ cwd: typeof cwd === 'string' && cwd.trim().length > 0 ? cwd.trim() : null,
351
+ }
352
+ } catch {
353
+ return null
354
+ }
355
+ }
356
+
357
+ function resolveNxTargetProjectCwd(cli, command, targetNames = []) {
358
+ const projectRoot = cli?.projectRoot || process.cwd()
359
+ const nxResolution = extractNxTarget(command, targetNames)
360
+ if (!nxResolution) return null
361
+
362
+ const projectDir = join(projectRoot, 'apps', nxResolution.project)
363
+ const projectConfigPath = join(projectDir, 'project.json')
364
+ if (!existsSync(projectConfigPath)) return null
365
+
366
+ try {
367
+ const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8'))
368
+ const resolvedTarget = projectConfig?.targets?.[nxResolution.target]
369
+ const cwd = resolvedTarget?.options?.cwd
370
+ if (typeof cwd === 'string' && cwd.trim().length > 0) {
371
+ return cwd
372
+ }
373
+ if (nxResolution.target === 'test:e2e') {
374
+ const e2eDir = join(projectDir, 'e2e')
375
+ if (existsSync(e2eDir)) {
376
+ return relative(projectRoot, e2eDir)
377
+ }
378
+ }
379
+ return relative(projectRoot, dirname(projectConfigPath))
380
+ } catch {
381
+ return null
382
+ }
383
+ }
384
+
385
+ function extractNxTarget(command, targetNames = []) {
386
+ const text = String(command || '').trim()
387
+ const names = Array.isArray(targetNames) && targetNames.length > 0 ? targetNames : ['test']
388
+
389
+ for (const targetName of names) {
390
+ if (targetName === 'test') {
391
+ const directMatch = text.match(/\bnx(?:\.js)?\s+test\s+([^\s]+)/)
392
+ if (directMatch?.[1]) {
393
+ return { project: directMatch[1], target: 'test' }
394
+ }
395
+ }
396
+
397
+ const escapedTarget = targetName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
398
+ const runMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+run\\s+([^:\\s]+):${escapedTarget}\\b`))
399
+ if (runMatch?.[1]) {
400
+ return { project: runMatch[1], target: targetName }
401
+ }
402
+
403
+ const directColonMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+${escapedTarget}\\s+([^\\s]+)`))
404
+ if (directColonMatch?.[1]) {
405
+ return { project: directColonMatch[1], target: targetName }
406
+ }
407
+ }
408
+
409
+ return null
410
+ }
411
+
412
+ export async function handleLint(cli, args) {
413
+ void args
414
+ const baseConfig = cli.commands.lint
415
+ if (!baseConfig || !baseConfig.command) {
416
+ logger.error('未找到 lint 命令配置')
417
+ process.exitCode = 1
418
+ return
419
+ }
420
+
421
+ const config = { ...baseConfig }
422
+
423
+ if (cli.flags.fix) {
424
+ logger.step('运行代码检查(自动修复模式: --fix)')
425
+ const cmd = String(config.command)
426
+ // 若已包含 ` -- ` 分隔符,直接在末尾追加 --fix;否则通过 `--` 传递给 Nx 下游
427
+ config.command = cmd.includes(' -- ')
428
+ ? `${cmd} --fix`
429
+ : `${cmd} -- --fix`
430
+ } else {
431
+ logger.step('运行代码检查')
432
+ }
433
+
434
+ await cli.executeCommand(config)
435
+ }
436
+
437
+ export async function handleClean(cli, args) {
438
+ const target = args[0] || 'all'
439
+ const cleanConfig = cli.commands.clean[target]
440
+
441
+ if (!cleanConfig) {
442
+ logger.error(`未找到清理目标: ${target}`)
443
+ process.exitCode = 1
444
+ return
445
+ }
446
+
447
+ // 危险操作确认
448
+ if (cleanConfig.dangerous) {
449
+ const confirmed = await confirmManager.confirmDangerous(
450
+ `清理操作: ${target}`,
451
+ '当前环境',
452
+ cli.flags.Y
453
+ )
454
+
455
+ if (!confirmed) {
456
+ logger.info('操作已取消')
457
+ return
458
+ }
459
+ }
460
+
461
+ logger.step(`清理 ${target}`)
462
+ await cli.executeCommand(cleanConfig)
463
+ }
464
+
465
+ export async function handleCache(cli, args) {
466
+ const action = args[0] || 'clear'
467
+ const cacheConfig = cli.commands.cache?.[action]
468
+
469
+ if (!cacheConfig) {
470
+ logger.error(`未找到缓存操作: ${action}`)
471
+ logger.info('用法: dx cache clear')
472
+ process.exitCode = 1
473
+ return
474
+ }
475
+
476
+ // 危险操作确认
477
+ if (cacheConfig.dangerous) {
478
+ const confirmed = await confirmManager.confirmDangerous(
479
+ `缓存清理: ${action}`,
480
+ '当前环境',
481
+ cli.flags.Y
482
+ )
483
+
484
+ if (!confirmed) {
485
+ logger.info('操作已取消')
486
+ return
487
+ }
488
+
489
+ // 二次确认(更醒目):强调将清理全局 pnpm store 与 ~/.pnpm-store
490
+ if (!cli.flags.Y && action === 'clear') {
491
+ const second = await confirmManager.confirm(
492
+ '二次确认:将清理全局 pnpm store 与 ~/.pnpm-store,可能影响其他项目,是否继续?',
493
+ false,
494
+ false
495
+ )
496
+ if (!second) {
497
+ logger.info('操作已取消')
498
+ return
499
+ }
500
+ }
501
+ }
502
+
503
+ logger.step(`执行缓存操作: ${action}`)
504
+ await cli.executeCommand(cacheConfig)
505
+ }
506
+
507
+ export async function handleInstall(cli, args) {
508
+ void args
509
+ const installConfig = cli.commands.install
510
+ if (!installConfig) {
511
+ logger.error('未找到 install 命令配置')
512
+ process.exitCode = 1
513
+ return
514
+ }
515
+
516
+ logger.step('安装依赖')
517
+ await cli.executeCommand(installConfig)
518
+ }
519
+
520
+ export async function handleStatus(cli, args) {
521
+ void args
522
+ logger.step('系统状态')
523
+
524
+ const status = execManager.getStatus()
525
+ console.log(`运行中的进程: ${status.runningProcesses}`)
526
+
527
+ if (status.processes.length > 0) {
528
+ logger.table(
529
+ status.processes.map(p => [p.id, p.command, `${Math.round(p.duration/1000)}s`]),
530
+ ['进程ID', '命令', '运行时长']
531
+ )
532
+ }
533
+ }