@ranger1/dx 0.1.95 → 0.1.96

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,5 +1,5 @@
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'
@@ -83,11 +83,7 @@ export async function handleTest(cli, args) {
83
83
 
84
84
  // 解析 -t 参数用于指定特定测试用例(使用原始参数列表)
85
85
  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
- }
86
+ const testNamePattern = resolveTestNamePattern(allArgs)
91
87
 
92
88
  // 根据测试类型自动设置环境标志
93
89
  if (type === 'e2e' && !cli.flags.e2e) {
@@ -120,7 +116,8 @@ export async function handleTest(cli, args) {
120
116
  process.exit(1)
121
117
  }
122
118
 
123
- let command = fileCommand.replace('{TEST_PATH}', shellEscape(testPath))
119
+ const normalizedTestPath = normalizeE2eTestPathForCommand(cli, fileCommand, testPath)
120
+ let command = fileCommand.replace('{TEST_PATH}', shellEscape(normalizedTestPath))
124
121
 
125
122
  if (testNamePattern) {
126
123
  command += ` -t ${shellEscape(testNamePattern)}`
@@ -179,6 +176,15 @@ function shellEscape(value) {
179
176
  return `'${String(value).replace(/'/g, `'\\''`)}'`
180
177
  }
181
178
 
179
+ function resolveTestNamePattern(args = []) {
180
+ const aliases = ['-t', '--name', '--test-name-pattern']
181
+ for (let i = 0; i < args.length; i++) {
182
+ if (!aliases.includes(args[i])) continue
183
+ if (i + 1 < args.length) return args[i + 1]
184
+ }
185
+ return null
186
+ }
187
+
182
188
  function shouldUseDirectPathArg(command) {
183
189
  const text = String(command || '')
184
190
  return (
@@ -196,11 +202,11 @@ function normalizeUnitTestPathForCommand(cli, command, testPath) {
196
202
  return rawPath
197
203
  }
198
204
 
199
- const vitestProjectCwd = resolveNxVitestProjectCwd(cli, command)
200
- if (!vitestProjectCwd) return rawPath
205
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test'])
206
+ if (!projectCwd) return rawPath
201
207
 
202
208
  const projectRoot = cli?.projectRoot || process.cwd()
203
- const absoluteProjectCwd = join(projectRoot, vitestProjectCwd)
209
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
204
210
  const absoluteTestPath = join(projectRoot, rawPath)
205
211
  const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
206
212
 
@@ -211,33 +217,78 @@ function normalizeUnitTestPathForCommand(cli, command, testPath) {
211
217
  return relativePath
212
218
  }
213
219
 
214
- function resolveNxVitestProjectCwd(cli, command) {
220
+ function normalizeE2eTestPathForCommand(cli, command, testPath) {
221
+ const rawPath = String(testPath || '')
222
+ if (!rawPath) return rawPath
223
+
224
+ const projectCwd = resolveNxTargetProjectCwd(cli, command, ['test:e2e'])
225
+ if (!projectCwd) return rawPath
226
+
215
227
  const projectRoot = cli?.projectRoot || process.cwd()
216
- const nxTarget = extractNxTestTarget(command)
217
- if (!nxTarget) return null
228
+ const absoluteProjectCwd = join(projectRoot, projectCwd)
229
+ const absoluteTestPath = join(projectRoot, rawPath)
230
+ const relativePath = relative(absoluteProjectCwd, absoluteTestPath)
231
+
232
+ if (!relativePath || relativePath.startsWith('..')) {
233
+ return rawPath
234
+ }
218
235
 
219
- const projectConfigPath = join(projectRoot, 'apps', nxTarget, 'project.json')
236
+ return relativePath
237
+ }
238
+
239
+ function resolveNxTargetProjectCwd(cli, command, targetNames = []) {
240
+ const projectRoot = cli?.projectRoot || process.cwd()
241
+ const nxResolution = extractNxTarget(command, targetNames)
242
+ if (!nxResolution) return null
243
+
244
+ const projectDir = join(projectRoot, 'apps', nxResolution.project)
245
+ const projectConfigPath = join(projectDir, 'project.json')
220
246
  if (!existsSync(projectConfigPath)) return null
221
247
 
222
248
  try {
223
249
  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
250
+ const resolvedTarget = projectConfig?.targets?.[nxResolution.target]
251
+ const cwd = resolvedTarget?.options?.cwd
252
+ if (typeof cwd === 'string' && cwd.trim().length > 0) {
253
+ return cwd
254
+ }
255
+ if (nxResolution.target === 'test:e2e') {
256
+ const e2eDir = join(projectDir, 'e2e')
257
+ if (existsSync(e2eDir)) {
258
+ return relative(projectRoot, e2eDir)
259
+ }
260
+ }
261
+ return relative(projectRoot, dirname(projectConfigPath))
230
262
  } catch {
231
263
  return null
232
264
  }
233
265
  }
234
266
 
235
- function extractNxTestTarget(command) {
267
+ function extractNxTarget(command, targetNames = []) {
236
268
  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
269
+ const names = Array.isArray(targetNames) && targetNames.length > 0 ? targetNames : ['test']
270
+
271
+ for (const targetName of names) {
272
+ if (targetName === 'test') {
273
+ const directMatch = text.match(/\bnx(?:\.js)?\s+test\s+([^\s]+)/)
274
+ if (directMatch?.[1]) {
275
+ return { project: directMatch[1], target: 'test' }
276
+ }
277
+ }
278
+
279
+ const escapedTarget = targetName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
280
+ const runMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+run\\s+([^:\\s]+):${escapedTarget}\\b`))
281
+ if (runMatch?.[1]) {
282
+ return { project: runMatch[1], target: targetName }
283
+ }
284
+
285
+ const directColonMatch = text.match(new RegExp(`\\bnx(?:\\.js)?\\s+${escapedTarget}\\s+([^\\s]+)`))
286
+ if (directColonMatch?.[1]) {
287
+ return { project: directColonMatch[1], target: targetName }
288
+ }
289
+ }
290
+
291
+ return null
241
292
  }
