@weotro/dx 0.1.18 → 0.1.21

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
@@ -103,6 +103,57 @@ DX_CONFIG_DIR=/path/to/your-repo/dx/config dx status
103
103
 
104
104
  ### 1) dx/config/commands.json
105
105
 
106
+ #### 自定义命令参数透传
107
+
108
+ 在选中的可执行叶节点声明 `allowPassthrough: true`,然后用第一个独立的 `--`
109
+ 分开 DX 选项和子命令参数:
110
+
111
+ ```sh
112
+ dx release plan --prod -- --brands zh --targets backend --ref v0.2.205
113
+ ```
114
+
115
+ 最小 `commands.json`(脚本由目标项目提供):
116
+
117
+ ```json
118
+ {
119
+ "release": {
120
+ "plan": {
121
+ "production": {
122
+ "command": "node scripts/release/cli.mjs plan --environment production",
123
+ "allowPassthrough": true
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ 字段契约:
131
+
132
+ | 字段 | 类型 / 默认值 | 作用 |
133
+ | --- | --- | --- |
134
+ | `allowPassthrough` | boolean / `false` | 只有严格的 `true` 开启透传;必须放在环境选择后的 `command` 叶节点,父节点不继承 |
135
+ | `command` | string | 现有 Bash 命令;用户参数逐项转义后追加在末尾,固定参数在前 |
136
+
137
+ 适用于通用配置处理器执行的自定义命令,包括 `release plan/run` 这类内置命令族的扩展 action,
138
+ 以及任意非内置顶层命令。`concurrent`、`sequential`、`internal` 配置暂不支持透传,
139
+ 即使声明 `allowPassthrough: true` 也会拒绝非空参数;不会向多个子任务广播。
140
+ 内置 `test/db/env` 等仍使用各自已有的参数契约,此字段不会覆盖内置处理器。
141
+
142
+ 迁移时,为确实需要接收参数的叶节点增加该字段,并将业务参数移到 `--` 后。
143
+ 未开启或不支持透传的配置收到非空子参数时会非零退出,不再静默丢弃后执行默认命令。
144
+ 没有子参数或仅有末尾 `--` 时保留旧行为;省略分隔符的未知选项继续报错。
145
+
146
+ 第一个 `--` 后的顺序、重复项、空字符串、特殊字符和第二个 `--` 均保留。
147
+ 其中的 `--prod`、`-Y`、`--help`、`--version`、`--config-dir` 不影响 DX 自身。
148
+ 危险操作仍须通过原确认流程;子命令退出失败时 DX 非零退出。
149
+ DX 不校验业务参数含义,拼错的 key、品牌、目标、ref 由子脚本严格校验。
150
+
151
+ 建议 `command` 只调用一个脚本;包含管道、多个语句或末尾注释的 shell 文本应先移入脚本,
152
+ 确保追加参数由明确的入口接收。不要在命令中自行拼接用户输入。
153
+
154
+ 回归验证:`pnpm test --runInBand test/configured-passthrough.test.js`,测试使用真实
155
+ `bin/dx.js` 和临时 probe,校验实际子进程 argv、拒绝时未启动子进程及失败退出。
156
+
106
157
  这是核心文件,定义了 dx 各命令要执行的 shell 命令,也承载帮助信息配置。
107
158
 
108
159
  它支持:
package/bin/dx.js CHANGED
@@ -8,7 +8,9 @@ function parseConfigDir(argv) {
8
8
  const envValue = process.env.DX_CONFIG_DIR
9
9
  if (envValue) return String(envValue)
10
10
 
11
- const args = Array.isArray(argv) ? argv : []
11
+ const raw = Array.isArray(argv) ? argv : []
12
+ const separator = raw.indexOf('--')
13
+ const args = separator === -1 ? raw : raw.slice(0, separator)
12
14
  const idx = args.indexOf('--config-dir')
13
15
  if (idx !== -1 && idx + 1 < args.length) return String(args[idx + 1])
14
16
  for (const token of args) {
@@ -22,7 +24,14 @@ function stripConfigDirArgs(argv) {
22
24
  const out = []
23
25
  for (let i = 0; i < args.length; i++) {
24
26
  const token = args[i]
27
+ if (token === '--') {
28
+ out.push(...args.slice(i))
29
+ break
30
+ }
25
31
  if (token === '--config-dir') {
32
+ if (args[i + 1] === undefined || args[i + 1] === '--') {
33
+ throw new Error('--config-dir 需要在 -- 分隔符前提供配置目录')
34
+ }
26
35
  i++
27
36
  continue
28
37
  }
package/lib/cli/dx-cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  resolveTargetRequiredVars,
12
12
  } from '../env-policy.js'
13
13
  import { FLAG_DEFINITIONS, parseFlags } from './flags.js'
14
- import { getCleanArgs, getCleanArgsWithConsumedValues } from './args.js'
14
+ import { getCleanArgs, getCleanArgsWithConsumedValues, getPassthroughArgs } from './args.js'
15
15
  import { showHelp, showCommandHelp } from './help.js'
16
16
  import { buildStrictHelpValidationContext, validateHelpConfig } from './help-schema.js'
17
17
  import { appendNxVerboseFlag } from './nx-command.js'
@@ -189,12 +189,14 @@ class DxCli {
189
189
  }
190
190
 
191
191
  registerConfiguredCommandHandlers() {
192
+ this.configuredCommandNames = new Set()
192
193
  for (const [commandName, config] of Object.entries(this.commands || {})) {
193
194
  if (commandName === 'help') continue
194
195
  if (this.commandHandlers[commandName]) continue
195
196
  if (!this.isExecutableConfigTree(config)) continue
196
197
 
197
198
  this.commandHandlers[commandName] = args => this.handleConfiguredCommand(commandName, args)
199
+ this.configuredCommandNames.add(commandName)
198
200
  }
199
201
  }
200
202
 
@@ -220,6 +222,9 @@ class DxCli {
220
222
  return
221
223
  }
222
224
 
225
+ const passthroughArgs = getPassthroughArgs(this.args)
226
+ this.validateConfiguredPassthrough(config, passthroughArgs)
227
+
223
228
  if (config.dangerous) {
224
229
  const operation = config.description || [commandName, ...args].join(' ')
225
230
  const confirmed = await confirmManager.confirmDangerous(operation, environment, this.flags.Y)
@@ -234,7 +239,17 @@ class DxCli {
234
239
  } else if (config.sequential && Array.isArray(config.commands)) {
235
240
  await this.handleSequentialCommands(config.commands, environment)
236
241
  } else {
237
- await this.executeCommand(config)
242
+ await this.executeCommand(config, undefined, passthroughArgs)
243
+ }
244
+ }
245
+
246
+ validateConfiguredPassthrough(config, args) {
247
+ if (args.length === 0) return
248
+ if (config?.internal || config?.concurrent || config?.sequential || !config?.command) {
249
+ throw new Error('此命令类型不支持 -- 参数透传;仅普通 command 叶节点支持,请移除额外参数或改用独立脚本命令。')
250
+ }
251
+ if (config.allowPassthrough !== true) {
252
+ throw new Error('此命令未启用参数透传;请在选中的 command 叶节点设置 allowPassthrough: true,并使用 dx <命令路径> [环境标志] -- <子命令参数>。')
238
253
  }
239
254
  }
240
255
 
@@ -389,6 +404,14 @@ class DxCli {
389
404
 
390
405
  this.validateInputs()
391
406
 
407
+ // Reject unsupported custom argv before dependency installation or startup hooks.
408
+ if (this.configuredCommandNames.has(this.command)) {
409
+ const consumed = this.validateFlags(this.command, this.getAllowedFlags(this.command))
410
+ const [, ...args] = getCleanArgsWithConsumedValues(this.args, consumed)
411
+ const config = this.resolveConfiguredCommand(this.command, args, this.determineEnvironment())
412
+ this.validateConfiguredPassthrough(config, getPassthroughArgs(this.args))
413
+ }
414
+
392
415
  // Fail fast for unknown commands before dependency/env startup checks.
393
416
  if (this.command && !this.commandHandlers[this.command]) {
394
417
  logger.error(`未知命令: ${this.command}`)
@@ -810,6 +833,7 @@ class DxCli {
810
833
 
811
834
  reportUnknownFlag(command, flag, allowedFlags) {
812
835
  logger.error(`检测到未识别的选项: ${flag}`)
836
+ logger.info('自定义命令参数请放在 -- 后,并在 command 叶节点设置 allowPassthrough: true。')
813
837
  const supported = Array.from(allowedFlags.keys())
814
838
  if (supported.length > 0) {
815
839
  logger.info(`支持的选项: ${supported.join(', ')}`)
@@ -937,7 +961,8 @@ class DxCli {
937
961
  }
938
962
 
939
963
  // 执行单个命令
940
- async executeCommand(config, overrideFlags) {
964
+ async executeCommand(config, overrideFlags, passthroughArgs = []) {
965
+ this.validateConfiguredPassthrough(config, passthroughArgs)
941
966
  if (!config) {
942
967
  logger.error('无效的命令配置')
943
968
  return
@@ -1022,6 +1047,7 @@ class DxCli {
1022
1047
  env: config.env || {},
1023
1048
  skipEnvValidation: Boolean(config.skipEnvValidation),
1024
1049
  forcePortCleanup: Boolean(config.forcePortCleanup),
1050
+ passthroughArgs,
1025
1051
  }
1026
1052
 
1027
1053
  await execManager.executeCommand(rawCommand, options)
@@ -84,19 +84,31 @@ export function getGlobalHelpModel(commands = {}, context = {}) {
84
84
  export function getCommandHelpModel(commands = {}, commandName, context = {}) {
85
85
  const commandConfig = commands?.[commandName] ?? {}
86
86
  const commandHelp = getCommandHelpConfig(commands, commandName)
87
+ const notes = [...normalizeArray(commandHelp?.notes)]
88
+ if (hasPassthroughCommand(commandConfig)) {
89
+ notes.push(`自定义 command 叶节点设置 allowPassthrough: true 后可透传参数:dx ${commandName} <命令路径> [环境标志] -- <子命令参数>;未开启或组合/internal 命令收到额外参数会报错。`)
90
+ }
87
91
 
88
92
  return {
89
93
  name: commandName,
90
94
  summary: resolveSummary(commandConfig, commandHelp) || BUILTIN_SUMMARIES[commandName] || '',
91
95
  usage: resolveUsage(commandName, commandHelp, commandConfig),
92
96
  args: normalizeArray(commandHelp?.args),
93
- notes: normalizeArray(commandHelp?.notes),
97
+ notes,
94
98
  examples: normalizeArray(commandHelp?.examples),
95
99
  options: normalizeArray(commandHelp?.options),
96
100
  targets: resolveVisibleTargets(commands, commandName, commandConfig, context),
97
101
  }
98
102
  }
99
103
 
104
+ function hasPassthroughCommand(node) {
105
+ if (!isPlainObject(node)) return false
106
+ if (node.command || node.internal || node.concurrent || node.sequential) {
107
+ return Boolean(node.command && node.allowPassthrough === true && !node.internal && !node.concurrent && !node.sequential)
108
+ }
109
+ return Object.entries(node).some(([key, value]) => key !== 'help' && hasPassthroughCommand(value))
110
+ }
111
+
100
112
  export function resolveSummary(node = {}, help = null) {
101
113
  return help?.summary || node?.help?.summary || node?.description || ''
102
114
  }
package/lib/exec.js CHANGED
@@ -66,6 +66,7 @@ export class ExecManager {
66
66
  ports = [],
67
67
  skipEnvValidation = false,
68
68
  forcePortCleanup = false,
69
+ passthroughArgs = [],
69
70
  } = options
70
71
 
71
72
  // 在执行前同步环境,确保 Nx/构建工具拿到规范的 NODE_ENV
@@ -186,7 +187,12 @@ export class ExecManager {
186
187
  }
187
188
  }
188
189
 
189
- logger.command(fullCommand)
190
+ // Quote for the bash executor, after command classification so child data
191
+ // cannot change environment selection or trigger port-cleanup heuristics.
192
+ const executionCommand = passthroughArgs.length > 0
193
+ ? `${fullCommand} ${passthroughArgs.map(arg => `'${arg.replace(/'/g, "'\\''")}'`).join(' ')}`
194
+ : fullCommand
195
+ logger.command(executionCommand)
190
196
 
191
197
  // 执行命令(可能重试)
192
198
  let lastError = null
@@ -253,7 +259,7 @@ export class ExecManager {
253
259
  }
254
260
  }
255
261
 
256
- const result = await this.spawnCommand(fullCommand, {
262
+ const result = await this.spawnCommand(executionCommand, {
257
263
  cwd: cwd || process.cwd(),
258
264
  stdio,
259
265
  env: sanitizeChildEnv({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weotro/dx",
3
- "version": "0.1.18",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,8 +25,9 @@ Use this skill to complete a Dependabot remediation loop with minimal manual inp
25
25
  5. Apply dependency changes (prefer overrides/resolutions for transitive alerts).
26
26
  6. Refresh lockfile.
27
27
  7. Run required project verification commands.
28
- 8. For an authorized PR delivery, commit, push, and open a focused PR; a local remediation request finishes with the verified local changes.
29
- 9. Report residual risk (unpatched alerts) in PR and final reply.
28
+ 8. For authorized issue and PR delivery, find and reuse a matching tracking issue or create one with the alert scope, remediation plan, and verification results. Commit, push, and open a focused PR linked to that issue. A local remediation request finishes with verified local changes and drafts for any requested delivery that still lacks authorization.
29
+ 9. If the user requests merging, continue through the merge workflow below using the existing authorization; opening a PR alone does not complete that request.
30
+ 10. Report the issue, PR, merge outcome, and residual risk using the final reply requirements below.
30
31
 
31
32
  ## Default Commands
32
33
  ```bash
