@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.
@@ -1,18 +1,13 @@
1
1
  import { existsSync, readFileSync } from 'node:fs'
2
- import { join, relative } from 'node:path'
2
+ import { dirname, join, relative } from 'node:path'
3
3
  import { logger } from '../../logger.js'
4
4
  import { confirmManager } from '../../confirm.js'
5
5
  import { execManager } from '../../exec.js'
6
6
  import { showHelp, showCommandHelp } from '../help.js'
7
7
 
8
8
  export function handleHelp(cli, args = []) {
9
- void cli
10
- if (args[0]) showCommandHelp(args[0])
11
- else showHelp()
12
- }
13
-
14
- export function handleDev(cli, args = []) {
15
- return cli.reportDevCommandRemoved(args)
9
+ if (args[0]) showCommandHelp(args[0], cli)
10
+ else showHelp(cli)
16
11
  }
17
12
 
18
13
  export async function handleBuild(cli, args) {
@@ -24,12 +19,6 @@ export async function handleBuild(cli, args) {
24
19
 
25
20
  const buildConfig = cli.commands.build[target]
26
21
  if (!buildConfig) {
27
- // 兼容用户误输入或快捷命令,把 "all./scripts/dx" 这类粘连错误拆分
28
- const fixed = String(target).split(/\s|\t|\r|\n|\u00A0|\.|\//)[0]
29
- if (fixed && cli.commands.build[fixed]) {
30
- logger.warn(`自动修正构建目标: ${target} -> ${fixed}`)
31
- return await handleBuild(cli, [fixed])
32
- }
33
22
  logger.error(`未找到构建目标: ${target}`)
34
23
  process.exitCode = 1
35
24
  return
@@ -40,14 +29,12 @@ export async function handleBuild(cli, args) {
40
29
  // 处理嵌套配置
41
30
  let config = buildConfig
42
31
  if (typeof config === 'object' && !config.command) {
43
- const supportsCurrentEnv = Boolean(
44
- config[envKey] || (envKey === 'staging' && config.prod),
45
- )
32
+ const supportsCurrentEnv = Boolean(config[envKey])
46
33
  if (explicitEnv && !supportsCurrentEnv) {
47
34
  const envFlag = cli.getEnvironmentFlagExample(envKey) || `--${envKey}`
48
35
  logger.error(`构建目标 ${target} 不支持 ${envFlag} 环境`)
49
36
  logger.info('显式传入环境标志时,必须是该 target 实际支持的环境。')
50
- const available = ['dev', 'staging', 'prod', 'test', 'e2e']
37
+ const available = ['development', 'staging', 'production', 'test', 'e2e']
51
38
  .filter(key => key in config)
52
39
  .map(key => cli.getEnvironmentFlagExample(key) || `--${key}`)
53
40
  if (available.length > 0) {
@@ -61,10 +48,15 @@ export async function handleBuild(cli, args) {
61
48
  return
62
49
  }
63
50
 
64
- // 如果是嵌套配置,尝试获取环境特定的配置(兼容 dev/prod 与 development/production 命名)
51
+ // 严格按显式环境选择配置,不做环境回退
65
52
  if (config[envKey]) config = config[envKey]
66
- else if (envKey === 'staging' && config.prod) config = config.prod
67
- else config = config.dev || config
53
+ else config = null
54
+ }
55
+
56
+ if (!config) {
57
+ logger.error(`构建目标 ${target} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置`)
58
+ process.exitCode = 1
59
+ return
68
60
  }
69
61
 
70
62
  if (config.concurrent) {
@@ -83,11 +75,7 @@ export async function handleTest(cli, args) {
83
75
 
84
76
  // 解析 -t 参数用于指定特定测试用例(使用原始参数列表)
85
77
  const allArgs = cli.args // 使用原始参数列表包含所有标志
86
- const testNamePatternIndex = allArgs.indexOf('-t')
87
- let testNamePattern = null
88
- if (testNamePatternIndex !== -1 && testNamePatternIndex + 1 < allArgs.length) {
89
- testNamePattern = allArgs[testNamePatternIndex + 1]
90
- }
78
+ const testNamePattern = resolveTestNamePattern(allArgs)
91
79
 
92
80
  // 根据测试类型自动设置环境标志
93
81
  if (type === 'e2e' && !cli.flags.e2e) {
@@ -120,7 +108,8 @@ export async function handleTest(cli, args) {
120
108
  process.exit(1)
121
109
  }
122
110
 
123
- let command = fileCommand.replace('{TEST_PATH}', shellEscape(testPath))
111
+ const normalizedTestPath = normalizeE2eTestPathForCommand(cli, fileCommand, testPath)
112
+ let command = fileCommand.replace('{TEST_PATH}', shellEscape(normalizedTestPath))
124
113
 
125
114
  if (testNamePattern) {
126
115
  command += ` -t ${shellEscape(testNamePattern)}`
@@ -179,6 +168,15 @@ function shellEscape(value) {
179
168
  return `'${String(value).replace(/'/g, `'\\''`)}'`
180
169
  }
181
170
 
171
+ function resolveTestNamePattern(args = []) {
172
+ const aliases = ['-t', '--name', '--test-name-pattern']
173
+ for (let i = 0; i < args.length; i++) {
174
+ if (!aliases.includes(args[i])) continue
175
+ if (i + 1 < args.length) return args[i + 1]
176
+ }
177
+ return null
178
+ }
179
+
182
180
  function shouldUseDirectPathArg(command) {
183
181
  const text = String(command || '')
184
182
  return (
@@ -196,11 +194,11 @@ function normalizeUnitTestPathForCommand(cli, command, testPath) {
196
194
  return rawPath
197
195
  }
198
196
 
199
- const vitestProjectCwd = resolveNxVitestProjectCwd(cli, command)
200
- if (!vitestProjectCwd) return rawPath
197
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test'])
198
+ if (!projectCwd) return rawPath
201
199
 
202
200
  const projectRoot = cli?.projectRoot || process.cwd()
203
- const absoluteProjectCwd = join(projectRoot, vitestProjectCwd)
201
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
204
202
  const absoluteTestPath = join(projectRoot, rawPath)
205
203
  const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
206
204
 
@@ -211,33 +209,78 @@ function normalizeUnitTestPathForCommand(cli, command, testPath) {
211
209
  return relativePath
212
210
  }
213
211
 
214
- function resolveNxVitestProjectCwd(cli, command) {
212
+ function normalizeE2eTestPathForCommand(cli, command, testPath) {
213
+ const rawPath = String(testPath || '')
214
+ if (!rawPath) return rawPath
215
+
216
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test:e2e'])
217
+ if (!projectCwd) return rawPath
218
+
215
219
  const projectRoot = cli?.projectRoot || process.cwd()
216
- const nxTarget = extractNxTestTarget(command)
217
- if (!nxTarget) return null
220
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
221
+ const absoluteTestPath = join(projectRoot, rawPath)
222
+ const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
218
223
 
219
- const projectConfigPath = join(projectRoot, 'apps', nxTarget, 'project.json')
224
+ if (!relativePath || relativePath.startsWith('..')) {
225
+ return rawPath
226
+ }
227
+
228
+ return relativePath
229
+ }
230
+
231
+ function resolveNxTargetProjectCwd(cli, command, targetNames = []) {
232
+ const projectRoot = cli?.projectRoot || process.cwd()
233
+ const nxResolution = extractNxTarget(command, targetNames)
234
+ if (!nxResolution) return null
235
+
236
+ const projectDir = join(projectRoot, 'apps', nxResolution.project)
237
+ const projectConfigPath = join(projectDir, 'project.json')
220
238
  if (!existsSync(projectConfigPath)) return null
221
239
 
222
240
  try {
223
241
  const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8'))
224
- const testTarget = projectConfig?.targets?.test
225
- const command = String(testTarget?.options?.command || '')
226
- const cwd = testTarget?.options?.cwd
227
- if (!/\bvitest\s+run\b/.test(command)) return null
228
- if (typeof cwd !== 'string' || cwd.trim().length === 0) return null
229
- return cwd
242
+ const resolvedTarget = projectConfig?.targets?.[nxResolution.target]
243
+ const cwd = resolvedTarget?.options?.cwd
244
+ if (typeof cwd === 'string' && cwd.trim().length > 0) {
245
+ return cwd
246
+ }
247
+ if (nxResolution.target === 'test:e2e') {
248
+ const e2eDir = join(projectDir, 'e2e')
249
+ if (existsSync(e2eDir)) {
250
+ return relative(projectRoot, e2eDir)
251
+ }
252
+ }
253
+ return relative(projectRoot, dirname(projectConfigPath))
230
254
  } catch {
231
255
  return null
232
256
  }
233
257
  }
234
258
 
235
- function extractNxTestTarget(command) {
259
+ function extractNxTarget(command, targetNames = []) {
236
260
  const text = String(command || '').trim()
237
- const match =
238
- text.match(/\bnx(?:\.js)?\s+test\s+([^\s]+)/) ||
239
- text.match(/\bnx(?:\.js)?\s+run\s+([^:\s]+):test\b/)
240
- return match?.[1] || null
261
+ const names = Array.isArray(targetNames) && targetNames.length > 0 ? targetNames : ['test']
262
+
263
+ for (const targetName of names) {
264
+ if (targetName === 'test') {
265
+ const directMatch = text.match(/\bnx(?:\.js)?\s+test\s+([^\s]+)/)
266
+ if (directMatch?.[1]) {
267
+ return { project: directMatch[1], target: 'test' }
268
+ }
269
+ }
270
+
271
+ const escapedTarget = targetName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
272
+ const runMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+run\\s+([^:\\s]+):${escapedTarget}\\b`))
273
+ if (runMatch?.[1]) {
274
+ return { project: runMatch[1], target: targetName }
275
+ }
276
+
277
+ const directColonMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+${escapedTarget}\\s+([^\\s]+)`))
278
+ if (directColonMatch?.[1]) {
279
+ return { project: directColonMatch[1], target: targetName }
280
+ }
281
+ }
282
+
283
+ return null
241
284
  }
242
285
 
243
286
  export async function handleLint(cli, args) {
@@ -22,7 +22,7 @@ export async function handleDatabase(cli, args) {
22
22
  const envKey = cli.normalizeEnvKey(environment)
23
23
 
24
24
  // 创建迁移只允许在开发环境进行,避免 AI/用户在非开发环境误执行
25
- if (action === 'migrate' && envKey && envKey !== 'dev') {
25
+ if (action === 'migrate' && envKey && envKey !== 'development') {
26
26
  const envFlag = cli.getEnvironmentFlagExample(envKey) || `--${envKey}`
27
27
  logger.error('dx db migrate 仅允许在 --dev 环境下创建迁移')
28
28
  logger.info('请使用 `dx db migrate --dev --name <migration-name>` 创建新迁移。')
@@ -49,10 +49,15 @@ export async function handleDatabase(cli, args) {
49
49
  // 处理嵌套配置
50
50
  let config = dbConfig
51
51
  if (typeof config === 'object' && !config.command) {
52
- // 如果是嵌套配置,尝试获取环境特定的配置(兼容 dev/prod 与 development/production 命名)
52
+ // 严格按显式环境选择配置,不做环境回退
53
53
  if (config[envKey]) config = config[envKey]
54
- else if (envKey === 'staging' && config.prod) config = config.prod
55
- else config = config.dev || config
54
+ else config = null
55
+ }
56
+
57
+ if (!config) {
58
+ logger.error(`数据库操作 ${action} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置`)
59
+ process.exitCode = 1
60
+ return
56
61
  }
57
62
 
58
63
  // 危险操作确认
@@ -71,7 +76,7 @@ export async function handleDatabase(cli, args) {
71
76
 
72
77
  // 支持为 migrate 传入迁移名:--name/-n
73
78
  let command = config.command
74
- if (action === 'migrate' && envKey === 'dev') {
79
+ if (action === 'migrate' && envKey === 'development') {
75
80
  const allArgs = cli.args
76
81
  let migrationName = null
77
82
  // 优先解析显式标志 --name/-n
@@ -123,24 +128,14 @@ export async function handleDatabase(cli, args) {
123
128
  .join('')
124
129
  }
125
130
 
126
- if (action === 'reset' && envKey !== 'prod') {
131
+ if (action === 'reset' && envKey !== 'production') {
127
132
  extraEnv.PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION = 'yes'
128
133
  logger.info('非生产环境重置数据库,已自动确认危险操作: PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION=yes')
129
134
  }
130
135
 
131
- const execFlags = { ...cli.flags }
132
- ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
133
- key => delete execFlags[key],
134
- )
135
- if (envKey === 'prod') execFlags.prod = true
136
- else if (envKey === 'dev') execFlags.dev = true
137
- else if (envKey === 'test') execFlags.test = true
138
- else if (envKey === 'e2e') execFlags.e2e = true
139
- else if (envKey === 'staging') execFlags.staging = true
140
-
141
136
  await cli.executeCommand(
142
137
  { ...config, command, env: { ...(config.env || {}), ...extraEnv } },
143
- execFlags,
138
+ cli.createExecutionFlags(environment),
144
139
  )
145
140
  }
146
141
 
@@ -177,7 +172,7 @@ export async function handleDatabaseScript(cli, scriptName, envKey, dbConfig) {
177
172
  return
178
173
  }
179
174
 
180
- const environment = envKey === 'dev' ? 'development' : envKey === 'prod' ? 'production' : envKey
175
+ const environment = envKey
181
176
  logger.step(`执行数据库脚本: ${cleanScriptName} (${environment})`)
182
177
  logger.info(`脚本路径: ${scriptPath}`)
183
178
 
@@ -185,12 +180,17 @@ export async function handleDatabaseScript(cli, scriptName, envKey, dbConfig) {
185
180
  let config = dbConfig
186
181
  if (typeof config === 'object' && !config.command) {
187
182
  if (config[envKey]) config = config[envKey]
188
- else if (envKey === 'staging' && config.prod) config = config.prod
189
- else config = config.dev || config
183
+ else config = null
184
+ }
185
+
186
+ if (!config) {
187
+ logger.error(`数据库脚本 ${scriptName} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置`)
188
+ process.exitCode = 1
189
+ return
190
190
  }
191
191
 
192
192
  // 危险操作确认
193
- if (config.dangerous && envKey === 'prod') {
193
+ if (config.dangerous && envKey === 'production') {
194
194
  const confirmed = await confirmManager.confirmDatabaseOperation(
195
195
  `script: ${cleanScriptName}`,
196
196
  envManager.getEnvironmentDescription(environment),
@@ -224,18 +224,8 @@ export async function handleDatabaseScript(cli, scriptName, envKey, dbConfig) {
224
224
  const extraEnv = { NX_CACHE: 'false' }
225
225
  logger.info('为数据库脚本禁用 Nx 缓存: NX_CACHE=false')
226
226
 
227
- const execFlags = { ...cli.flags }
228
- ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
229
- key => delete execFlags[key],
230
- )
231
- if (envKey === 'prod') execFlags.prod = true
232
- else if (envKey === 'dev') execFlags.dev = true
233
- else if (envKey === 'test') execFlags.test = true
234
- else if (envKey === 'e2e') execFlags.e2e = true
235
- else if (envKey === 'staging') execFlags.staging = true
236
-
237
227
  await cli.executeCommand(
238
228
  { ...config, command, env: { ...(config.env || {}), ...extraEnv } },
239
- execFlags,
229
+ cli.createExecutionFlags(environment),
240
230
  )
241
231
  }
@@ -25,8 +25,13 @@ export async function handleExport(cli, args) {
25
25
  let config = exportConfig
26
26
  if (typeof config === 'object' && !config.command) {
27
27
  if (config[envKey]) config = config[envKey]
28
- else if (envKey === 'staging' && config.prod) config = config.prod
29
- else config = config.dev || config
28
+ else config = null
29
+ }
30
+
31
+ if (!config) {
32
+ logger.error(`导出目标 ${target} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置`)
33
+ process.exitCode = 1
34
+ return
30
35
  }
31
36
 
32
37
  logger.step(`导出 ${target} (${environment})`)
@@ -1,24 +1,11 @@
1
1
  import { logger } from '../../logger.js'
2
2
 
3
3
  export async function handleStart(cli, args) {
4
- const service = args[0] || 'dev'
4
+ const service = args[0] || 'development'
5
5
 
6
6
  const environment = cli.determineEnvironment()
7
7
  const envKey = cli.normalizeEnvKey(environment)
8
-
9
- let rawConfig = cli.commands.start[service]
10
- let configNamespace = 'start'
11
-
12
- if (!rawConfig && cli.commands.dev?.[service]) {
13
- if (envKey !== 'dev') {
14
- logger.error(`目标 ${service} 仅支持开发环境启动,请使用 --dev 或省略环境标志。`)
15
- process.exitCode = 1
16
- return
17
- }
18
- rawConfig = cli.commands.dev[service]
19
- configNamespace = 'dev'
20
- logger.info(`检测到 legacy "dev" 配置,已自动回退至 ${service} 开发脚本。`)
21
- }
8
+ const rawConfig = cli.commands.start[service]
22
9
 
23
10
  if (!rawConfig) {
24
11
  logger.error(`未找到启动配置: ${service}`)
@@ -27,13 +14,17 @@ export async function handleStart(cli, args) {
27
14
  }
28
15
 
29
16
  let startConfig = rawConfig
30
- if (configNamespace === 'start' && rawConfig && typeof rawConfig === 'object') {
31
- if (rawConfig[envKey]) startConfig = rawConfig[envKey]
32
- else if (envKey === 'staging' && rawConfig?.prod) startConfig = rawConfig.prod
17
+ const isRunnableConfig =
18
+ rawConfig &&
19
+ typeof rawConfig === 'object' &&
20
+ (rawConfig.command || rawConfig.internal || rawConfig.concurrent || rawConfig.sequential)
21
+
22
+ if (!isRunnableConfig && rawConfig && typeof rawConfig === 'object') {
23
+ startConfig = rawConfig[envKey] || null
33
24
  }
34
25
 
35
26
  if (!startConfig) {
36
- logger.error(`启动目标 ${service} 未提供 ${environment} 环境配置。`)
27
+ logger.error(`启动目标 ${service} 未提供 ${cli.getEnvironmentFlagExample(envKey) || envKey} 环境配置。`)
37
28
  process.exitCode = 1
38
29
  return
39
30
  }
@@ -41,7 +32,7 @@ export async function handleStart(cli, args) {
41
32
  logger.step(`启动 ${service} 服务 (${environment})`)
42
33
 
43
34
  if (startConfig.concurrent && Array.isArray(startConfig.commands)) {
44
- await cli.handleConcurrentCommands(startConfig.commands, configNamespace, envKey)
35
+ await cli.handleConcurrentCommands(startConfig.commands, 'start', envKey)
45
36
  return
46
37
  }
47
38
 
@@ -52,26 +43,16 @@ export async function handleStart(cli, args) {
52
43
 
53
44
  const ports = cli.collectStartPorts(service, startConfig, envKey)
54
45
 
55
- if (envKey === 'dev' && ports.length > 0) {
46
+ if (envKey === 'development' && ports.length > 0) {
56
47
  logger.info(`开发环境自动清理端口: ${ports.join(', ')}`)
57
48
  }
58
49
 
59
50
  const configToExecute = {
60
51
  ...startConfig,
61
52
  ...(ports.length > 0 ? { ports } : {}),
62
- ...(envKey === 'dev' ? { forcePortCleanup: true } : {}),
53
+ ...(envKey === 'development' ? { forcePortCleanup: true } : {}),
63
54
  }
64
55
 
65
56
  // 为执行阶段构造环境标志,确保 dotenv 选择正确层
66
- const execFlags = { ...cli.flags }
67
- ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
68
- key => delete execFlags[key]
69
- )
70
- if (envKey === 'prod') execFlags.prod = true
71
- else if (envKey === 'dev') execFlags.dev = true
72
- else if (envKey === 'test') execFlags.test = true
73
- else if (envKey === 'e2e') execFlags.e2e = true
74
- else if (envKey === 'staging') execFlags.staging = true
75
-
76
- await cli.executeCommand(configToExecute, execFlags)
57
+ await cli.executeCommand(configToExecute, cli.createExecutionFlags(environment))
77
58
  }
@@ -64,8 +64,6 @@ export async function handleWorktree(cli, args) {
64
64
  break
65
65
 
66
66
  case 'del':
67
- case 'delete':
68
- case 'rm':
69
67
  // 互斥校验:--all 不能与 issue 编号同时使用
70
68
  // args[0] 是 action,args[1] 开始才是 issue 编号
71
69
  if (cli.flags.all && args.length > 1) {
@@ -131,12 +129,10 @@ export async function handleWorktree(cli, args) {
131
129
  break
132
130
 
133
131
  case 'list':
134
- case 'ls':
135
132
  await worktreeManager.list()
136
133
  break
137
134
 
138
135
  case 'clean':
139
- case 'prune':
140
136
  await worktreeManager.clean()
141
137
  break
142
138