@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,552 @@
1
+ import { logger } from '../logger.js'
2
+ import { parseFlags } from './flags.js'
3
+
4
+ function isPlainObject(value) {
5
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
6
+ }
7
+
8
+ function assertOptionalString(value, path) {
9
+ if (value === undefined) return
10
+ if (typeof value !== 'string' || value.trim() === '') {
11
+ throw new Error(`${path} must be a non-empty string`)
12
+ }
13
+ }
14
+
15
+ function assertArrayOfObjects(value, path) {
16
+ if (value === undefined) return
17
+ if (!Array.isArray(value)) {
18
+ throw new Error(`${path} must be an array`)
19
+ }
20
+
21
+ value.forEach((entry, index) => {
22
+ if (!isPlainObject(entry)) {
23
+ throw new Error(`${path}[${index}] must be an object`)
24
+ }
25
+ })
26
+ }
27
+
28
+ function assertArray(value, path) {
29
+ if (value === undefined) return
30
+ if (!Array.isArray(value)) {
31
+ throw new Error(`${path} must be an array`)
32
+ }
33
+ }
34
+
35
+ function assertDescription(value, path) {
36
+ if (typeof value !== 'string' || value.trim() === '') {
37
+ throw new Error(`${path} must include a description`)
38
+ }
39
+ }
40
+
41
+ function assertFlagsExist(flags, knownFlags, path) {
42
+ if (!Array.isArray(flags) || flags.length === 0) {
43
+ throw new Error(`${path} must include at least one flag`)
44
+ }
45
+
46
+ flags.forEach((flag, index) => {
47
+ if (typeof flag !== 'string' || flag.trim() === '') {
48
+ throw new Error(`${path}[${index}] must be a non-empty string`)
49
+ }
50
+ if (!knownFlags.has(flag)) {
51
+ throw new Error(`${path}[${index}] references unknown flag: ${flag}`)
52
+ }
53
+ })
54
+ }
55
+
56
+ function extractCommandName(command) {
57
+ if (typeof command !== 'string' || command.trim() === '') {
58
+ return ''
59
+ }
60
+
61
+ const tokens = command.trim().split(/\s+/)
62
+
63
+ if (tokens[0] === 'dx') {
64
+ return tokens[1] ?? ''
65
+ }
66
+
67
+ return tokens[0] ?? ''
68
+ }
69
+
70
+ function assertRegisteredCommand(command, registeredCommands, path) {
71
+ const commandName = extractCommandName(command)
72
+
73
+ if (!commandName) {
74
+ throw new Error(`${path} must include a command`)
75
+ }
76
+
77
+ if (!registeredCommands.has(commandName)) {
78
+ throw new Error(`${path} references unknown command: ${commandName}`)
79
+ }
80
+ }
81
+
82
+ function validateHelpExamples(examples, path, context) {
83
+ const { registeredCommands, exampleValidator } = context
84
+
85
+ assertArrayOfObjects(examples, path)
86
+
87
+ examples?.forEach((example, index) => {
88
+ const entryPath = `${path}[${index}]`
89
+ assertOptionalString(example.command, `${entryPath}.command`)
90
+ assertDescription(example.description, `${entryPath}.description`)
91
+ assertRegisteredCommand(example.command, registeredCommands, `${entryPath}.command`)
92
+
93
+ const result = exampleValidator(example.command)
94
+ if (!result?.ok) {
95
+ throw new Error(result?.reason || `${entryPath}.command failed validation`)
96
+ }
97
+ })
98
+ }
99
+
100
+ function validateHelpOptions(options, path, knownFlags) {
101
+ assertArrayOfObjects(options, path)
102
+
103
+ options?.forEach((option, index) => {
104
+ const entryPath = `${path}[${index}]`
105
+ assertFlagsExist(option.flags, knownFlags, `${entryPath}.flags`)
106
+ assertDescription(option.description, `${entryPath}.description`)
107
+ })
108
+ }
109
+
110
+ function validateCommandHelp(commandName, help, context) {
111
+ const { registeredCommands, usageValidator } = context
112
+
113
+ if (!isPlainObject(help)) {
114
+ throw new Error(`help.commands.${commandName} must be an object`)
115
+ }
116
+
117
+ if (!registeredCommands.has(commandName)) {
118
+ throw new Error(`help.commands.${commandName} references unknown command`)
119
+ }
120
+
121
+ assertOptionalString(help.summary, `help.commands.${commandName}.summary`)
122
+ assertArray(help.notes, `help.commands.${commandName}.notes`)
123
+
124
+ help.notes?.forEach((note, index) => {
125
+ assertOptionalString(note, `help.commands.${commandName}.notes[${index}]`)
126
+ })
127
+
128
+ validateHelpOptions(
129
+ help.options,
130
+ `help.commands.${commandName}.options`,
131
+ context.knownFlags,
132
+ )
133
+ validateHelpExamples(help.examples, `help.commands.${commandName}.examples`, context)
134
+
135
+ if (help.usage !== undefined) {
136
+ assertOptionalString(help.usage, `help.commands.${commandName}.usage`)
137
+ const result = usageValidator(commandName, help.usage)
138
+
139
+ if (!result?.ok) {
140
+ throw new Error(result?.reason || `help.commands.${commandName}.usage failed validation`)
141
+ }
142
+ }
143
+ }
144
+
145
+ function validateTargetHelp(commandName, targetName, help, commands, context) {
146
+ const entryPath = `help.targets.${commandName}.${targetName}`
147
+
148
+ if (!isPlainObject(help)) {
149
+ throw new Error(`${entryPath} must be an object`)
150
+ }
151
+
152
+ if (!context.registeredCommands.has(commandName) || !isPlainObject(commands?.[commandName])) {
153
+ throw new Error(`help.targets.${commandName} references unknown command`)
154
+ }
155
+
156
+ if (!Object.prototype.hasOwnProperty.call(commands[commandName], targetName)) {
157
+ throw new Error(`${entryPath} references unknown target`)
158
+ }
159
+
160
+ assertOptionalString(help.summary, `${entryPath}.summary`)
161
+ assertArray(help.notes, `${entryPath}.notes`)
162
+
163
+ help.notes?.forEach((note, index) => {
164
+ assertOptionalString(note, `${entryPath}.notes[${index}]`)
165
+ })
166
+
167
+ validateHelpOptions(help.options, `${entryPath}.options`, context.knownFlags)
168
+ validateHelpExamples(help.examples, `${entryPath}.examples`, context)
169
+ }
170
+
171
+ export function validateHelpConfig(commands, context = {}) {
172
+ const {
173
+ registeredCommands = [],
174
+ knownFlags = new Map(),
175
+ usageValidator = () => ({ ok: true }),
176
+ exampleValidator = () => ({ ok: true }),
177
+ } = context
178
+
179
+ if (!isPlainObject(commands)) {
180
+ throw new Error('commands must be an object')
181
+ }
182
+
183
+ const normalizedContext = {
184
+ registeredCommands: new Set(registeredCommands),
185
+ knownFlags,
186
+ usageValidator,
187
+ exampleValidator,
188
+ }
189
+
190
+ const help = commands.help
191
+ if (help === undefined) {
192
+ return commands
193
+ }
194
+
195
+ if (!isPlainObject(help)) {
196
+ throw new Error('help must be an object')
197
+ }
198
+
199
+ assertOptionalString(help.summary, 'help.summary')
200
+ validateHelpOptions(help.globalOptions, 'help.globalOptions', normalizedContext.knownFlags)
201
+ validateHelpExamples(help.examples, 'help.examples', normalizedContext)
202
+
203
+ if (help.commands !== undefined) {
204
+ if (!isPlainObject(help.commands)) {
205
+ throw new Error('help.commands must be an object')
206
+ }
207
+
208
+ Object.entries(help.commands).forEach(([commandName, commandHelp]) => {
209
+ validateCommandHelp(commandName, commandHelp, normalizedContext)
210
+ })
211
+ }
212
+
213
+ if (help.targets !== undefined) {
214
+ if (!isPlainObject(help.targets)) {
215
+ throw new Error('help.targets must be an object')
216
+ }
217
+
218
+ Object.entries(help.targets).forEach(([commandName, targetHelp]) => {
219
+ if (!isPlainObject(targetHelp)) {
220
+ throw new Error(`help.targets.${commandName} must be an object`)
221
+ }
222
+
223
+ Object.entries(targetHelp).forEach(([targetName, targetEntryHelp]) => {
224
+ validateTargetHelp(commandName, targetName, targetEntryHelp, commands, normalizedContext)
225
+ })
226
+ })
227
+ }
228
+
229
+ return commands
230
+ }
231
+
232
+ export function buildStrictHelpValidationContext(cli) {
233
+ const knownFlags = new Map()
234
+ for (const definitions of Object.values(cli?.flagDefinitions || {})) {
235
+ if (!Array.isArray(definitions)) continue
236
+ for (const definition of definitions) {
237
+ if (!definition?.flag) continue
238
+ knownFlags.set(definition.flag, definition)
239
+ }
240
+ }
241
+
242
+ return {
243
+ registeredCommands: Object.keys(cli?.commandHandlers || {}),
244
+ knownFlags,
245
+ usageValidator: (commandName, usageText) =>
246
+ validateUsageAgainstRuntime(commandName, usageText, cli),
247
+ exampleValidator: commandText => validateExampleCommandAgainstCli(commandText, cli),
248
+ }
249
+ }
250
+
251
+ export function validateExampleCommandAgainstCli(commandText, cli) {
252
+ let tokens
253
+
254
+ try {
255
+ tokens = shellLikeSplit(commandText)
256
+ } catch (error) {
257
+ return { ok: false, reason: error.message }
258
+ }
259
+
260
+ if (tokens[0] !== cli.invocation) {
261
+ return { ok: false, reason: `example must start with ${cli.invocation}` }
262
+ }
263
+
264
+ const commandName = tokens[1]
265
+ if (!commandName) {
266
+ return { ok: false, reason: 'example must include a top-level command' }
267
+ }
268
+
269
+ if (!cli.commandHandlers?.[commandName]) {
270
+ return { ok: false, reason: `unknown command: ${commandName}` }
271
+ }
272
+
273
+ return runCliInputValidation(cli, tokens.slice(1))
274
+ }
275
+
276
+ export function validateUsageAgainstRuntime(commandName, usageText, cli) {
277
+ let tokens
278
+
279
+ try {
280
+ tokens = shellLikeSplit(usageText)
281
+ } catch (error) {
282
+ return { ok: false, reason: error.message }
283
+ }
284
+
285
+ if (tokens[0] !== cli.invocation) {
286
+ return { ok: false, reason: `usage must start with ${cli.invocation}` }
287
+ }
288
+
289
+ if (tokens[1] !== commandName) {
290
+ return {
291
+ ok: false,
292
+ reason: `usage for ${commandName} must start with "${cli.invocation} ${commandName}"`,
293
+ }
294
+ }
295
+
296
+ const runtimePositionals = tokens
297
+ .slice(2)
298
+ .filter(token => !isEnvironmentPlaceholder(token))
299
+ const runtimeMaxPositionals = getRuntimeMaxPositionals(commandName, tokens)
300
+
301
+ if (
302
+ Number.isInteger(runtimeMaxPositionals) &&
303
+ runtimePositionals.length > runtimeMaxPositionals
304
+ ) {
305
+ return {
306
+ ok: false,
307
+ reason: `usage for ${commandName} advertises ${runtimePositionals.length} positionals but runtime allows ${runtimeMaxPositionals}`,
308
+ }
309
+ }
310
+
311
+ if (mentionsPositionalEnvironmentPlaceholder(tokens)) {
312
+ return {
313
+ ok: false,
314
+ reason: `usage for ${commandName} must use environment flags instead of positional env placeholders`,
315
+ }
316
+ }
317
+
318
+ if (commandName === 'start' && !tokens.includes('<service>')) {
319
+ return { ok: false, reason: 'usage for start must include <service>' }
320
+ }
321
+
322
+ return { ok: true }
323
+ }
324
+
325
+ function getRuntimeMaxPositionals(commandName, tokens = []) {
326
+ switch (commandName) {
327
+ case 'help':
328
+ return 1
329
+ case 'build':
330
+ case 'package':
331
+ case 'clean':
332
+ case 'cache':
333
+ return 1
334
+ case 'db':
335
+ return 2
336
+ case 'worktree':
337
+ return 3
338
+ case 'test':
339
+ return tokens[2] === 'unit' ? null : 3
340
+ case 'start':
341
+ return 1
342
+ case 'lint':
343
+ case 'status':
344
+ return 0
345
+ default:
346
+ return null
347
+ }
348
+ }
349
+
350
+ function runCliInputValidation(cli, args) {
351
+ if (typeof cli?.validateInputs !== 'function') {
352
+ return validateArgumentTokens(args, cli)
353
+ }
354
+
355
+ const exitSpy = process.exit
356
+ const originalLogger = {
357
+ error: logger.error,
358
+ info: logger.info,
359
+ }
360
+ const messages = []
361
+ const originalState = {
362
+ args: cli.args,
363
+ command: cli.command,
364
+ flags: cli.flags,
365
+ subcommand: cli.subcommand,
366
+ }
367
+
368
+ try {
369
+ process.exit = code => {
370
+ throw new Error(`process.exit:${code}`)
371
+ }
372
+ logger.error = message => {
373
+ messages.push(String(message))
374
+ }
375
+ logger.info = message => {
376
+ messages.push(String(message))
377
+ }
378
+
379
+ cli.args = [...args]
380
+ cli.flags = parseFlags(cli.args)
381
+ cli.command = cli.args[0]
382
+ cli.subcommand = cli.args[1]
383
+ cli.validateInputs()
384
+ return { ok: true }
385
+ } catch (error) {
386
+ if (String(error?.message || '').startsWith('process.exit:')) {
387
+ return { ok: false, reason: messages.join(' | ') || error.message }
388
+ }
389
+ throw error
390
+ } finally {
391
+ process.exit = exitSpy
392
+ logger.error = originalLogger.error
393
+ logger.info = originalLogger.info
394
+ cli.args = originalState.args
395
+ cli.command = originalState.command
396
+ cli.flags = originalState.flags
397
+ cli.subcommand = originalState.subcommand
398
+ }
399
+ }
400
+
401
+ function validateArgumentTokens(args, cli) {
402
+ const tokens = Array.isArray(args) ? [...args] : []
403
+ const commandName = tokens[0]
404
+ if (!commandName) {
405
+ return { ok: false, reason: 'example must include a top-level command' }
406
+ }
407
+
408
+ const definitions = cli?.flagDefinitions || {}
409
+ const knownFlags = new Map()
410
+ for (const entries of Object.values(definitions)) {
411
+ if (!Array.isArray(entries)) continue
412
+ for (const entry of entries) {
413
+ if (!entry?.flag) continue
414
+ knownFlags.set(entry.flag, Boolean(entry.expectsValue))
415
+ }
416
+ }
417
+
418
+ const positionalArgs = []
419
+ for (let index = 1; index < tokens.length; index += 1) {
420
+ const token = tokens[index]
421
+ if (token === '--') break
422
+ if (!token.startsWith('-')) {
423
+ positionalArgs.push(token)
424
+ continue
425
+ }
426
+
427
+ if (!knownFlags.has(token)) {
428
+ return { ok: false, reason: `检测到未识别的选项: ${token}` }
429
+ }
430
+
431
+ if (knownFlags.get(token)) {
432
+ const next = tokens[index + 1]
433
+ if (next === undefined || next.startsWith('-')) {
434
+ return { ok: false, reason: `选项 ${token} 需要提供参数值` }
435
+ }
436
+ index += 1
437
+ }
438
+ }
439
+
440
+ const maxByCommand = {
441
+ help: 1,
442
+ build: 1,
443
+ package: 1,
444
+ db: 2,
445
+ worktree: 3,
446
+ start: 1,
447
+ lint: 0,
448
+ clean: 1,
449
+ cache: 1,
450
+ status: 0,
451
+ }
452
+
453
+ let max = maxByCommand[commandName]
454
+ if (commandName === 'test') {
455
+ max = positionalArgs[0] === 'unit' ? null : 3
456
+ }
457
+
458
+ if (Number.isInteger(max) && positionalArgs.length > max) {
459
+ return { ok: false, reason: `命令 ${commandName} 存在未识别的额外参数: ${positionalArgs.slice(max).join(', ')}` }
460
+ }
461
+
462
+ return { ok: true }
463
+ }
464
+
465
+ function isPlaceholderToken(token) {
466
+ return (
467
+ (token.startsWith('<') && token.endsWith('>')) ||
468
+ (token.startsWith('[') && token.endsWith(']'))
469
+ )
470
+ }
471
+
472
+ function isEnvironmentPlaceholder(token) {
473
+ return token === '[环境标志]'
474
+ }
475
+
476
+ function mentionsPositionalEnvironmentPlaceholder(tokens) {
477
+ return tokens.some(token => {
478
+ const normalized = token.replace(/^[<[|]+|[\]>|]+$/g, '').toLowerCase()
479
+ return normalized === 'env' || normalized === 'environment'
480
+ })
481
+ }
482
+
483
+ export function shellLikeSplit(text) {
484
+ const tokens = []
485
+ let current = ''
486
+ let quote = null
487
+ let tokenStarted = false
488
+
489
+ for (let index = 0; index < text.length; index += 1) {
490
+ const character = text[index]
491
+
492
+ if (quote) {
493
+ if (character === '\\') {
494
+ const next = text[index + 1]
495
+ if (next === quote || next === '\\') {
496
+ current += next
497
+ tokenStarted = true
498
+ index += 1
499
+ continue
500
+ }
501
+ }
502
+
503
+ if (character === quote) {
504
+ quote = null
505
+ continue
506
+ }
507
+
508
+ current += character
509
+ tokenStarted = true
510
+ continue
511
+ }
512
+
513
+ if (character === '"' || character === "'") {
514
+ quote = character
515
+ tokenStarted = true
516
+ continue
517
+ }
518
+
519
+ if (character === '\\') {
520
+ const next = text[index + 1]
521
+ if (next !== undefined) {
522
+ current += next
523
+ tokenStarted = true
524
+ index += 1
525
+ continue
526
+ }
527
+ }
528
+
529
+ if (/\s/.test(character)) {
530
+ if (tokenStarted) {
531
+ tokens.push(current)
532
+ current = ''
533
+ tokenStarted = false
534
+ }
535
+ continue
536
+ }
537
+
538
+ current += character
539
+ tokenStarted = true
540
+ }
541
+
542
+ if (quote) {
543
+ const quoteName = quote === '"' ? 'double' : 'single'
544
+ throw new Error(`Unclosed ${quoteName} quote in "${text}"`)
545
+ }
546
+
547
+ if (tokenStarted) {
548
+ tokens.push(current)
549
+ }
550
+
551
+ return tokens
552
+ }
@@ -0,0 +1,141 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { buildHelpRuntimeContext, getCommandHelpModel, getGlobalHelpModel } from './help-model.js'
4
+ import { FLAG_DEFINITIONS } from './flags.js'
5
+ import { renderCommandHelp, renderGlobalHelp } from './help-renderer.js'
6
+ import { buildStrictHelpValidationContext, validateHelpConfig } from './help-schema.js'
7
+ import { getPackageVersion } from '../version.js'
8
+
9
+ const HIDDEN_COMMANDS = new Set(['help'])
10
+
11
+ function isPlainObject(value) {
12
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
13
+ }
14
+
15
+ function loadDefaultCommands() {
16
+ const configDir = process.env.DX_CONFIG_DIR || join(process.cwd(), 'dx', 'config')
17
+ const commandsPath = join(configDir, 'commands.json')
18
+
19
+ try {
20
+ return JSON.parse(readFileSync(commandsPath, 'utf8'))
21
+ } catch {
22
+ return null
23
+ }
24
+ }
25
+
26
+ function deriveRegisteredCommands(commands = {}) {
27
+ const ordered = []
28
+ const seen = new Set()
29
+ const sources = [
30
+ Array.isArray(commands?.help?.commandOrder) ? commands.help.commandOrder : [],
31
+ Object.keys(commands?.help?.commands || {}),
32
+ Object.keys(commands).filter(name => !HIDDEN_COMMANDS.has(name) && name !== 'help'),
33
+ ]
34
+
35
+ for (const entries of sources) {
36
+ for (const name of entries) {
37
+ if (typeof name !== 'string' || !name || seen.has(name)) continue
38
+ seen.add(name)
39
+ ordered.push(name)
40
+ }
41
+ }
42
+
43
+ return ordered
44
+ }
45
+
46
+ function buildSyntheticCliContext(commands) {
47
+ const commandHandlers = Object.fromEntries(
48
+ deriveRegisteredCommands(commands).map(name => [name, () => {}]),
49
+ )
50
+
51
+ return {
52
+ invocation: 'dx',
53
+ commands,
54
+ commandHandlers,
55
+ flagDefinitions: FLAG_DEFINITIONS,
56
+ }
57
+ }
58
+
59
+ function resolveCliContext(cliContext = null) {
60
+ if (isPlainObject(cliContext?.commands)) {
61
+ const runtimeContext = buildHelpRuntimeContext(cliContext)
62
+ validateHelpConfig(cliContext.commands, buildStrictHelpValidationContext(cliContext))
63
+ return {
64
+ invocation: cliContext.invocation || 'dx',
65
+ commands: cliContext.commands,
66
+ runtimeContext,
67
+ }
68
+ }
69
+
70
+ const commands = loadDefaultCommands()
71
+ if (!commands) return null
72
+
73
+ const syntheticCli = buildSyntheticCliContext(commands)
74
+ const runtimeContext = buildHelpRuntimeContext(syntheticCli)
75
+ validateHelpConfig(commands, buildStrictHelpValidationContext(syntheticCli))
76
+
77
+ return {
78
+ invocation: syntheticCli.invocation,
79
+ commands,
80
+ runtimeContext,
81
+ }
82
+ }
83
+
84
+ function hasRenderableCommandModel(model = {}) {
85
+ return Boolean(
86
+ model?.usage ||
87
+ model?.summary ||
88
+ model?.targets?.length ||
89
+ model?.notes?.length ||
90
+ model?.examples?.length ||
91
+ model?.options?.length,
92
+ )
93
+ }
94
+
95
+ function renderDynamicGlobalHelp(cliContext = null) {
96
+ const resolved = resolveCliContext(cliContext)
97
+ const version = getPackageVersion()
98
+ if (!resolved) {
99
+ return renderGlobalHelp({
100
+ title: `DX CLI v${version}`,
101
+ invocation: 'dx',
102
+ })
103
+ }
104
+
105
+ const model = getGlobalHelpModel(resolved.commands, resolved.runtimeContext)
106
+
107
+ return renderGlobalHelp({
108
+ title: `DX CLI v${version}`,
109
+ invocation: resolved.invocation,
110
+ ...model,
111
+ })
112
+ }
113
+
114
+ function renderDynamicCommandHelp(commandName, cliContext = null) {
115
+ const resolved = resolveCliContext(cliContext)
116
+ if (!resolved) return ''
117
+
118
+ const model = getCommandHelpModel(resolved.commands, commandName, resolved.runtimeContext)
119
+ if (!hasRenderableCommandModel(model)) return ''
120
+
121
+ return renderCommandHelp({
122
+ invocation: resolved.invocation,
123
+ ...model,
124
+ })
125
+ }
126
+
127
+ export function showHelp(cliContext = null) {
128
+ console.log(renderDynamicGlobalHelp(cliContext))
129
+ }
130
+
131
+ export function showCommandHelp(command, cliContext = null) {
132
+ const commandName = String(command || '').toLowerCase()
133
+ const dynamicOutput = renderDynamicCommandHelp(commandName, cliContext)
134
+
135
+ if (dynamicOutput) {
136
+ console.log(dynamicOutput)
137
+ return
138
+ }
139
+
140
+ showHelp(cliContext)
141
+ }
@@ -0,0 +1,4 @@
1
+ export { DxCli } from './dx-cli.js'
2
+ export { FLAG_DEFINITIONS, parseFlags } from './flags.js'
3
+ export { getCleanArgs, getPassthroughArgs } from './args.js'
4
+ export { showHelp, showCommandHelp } from './help.js'
@@ -0,0 +1,13 @@
1
+ export function appendNxVerboseFlag(command) {
2
+ const text = String(command || '').trim()
3
+ if (!text) return text
4
+ if (!/\bnx(?:\.js)?\b/.test(text)) return text
5
+ if (/(?:^|\s)--verbose(?:\s|$)/.test(text)) return text
6
+
7
+ const passthroughIndex = text.indexOf(' -- ')
8
+ if (passthroughIndex === -1) {
9
+ return `${text} --verbose`
10
+ }
11
+
12
+ return `${text.slice(0, passthroughIndex)} --verbose${text.slice(passthroughIndex)}`
13
+ }