@@ -51,7 +52,18 @@ pnpm install --lockfile-only
51
52
 
52
53
  ## PR Requirements
53
54
  Include these sections:
54
- 1. Fixed alerts: alert id, package, target version
55
- 2. Remaining alerts: alert id, reason (for example, no upstream patch)
56
- 3. Verification: exact commands run and outcomes
57
- 4. Risk note: what is deferred and why
55
+ 1. Tracking issue: issue number and URL; use `Closes #<issue>` only when the PR completes the issue's entire scope. Otherwise use `Refs #<issue>` and keep the remaining work tracked.
56
+ 2. Fixed alerts: alert id, package, target version
57
+ 3. Remaining alerts: alert id, reason (for example, no upstream patch)
58
+ 4. Verification: exact commands run and outcomes
59
+ 5. Risk note: what is deferred and why
60
+
61
+ ## Merge Workflow
62
+ - Before merging, verify the PR's current head, required checks, review requirements, and mergeability. Follow the repository's merge strategy and branch protection; do not bypass required checks or reviews.
63
+ - When merge is authorized and requirements pass, merge with `gh pr merge`, binding the operation to the verified head via `--match-head-commit`. If the head changes, verify the new head before retrying.
64
+ - If checks or reviews block merging, resolve actionable failures within scope and recheck. Report any remaining blocker precisely. Enabling auto-merge or entering a merge queue is pending, not a completed merge.
65
+ - After merging, verify the PR's `MERGED` state and merge commit, check the tracking issue's state, and fetch Dependabot alerts again. Report still-open alerts as observed; distinguish scan results that have not yet updated from confirmed remediation failures.
66
+
67
+ ## Final Reply Requirements
68
+ Always explicitly mention both **Issue** and **PR**, including their numbers, URLs, and observed states. If either was not created or reused, say so and explain why; never substitute a Dependabot alert number for an issue number.
69
+ State whether the PR was actually merged, with the merge commit when available, or give the concrete pending condition or blocker. Include verification results and remaining alerts. Do not claim completion of a requested merge based only on PR creation, auto-merge configuration, or queue entry.
@@ -11,6 +11,8 @@ description: 仅在显式调用 git-release 时使用:准备或执行版本发
11
11
  只读检索、本地草稿、可回滚编辑和常规测试可自主推断并继续;从相关文件和直接依赖开始,仅在证据不足或影响跨模块时扩大范围,复用仍有效的验证。