242
293
 
243
294
  export async function handleLint(cli, args) {
package/lib/cli/flags.js CHANGED
@@ -24,7 +24,11 @@ export const FLAG_DEFINITIONS = {
24
24
  { flag: '--name', expectsValue: true },
25
25
  { flag: '-n', expectsValue: true },
26
26
  ],
27
- test: [{ flag: '-t', expectsValue: true }],
27
+ test: [
28
+ { flag: '-t', expectsValue: true },
29
+ { flag: '--name', expectsValue: true },
30
+ { flag: '--test-name-pattern', expectsValue: true },
31
+ ],
28
32
  package: [
29
33
  { flag: '--skip-build' },
30
34
  { flag: '--keep-workdir' },
package/lib/cli/help.js CHANGED
@@ -41,11 +41,11 @@ export function showHelp() {
41
41
  ' dx db script fix-pending-transfer-status --prod # 运行数据库脚本(生产环境,需确认)',
42
42
  ' dx db script my-script --dev -- --arg1 --arg2 # 向脚本传递额外参数(-- 后面的部分)',
43
43
  '',
44
- ' test [type] [target] [path] [-t pattern] 运行测试',
44
+ ' test [type] [target] [path] [-t pattern|--name pattern] 运行测试',
45
45
  ' type: e2e, unit (默认: e2e)',
46
46
  ' target: 由 commands.json 的 test.<type>.<target> 决定(e2e 默认会拒绝隐式 all)',
47
47
  ' path: 测试文件或目录路径 (guarded e2e target 必填,例如 backend/quantify)',
48
- ' -t pattern: 指定测试用例名称模式 (可选,需要和 path 一起使用)',
48
+ ' -t pattern / --name pattern: 指定测试用例名称模式 (可选,需要和 path 一起使用)',
49
49
  ' 说明: guarded E2E target 禁止无路径或 all 全量执行,dx test e2e all 也不受支持',
50
50
  '',
51
51
  ' worktree [action] [num...] Git Worktree管理',
@@ -94,6 +94,7 @@ export function showHelp() {
94
94
  ' dx test e2e backend apps/backend/e2e/auth # 按目录运行后端 E2E',
95
95
  ' dx test e2e backend apps/backend/e2e/activity/activity.admin.e2e-spec.ts # 运行单个E2E测试文件',
96
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" # 运行单测中的特定用例',
97
98
  ' dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts # 运行 Quantify E2E 文件',
98
99
  ' dx test unit backend apps/backend/src/modules/chat/chat.service.spec.ts # 运行单个后端单测文件',
99
100
  ' dx test e2e all # 不受支持,必须指定 target 和 path',
@@ -254,13 +255,13 @@ start 命令用法:
254
255
  case 'test':
255
256
  console.log(`
256
257
  test 命令用法:
257
- dx test [type] [target] [path] [-t pattern]
258
+ dx test [type] [target] [path] [-t pattern|--name pattern]
258
259
 
259
260
  参数说明:
260
261
  type: e2e, unit (默认: e2e)
261
262
  target: 由 commands.json 的 test.<type>.<target> 决定
262
263
  path: guarded e2e target 必须提供文件或目录路径
263
- -t pattern: 指定测试用例名称模式,需要和 path 一起使用
264
+ -t pattern / --name pattern: 指定测试用例名称模式,需要和 path 一起使用
264
265
 
265
266
  限制说明:
266
267
  guarded E2E target 禁止无路径全量执行。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.95",
3
+ "version": "0.1.96",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {