@ranger1/dx 0.1.96 → 0.1.98

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/README.md CHANGED
@@ -4,6 +4,38 @@
4
4
 
5
5
  本工具通过项目内的 `dx/config/*` 配置文件来驱动命令执行:你可以把它理解成「带环境变量分层 + 校验 + 命令编排能力的脚本系统」。
6
6
 
7
+ ## 当前规范
8
+
9
+ 当前版本的 `dx` 已收敛到 strict 配置规范。
10
+
11
+ 这意味着:
12
+
13
+ - 配置写错时直接报错,不再自动兼容旧写法
14
+ - `help` 输出由 `dx/config/commands.json` 动态生成,不再依赖代码中的大段硬编码文案
15
+ - 命令配置中的环境分支必须使用完整环境键
16
+
17
+ 当前推荐且受支持的环境键:
18
+
19
+ - `development`
20
+ - `staging`
21
+ - `production`
22
+ - `test`
23
+ - `e2e`
24
+
25
+ 当前推荐且受支持的 CLI 环境标志:
26
+
27
+ - `--dev`
28
+ - `--staging`
29
+ - `--prod`
30
+ - `--test`
31
+ - `--e2e`
32
+
33
+ 不再建议写法:
34
+
35
+ - `dev` / `prod` 作为配置节点名
36
+ - `--development` / `--production` / `--stage`
37
+ - 任何旧配置回退、旧命令别名或自动输入修正
38
+
7
39
  ## 安装
8
40
 
9
41
  必须全局安装,并始终使用最新版本:
@@ -70,12 +102,14 @@ DX_CONFIG_DIR=/path/to/your-repo/dx/config dx status
70
102
 
71
103
  ### 1) dx/config/commands.json
72
104
 
73
- 这是核心文件,定义了 dx 各命令要执行的 shell 命令,支持:
105
+ 这是核心文件,定义了 dx 各命令要执行的 shell 命令,也承载帮助信息配置。
106
+
107
+ 它支持:
74
108
 
75
109
  - 单命令:`{ "command": "..." }`
76
- - 并发:`{ "concurrent": true, "commands": ["build.front.dev", "build.admin.dev"] }`
77
- - 串行:`{ "sequential": true, "commands": ["build.backend.prod", "build.sdk"] }`
78
- - 环境分支:如 `build.backend.dev` / `build.backend.prod`(dx 会根据 `--dev/--prod/--staging/...` 选择)
110
+ - 并发:`{ "concurrent": true, "commands": ["build.front.development", "build.admin.development"] }`
111
+ - 串行:`{ "sequential": true, "commands": ["build.backend.production", "build.sdk"] }`
112
+ - 环境分支:如 `build.backend.development` / `build.backend.production`(dx 会根据 `--dev/--prod/--staging/...` 选择)
79
113
  - dotenv 包裹:配置里带 `"app": "backend"` 时,dx 会按 `env-layers.json` 拼出 dotenv 层并用 `pnpm exec dotenv ... -- <command>` 执行
80
114
 
81
115
  常见字段(单命令配置):
@@ -95,9 +129,39 @@ DX_CONFIG_DIR=/path/to/your-repo/dx/config dx status
95
129
  命令路径引用(并发/串行的 commands 数组)使用点号字符串,例如:
96
130
 
97
131
  ```json
98
- { "concurrent": true, "commands": ["build.shared", "build.front.dev"] }
132
+ { "concurrent": true, "commands": ["build.shared", "build.front.development"] }
133
+ ```
134
+
135
+ 帮助配置示例:
136
+
137
+ ```json
138
+ {
139
+ "help": {
140
+ "summary": "统一开发环境管理工具",
141
+ "globalOptions": [
142
+ { "flags": ["--dev"], "description": "使用 development 环境" },
143
+ { "flags": ["--prod"], "description": "使用 production 环境" }
144
+ ],
145
+ "commands": {
146
+ "start": {
147
+ "summary": "启动/桥接服务",
148
+ "notes": ["未指定 service 时默认使用开发套件,仅允许 --dev"],
149
+ "examples": [
150
+ { "command": "dx start backend --dev", "description": "启动后端开发服务" }
151
+ ]
152
+ }
153
+ }
154
+ }
155
+ }
99
156
  ```
100
157
 
158
+ 约束:
159
+
160
+ - 命令级帮助推荐放在 `help.commands.<command>`
161
+ - target 级帮助推荐放在 `help.targets.<command>.<target>`
162
+ - 帮助示例必须与真实命令树一致,不能写配置里不存在的 target
163
+ - 运行时会校验 help 配置结构;坏配置会直接报错
164
+
101
165
  ### 2) dx/config/env-layers.json
102
166
 
103
167
  用于定义不同环境下加载哪些 `.env.*` 文件(顺序 = 覆盖优先级)。格式:
@@ -202,6 +266,15 @@ dx test e2e backend apps/backend/e2e/auth
202
266
  dx test e2e quantify apps/quantify/e2e/health/health.e2e-spec.ts
203
267
  ```
204
268
 
269
+ 关于 `help`:
270
+
271
+ - `dx --help`
272
+ - `dx help <command>`
273
+
274
+ 现在都优先从 `commands.json` 的 `help` 区域动态生成。
275
+
276
+ 如果某个命令还没有补充足够的 `help` 元数据,输出会回退到配置结构推导出的最小帮助,而不是旧的手写兼容文案。
277
+
205
278
  命令约束摘要:
206
279
 
207
280
  - 对声明了 `requiresPath: true` 的 E2E target,`dx test e2e <target>` 必须提供文件或目录路径,禁止无路径或 `all` 全量执行
@@ -6,13 +6,8 @@ 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) {
@@ -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