12
12
  仅在不可逆或难以回滚的修改缺少具体授权时,先完成预览、草稿与验证,再说明对象和影响并等待确认;会话中已有授权继续有效。缺少事实时先查证、标注假设并推进独立工作,确实无法确定操作目标时才询问。对外发送消息须有明确授权。
13
13
 
14
+ 用户显式调用本技能执行发布,即明确授权在本次目标仓库创建并推送目标版本的 tag,以及创建并发布对应的 GitHub Release。完成准备和验证后连续执行至发布结果核验,无需再次询问是否推送 tag 或创建 Release。用户明确限定为仅准备、草稿、审计或修改技能时,按该范围执行;本授权不包含覆盖冲突 tag、删除既有 Release 或额外的生产操作。
15
+
14
16
  ## 发布准备
15
17
 
16
18
  从当前 release 分支、用户指定版本或项目版本策略确定目标版本,说明来源。若项目采用 `release/vX.Y.Z`(也支持 alpha/beta/rc)则沿用;其他命名服从项目规则。没有版本策略时可以准备下一补丁版本的本地草稿,公开发布前核实目标。
@@ -27,7 +29,7 @@ description: 仅在显式调用 git-release 时使用:准备或执行版本发
27
29
 
28
30
  ## 创建发布
29
31
 
30
- 推送 tag 和创建 Release 可能触发不可逆的制品发布或部署。在执行前核对目标仓库、版本、commit、发行说明及现有授权;授权已明确覆盖这些对象时直接继续,否则准备好具体结果再确认。
32
+ 执行前核对目标仓库、版本、commit 和发行说明,确认发布检查通过、目标 tag 无冲突,然后依据「执行边界与优先级」中的明确授权直接推送 tag 并创建 GitHub Release。发布流程可能自动触发制品发布或部署,不因此追加确认步骤;仅在目标无法确定、检查失败或远端状态冲突时暂停受阻操作并说明原因。
31
33
 
32
34
  按项目发布流程创建 tag、推送并创建 Release;例如:
33
35
  ```bash