@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.
- package/lib/cli/commands/core.js +88 -45
- package/lib/cli/commands/db.js +22 -32
- package/lib/cli/commands/export.js +7 -2
- package/lib/cli/commands/start.js +14 -33
- package/lib/cli/commands/worktree.js +0 -4
- package/lib/cli/dx-cli.js +46 -98
- package/lib/cli/flags.js +5 -7
- package/lib/cli/help-model.js +217 -0
- package/lib/cli/help-renderer.js +135 -0
- package/lib/cli/help-schema.js +549 -0
- package/lib/cli/help.js +114 -291
- package/lib/env.js +4 -5
- package/lib/worktree.js +37 -17
- package/package.json +1 -1
package/lib/cli/dx-cli.js
CHANGED
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
import { FLAG_DEFINITIONS, parseFlags } from './flags.js'
|
|
14
14
|
import { getCleanArgs, getCleanArgsWithConsumedValues } from './args.js'
|
|
15
15
|
import { showHelp, showCommandHelp } from './help.js'
|
|
16
|
+
import { buildStrictHelpValidationContext, validateHelpConfig } from './help-schema.js'
|
|
16
17
|
import { getPackageVersion } from '../version.js'
|
|
17
18
|
import {
|
|
18
19
|
handleHelp,
|
|
19
|
-
handleDev,
|
|
20
20
|
handleBuild,
|
|
21
21
|
handleTest,
|
|
22
22
|
handleLint,
|
|
@@ -51,7 +51,6 @@ class DxCli {
|
|
|
51
51
|
this.envCache = null
|
|
52
52
|
this.commandHandlers = {
|
|
53
53
|
help: args => handleHelp(this, args),
|
|
54
|
-
dev: args => handleDev(this, args),
|
|
55
54
|
start: args => handleStart(this, args),
|
|
56
55
|
build: args => handleBuild(this, args),
|
|
57
56
|
test: args => handleTest(this, args),
|
|
@@ -70,6 +69,7 @@ class DxCli {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
this.flagDefinitions = FLAG_DEFINITIONS
|
|
72
|
+
this.validateLoadedHelpConfig()
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// 加载命令配置
|
|
@@ -84,6 +84,10 @@ class DxCli {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
validateLoadedHelpConfig() {
|
|
88
|
+
validateHelpConfig(this.commands, buildStrictHelpValidationContext(this))
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
// 检测并安装依赖
|
|
88
92
|
async ensureDependencies() {
|
|
89
93
|
const nodeModulesPath = join(process.cwd(), 'node_modules')
|
|
@@ -172,22 +176,9 @@ class DxCli {
|
|
|
172
176
|
throw new Error('未找到 db.generate 命令配置,请检查 dx/config/commands.json')
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
const envKey = this.normalizeEnvKey(environment)
|
|
176
|
-
const execFlags = { ...this.flags }
|
|
177
|
-
;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
|
|
178
|
-
key => {
|
|
179
|
-
delete execFlags[key]
|
|
180
|
-
},
|
|
181
|
-
)
|
|
182
|
-
if (envKey === 'prod') execFlags.prod = true
|
|
183
|
-
else if (envKey === 'dev') execFlags.dev = true
|
|
184
|
-
else if (envKey === 'test') execFlags.test = true
|
|
185
|
-
else if (envKey === 'e2e') execFlags.e2e = true
|
|
186
|
-
else if (envKey === 'staging') execFlags.staging = true
|
|
187
|
-
|
|
188
179
|
await execManager.executeCommand(generateConfig.command, {
|
|
189
180
|
app: generateConfig.app || 'backend',
|
|
190
|
-
flags:
|
|
181
|
+
flags: this.createExecutionFlags(environment),
|
|
191
182
|
// Prisma generate 不应卡在环境变量校验上
|
|
192
183
|
skipEnvValidation: true,
|
|
193
184
|
})
|
|
@@ -286,9 +277,9 @@ class DxCli {
|
|
|
286
277
|
// 显示帮助
|
|
287
278
|
if (this.flags.help || !this.command) {
|
|
288
279
|
if (this.flags.help && this.command && this.command !== 'help') {
|
|
289
|
-
showCommandHelp(this.command)
|
|
280
|
+
showCommandHelp(this.command, this)
|
|
290
281
|
} else {
|
|
291
|
-
showHelp()
|
|
282
|
+
showHelp(this)
|
|
292
283
|
}
|
|
293
284
|
return
|
|
294
285
|
}
|
|
@@ -298,7 +289,7 @@ class DxCli {
|
|
|
298
289
|
// Fail fast for unknown commands before dependency/env startup checks.
|
|
299
290
|
if (this.command && !this.commandHandlers[this.command]) {
|
|
300
291
|
logger.error(`未知命令: ${this.command}`)
|
|
301
|
-
showHelp()
|
|
292
|
+
showHelp(this)
|
|
302
293
|
process.exit(1)
|
|
303
294
|
}
|
|
304
295
|
|
|
@@ -346,7 +337,7 @@ class DxCli {
|
|
|
346
337
|
const [, ...subArgs] = cleanArgs
|
|
347
338
|
|
|
348
339
|
if (!command) {
|
|
349
|
-
showHelp()
|
|
340
|
+
showHelp(this)
|
|
350
341
|
return
|
|
351
342
|
}
|
|
352
343
|
|
|
@@ -504,14 +495,14 @@ class DxCli {
|
|
|
504
495
|
case 'worktree': {
|
|
505
496
|
if (positionalArgs.length === 0) return
|
|
506
497
|
const action = positionalArgs[0]
|
|
507
|
-
if (
|
|
498
|
+
if (action === 'del') {
|
|
508
499
|
return
|
|
509
500
|
}
|
|
510
|
-
if (
|
|
501
|
+
if (action === 'make') {
|
|
511
502
|
ensureMax(3)
|
|
512
503
|
break
|
|
513
504
|
}
|
|
514
|
-
if (
|
|
505
|
+
if (action === 'list' || action === 'clean') {
|
|
515
506
|
ensureMax(1)
|
|
516
507
|
break
|
|
517
508
|
}
|
|
@@ -550,11 +541,8 @@ class DxCli {
|
|
|
550
541
|
const value = String(token).toLowerCase()
|
|
551
542
|
return (
|
|
552
543
|
value === 'dev' ||
|
|
553
|
-
value === 'development' ||
|
|
554
544
|
value === 'prod' ||
|
|
555
|
-
value === 'production' ||
|
|
556
545
|
value === 'staging' ||
|
|
557
|
-
value === 'stage' ||
|
|
558
546
|
value === 'test' ||
|
|
559
547
|
value === 'e2e'
|
|
560
548
|
)
|
|
@@ -570,18 +558,19 @@ class DxCli {
|
|
|
570
558
|
if (suggestion) {
|
|
571
559
|
logger.info(`建议命令: ${suggestion}`)
|
|
572
560
|
} else if (normalizedFlag) {
|
|
573
|
-
|
|
561
|
+
logger.info(`示例: ${this.invocation} ${command} ... ${normalizedFlag}`)
|
|
574
562
|
}
|
|
575
563
|
logger.info('未显式指定环境时将默认使用 --dev。')
|
|
576
564
|
process.exit(1)
|
|
577
565
|
}
|
|
578
566
|
|
|
579
567
|
getEnvironmentFlagExample(token) {
|
|
580
|
-
|
|
581
|
-
switch (key) {
|
|
568
|
+
switch (String(token || '').toLowerCase()) {
|
|
582
569
|
case 'dev':
|
|
570
|
+
case 'development':
|
|
583
571
|
return '--dev'
|
|
584
572
|
case 'prod':
|
|
573
|
+
case 'production':
|
|
585
574
|
return '--prod'
|
|
586
575
|
case 'staging':
|
|
587
576
|
return '--staging'
|
|
@@ -634,13 +623,13 @@ class DxCli {
|
|
|
634
623
|
}
|
|
635
624
|
|
|
636
625
|
validateStartPositionals(positionalArgs) {
|
|
637
|
-
const service = positionalArgs[0] || '
|
|
626
|
+
const service = positionalArgs[0] || 'development'
|
|
638
627
|
const environment = this.determineEnvironment()
|
|
639
628
|
const envKey = this.normalizeEnvKey(environment)
|
|
640
629
|
|
|
641
|
-
if (service === '
|
|
630
|
+
if (service === 'development' && envKey !== 'development') {
|
|
642
631
|
logger.error('dx start 在未指定服务时仅允许使用开发环境')
|
|
643
|
-
logger.info(`示例: ${this.invocation} start
|
|
632
|
+
logger.info(`示例: ${this.invocation} start --dev`)
|
|
644
633
|
logger.info(`示例: ${this.invocation} start backend --prod`)
|
|
645
634
|
process.exit(1)
|
|
646
635
|
}
|
|
@@ -649,7 +638,7 @@ class DxCli {
|
|
|
649
638
|
if (!startConfig) return
|
|
650
639
|
|
|
651
640
|
// 对 start 下的单层 command 配置,默认视为开发态目标,仅允许 --dev。
|
|
652
|
-
if (startConfig.command && envKey !== '
|
|
641
|
+
if (startConfig.command && envKey !== 'development') {
|
|
653
642
|
logger.error(`启动目标 ${service} 仅支持开发环境`)
|
|
654
643
|
logger.info(`示例: ${this.invocation} start ${service} --dev`)
|
|
655
644
|
process.exit(1)
|
|
@@ -669,13 +658,13 @@ class DxCli {
|
|
|
669
658
|
const buildConfig = this.commands?.build?.[target]
|
|
670
659
|
if (!buildConfig || buildConfig.command) return
|
|
671
660
|
|
|
672
|
-
const supportsCurrentEnv = Boolean(buildConfig[envKey]
|
|
661
|
+
const supportsCurrentEnv = Boolean(buildConfig[envKey])
|
|
673
662
|
if (supportsCurrentEnv) return
|
|
674
663
|
|
|
675
664
|
const envFlag = this.getEnvironmentFlagExample(envKey) || `--${envKey}`
|
|
676
665
|
logger.error(`构建目标 ${target} 不支持 ${envFlag} 环境`)
|
|
677
666
|
logger.info('显式传入环境标志时,必须是该 target 实际支持的环境。')
|
|
678
|
-
const available = ['
|
|
667
|
+
const available = ['development', 'staging', 'production', 'test', 'e2e']
|
|
679
668
|
.filter(key => key in buildConfig)
|
|
680
669
|
.map(key => this.getEnvironmentFlagExample(key) || `--${key}`)
|
|
681
670
|
if (available.length > 0) {
|
|
@@ -688,19 +677,6 @@ class DxCli {
|
|
|
688
677
|
process.exit(1)
|
|
689
678
|
}
|
|
690
679
|
|
|
691
|
-
reportDevCommandRemoved(args) {
|
|
692
|
-
const target = args?.[0]
|
|
693
|
-
logger.error('`dx dev` 命令已移除,统一使用 `dx start`。')
|
|
694
|
-
if (target) {
|
|
695
|
-
logger.info(`请执行: ${this.invocation} start ${target} --dev`)
|
|
696
|
-
} else {
|
|
697
|
-
logger.info(`示例: ${this.invocation} start backend --dev`)
|
|
698
|
-
logger.info(` ${this.invocation} start front --dev`)
|
|
699
|
-
logger.info(` ${this.invocation} start admin --dev`)
|
|
700
|
-
}
|
|
701
|
-
process.exit(1)
|
|
702
|
-
}
|
|
703
|
-
|
|
704
680
|
reportExtraPositionals(command, extras) {
|
|
705
681
|
const list = extras.join(', ')
|
|
706
682
|
if (command === '全局') {
|
|
@@ -772,7 +748,7 @@ class DxCli {
|
|
|
772
748
|
continue
|
|
773
749
|
}
|
|
774
750
|
commands.push({
|
|
775
|
-
command:
|
|
751
|
+
command: config.command,
|
|
776
752
|
options: {
|
|
777
753
|
app: config.app,
|
|
778
754
|
ports: config.ports,
|
|
@@ -820,22 +796,11 @@ class DxCli {
|
|
|
820
796
|
if (environment && config) {
|
|
821
797
|
const envKey = this.normalizeEnvKey(environment)
|
|
822
798
|
if (config[envKey]) config = config[envKey]
|
|
823
|
-
else if (envKey === 'staging' && config.prod) config = config.prod
|
|
824
799
|
}
|
|
825
800
|
|
|
826
801
|
return config
|
|
827
802
|
}
|
|
828
803
|
|
|
829
|
-
// SDK 构建命令当前不再暴露 --online/--offline 模式,保留该方法仅为兼容旧调用
|
|
830
|
-
applySdkModeFlags(command) {
|
|
831
|
-
return command
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// 向后兼容的别名
|
|
835
|
-
applySdkOfflineFlag(command) {
|
|
836
|
-
return command
|
|
837
|
-
}
|
|
838
|
-
|
|
839
804
|
collectStartPorts(service, startConfig, envKey) {
|
|
840
805
|
const portSet = new Set()
|
|
841
806
|
|
|
@@ -845,15 +810,6 @@ class DxCli {
|
|
|
845
810
|
})
|
|
846
811
|
}
|
|
847
812
|
|
|
848
|
-
if (envKey === 'dev') {
|
|
849
|
-
const legacyConfig = this.commands.dev?.[service]
|
|
850
|
-
if (legacyConfig && Array.isArray(legacyConfig.ports)) {
|
|
851
|
-
legacyConfig.ports.forEach(port => {
|
|
852
|
-
this.addPortToSet(portSet, port)
|
|
853
|
-
})
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
813
|
return Array.from(portSet)
|
|
858
814
|
}
|
|
859
815
|
|
|
@@ -936,28 +892,6 @@ class DxCli {
|
|
|
936
892
|
}
|
|
937
893
|
|
|
938
894
|
const rawCommand = String(config.command).trim()
|
|
939
|
-
// backward compat: old commands.json referenced scripts/lib/*.js in the project
|
|
940
|
-
if (rawCommand.startsWith('node scripts/lib/sdk-build.js')) {
|
|
941
|
-
const argsText = rawCommand.replace(/^node\s+scripts\/lib\/sdk-build\.js\s*/g, '')
|
|
942
|
-
const args = argsText ? argsText.split(/\s+/).filter(Boolean) : []
|
|
943
|
-
await withTempEnv(async () => {
|
|
944
|
-
const { runSdkBuild } = await import('../sdk-build.js')
|
|
945
|
-
await runSdkBuild(args)
|
|
946
|
-
})
|
|
947
|
-
return
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
if (rawCommand.startsWith('node scripts/lib/backend-package.js')) {
|
|
951
|
-
const argsText = rawCommand.replace(/^node\s+scripts\/lib\/backend-package\.js\s*/g, '')
|
|
952
|
-
const args = argsText ? argsText.split(/\s+/).filter(Boolean) : []
|
|
953
|
-
await withTempEnv(async () => {
|
|
954
|
-
const { runBackendPackage } = await import('../backend-package.js')
|
|
955
|
-
await runBackendPackage(args)
|
|
956
|
-
})
|
|
957
|
-
return
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
const command = this.applySdkOfflineFlag(rawCommand)
|
|
961
895
|
|
|
962
896
|
const options = {
|
|
963
897
|
app: config.app,
|
|
@@ -969,7 +903,7 @@ class DxCli {
|
|
|
969
903
|
forcePortCleanup: Boolean(config.forcePortCleanup),
|
|
970
904
|
}
|
|
971
905
|
|
|
972
|
-
await execManager.executeCommand(
|
|
906
|
+
await execManager.executeCommand(rawCommand, options)
|
|
973
907
|
}
|
|
974
908
|
|
|
975
909
|
// 确定环境
|
|
@@ -977,17 +911,14 @@ class DxCli {
|
|
|
977
911
|
return envManager.detectEnvironment(this.flags)
|
|
978
912
|
}
|
|
979
913
|
|
|
980
|
-
// 规范化环境键到命令配置使用的命名(
|
|
914
|
+
// 规范化环境键到命令配置使用的命名(development/staging/production/test/e2e)
|
|
981
915
|
normalizeEnvKey(env) {
|
|
982
916
|
switch (String(env || '').toLowerCase()) {
|
|
983
917
|
case 'development':
|
|
984
|
-
|
|
985
|
-
return 'dev'
|
|
918
|
+
return 'development'
|
|
986
919
|
case 'production':
|
|
987
|
-
|
|
988
|
-
return 'prod'
|
|
920
|
+
return 'production'
|
|
989
921
|
case 'staging':
|
|
990
|
-
case 'stage':
|
|
991
922
|
return 'staging'
|
|
992
923
|
case 'test':
|
|
993
924
|
return 'test'
|
|
@@ -998,6 +929,23 @@ class DxCli {
|
|
|
998
929
|
}
|
|
999
930
|
}
|
|
1000
931
|
|
|
932
|
+
createExecutionFlags(environment) {
|
|
933
|
+
const envKey = this.normalizeEnvKey(environment)
|
|
934
|
+
const execFlags = { ...this.flags }
|
|
935
|
+
|
|
936
|
+
;['dev', 'prod', 'staging', 'test', 'e2e'].forEach(key => {
|
|
937
|
+
delete execFlags[key]
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
if (envKey === 'production') execFlags.prod = true
|
|
941
|
+
else if (envKey === 'development') execFlags.dev = true
|
|
942
|
+
else if (envKey === 'test') execFlags.test = true
|
|
943
|
+
else if (envKey === 'e2e') execFlags.e2e = true
|
|
944
|
+
else if (envKey === 'staging') execFlags.staging = true
|
|
945
|
+
|
|
946
|
+
return execFlags
|
|
947
|
+
}
|
|
948
|
+
|
|
1001
949
|
}
|
|
1002
950
|
|
|
1003
951
|
export { DxCli }
|
package/lib/cli/flags.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
export const FLAG_DEFINITIONS = {
|
|
2
2
|
_global: [
|
|
3
3
|
{ flag: '--dev' },
|
|
4
|
-
{ flag: '--development' },
|
|
5
4
|
{ flag: '--prod' },
|
|
6
|
-
{ flag: '--production' },
|
|
7
5
|
{ flag: '--staging' },
|
|
8
|
-
{ flag: '--stage' },
|
|
9
6
|
{ flag: '--test' },
|
|
10
7
|
{ flag: '--e2e' },
|
|
11
8
|
{ flag: '--no-env-check' },
|
|
@@ -24,7 +21,11 @@ export const FLAG_DEFINITIONS = {
|
|
|
24
21
|
{ flag: '--name', expectsValue: true },
|
|
25
22
|
{ flag: '-n', expectsValue: true },
|
|
26
23
|
],
|
|
27
|
-
test: [
|
|
24
|
+
test: [
|
|
25
|
+
{ flag: '-t', expectsValue: true },
|
|
26
|
+
{ flag: '--name', expectsValue: true },
|
|
27
|
+
{ flag: '--test-name-pattern', expectsValue: true },
|
|
28
|
+
],
|
|
28
29
|
package: [
|
|
29
30
|
{ flag: '--skip-build' },
|
|
30
31
|
{ flag: '--keep-workdir' },
|
|
@@ -55,15 +56,12 @@ export function parseFlags(args = []) {
|
|
|
55
56
|
if (!flag.startsWith('-')) continue
|
|
56
57
|
switch (flag) {
|
|
57
58
|
case '--dev':
|
|
58
|
-
case '--development':
|
|
59
59
|
flags.dev = true
|
|
60
60
|
break
|
|
61
61
|
case '--prod':
|
|
62
|
-
case '--production':
|
|
63
62
|
flags.prod = true
|
|
64
63
|
break
|
|
65
64
|
case '--staging':
|
|
66
|
-
case '--stage':
|
|
67
65
|
flags.staging = true
|
|
68
66
|
break
|
|
69
67
|
case '--test':
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const ENVIRONMENT_KEYS = new Set(['development', 'production', 'staging', 'test', 'e2e'])
|
|
2
|
+
const COMMAND_LIST_HIDDEN = new Set(['help'])
|
|
3
|
+
const META_KEYS = new Set(['help', 'description', 'args', 'interactive', 'dangerous'])
|
|
4
|
+
const INTERNAL_CONFIG_KEYS = new Set([
|
|
5
|
+
'services',
|
|
6
|
+
'urls',
|
|
7
|
+
'preflight',
|
|
8
|
+
'ecosystemConfig',
|
|
9
|
+
'pm2Bin',
|
|
10
|
+
'backendDeploy',
|
|
11
|
+
'artifactDeploy',
|
|
12
|
+
'telegramWebhook',
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export function buildHelpRuntimeContext(cli = {}) {
|
|
16
|
+
return {
|
|
17
|
+
registeredCommands: getRegisteredCommands(cli),
|
|
18
|
+
knownFlags: getKnownFlags(cli),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getRegisteredCommands(cli = {}) {
|
|
23
|
+
const handlers = cli?.commandHandlers
|
|
24
|
+
if (!handlers || typeof handlers !== 'object') return []
|
|
25
|
+
|
|
26
|
+
return Object.keys(handlers).filter(name => !COMMAND_LIST_HIDDEN.has(name))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getKnownFlags(cli = {}) {
|
|
30
|
+
const definitions = cli?.flagDefinitions
|
|
31
|
+
const knownFlags = new Map()
|
|
32
|
+
|
|
33
|
+
if (!definitions || typeof definitions !== 'object') return knownFlags
|
|
34
|
+
|
|
35
|
+
for (const entries of Object.values(definitions)) {
|
|
36
|
+
if (!Array.isArray(entries)) continue
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (!entry?.flag) continue
|
|
39
|
+
knownFlags.set(entry.flag, {
|
|
40
|
+
expectsValue: Boolean(entry.expectsValue),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return knownFlags
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function classifyCommandNode(node = {}) {
|
|
49
|
+
if (node?.help?.nodeType) return node.help.nodeType
|
|
50
|
+
if (looksLikeInternalConfigBag(node)) return 'internal-config-bag'
|
|
51
|
+
if (node?.command || node?.internal) return 'target-leaf'
|
|
52
|
+
if (node?.concurrent || node?.sequential) return 'orchestration-node'
|
|
53
|
+
if (looksLikeEnvContainer(node)) return 'env-container'
|
|
54
|
+
if (looksLikeCategoryNode(node)) return 'category-node'
|
|
55
|
+
return 'unknown-node'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isVisibleHelpNode(name, node, nodeType = classifyCommandNode(node)) {
|
|
59
|
+
void name
|
|
60
|
+
|
|
61
|
+
if (node?.help?.expose === false) return false
|
|
62
|
+
if (nodeType === 'internal-config-bag') return false
|
|
63
|
+
if (nodeType === 'category-node') return false
|
|
64
|
+
if (nodeType === 'orchestration-node') return node?.help?.expose === true
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getGlobalHelpModel(commands = {}, context = {}) {
|
|
69
|
+
const registeredCommands = Array.isArray(context?.registeredCommands)
|
|
70
|
+
? context.registeredCommands
|
|
71
|
+
: []
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
summary: commands?.help?.summary ?? '',
|
|
75
|
+
commands: registeredCommands.map(name => getCommandHelpModel(commands, name, context)),
|
|
76
|
+
globalOptions: normalizeArray(commands?.help?.globalOptions),
|
|
77
|
+
examples: normalizeArray(commands?.help?.examples),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getCommandHelpModel(commands = {}, commandName, context = {}) {
|
|
82
|
+
const commandConfig = commands?.[commandName] ?? {}
|
|
83
|
+
const commandHelp = getCommandHelpConfig(commands, commandName)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
name: commandName,
|
|
87
|
+
summary: resolveSummary(commandConfig, commandHelp),
|
|
88
|
+
usage: resolveUsage(commandName, commandHelp, commandConfig),
|
|
89
|
+
args: normalizeArray(commandHelp?.args),
|
|
90
|
+
notes: normalizeArray(commandHelp?.notes),
|
|
91
|
+
examples: normalizeArray(commandHelp?.examples),
|
|
92
|
+
options: normalizeArray(commandHelp?.options),
|
|
93
|
+
targets: resolveVisibleTargets(commands, commandName, commandConfig, context),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function resolveSummary(node = {}, help = null) {
|
|
98
|
+
return help?.summary || node?.help?.summary || node?.description || ''
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveUsage(commandName, commandHelp = {}, commandConfig = {}) {
|
|
102
|
+
return commandHelp?.usage || generateUsage(commandName, commandConfig)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function generateUsage(commandName, commandConfig = {}) {
|
|
106
|
+
switch (commandName) {
|
|
107
|
+
case 'start':
|
|
108
|
+
return 'dx start <service> [环境标志]'
|
|
109
|
+
case 'build':
|
|
110
|
+
case 'package':
|
|
111
|
+
return `dx ${commandName} <target> [环境标志]`
|
|
112
|
+
case 'db':
|
|
113
|
+
return 'dx db <action> [name] [环境标志]'
|
|
114
|
+
case 'test':
|
|
115
|
+
return 'dx test [type] <target> [path]'
|
|
116
|
+
case 'deploy':
|
|
117
|
+
case 'clean':
|
|
118
|
+
case 'cache':
|
|
119
|
+
return `dx ${commandName} <target>`
|
|
120
|
+
case 'help':
|
|
121
|
+
return 'dx help [command]'
|
|
122
|
+
case 'worktree':
|
|
123
|
+
return 'dx worktree [action] [args...]'
|
|
124
|
+
case 'lint':
|
|
125
|
+
case 'status':
|
|
126
|
+
return `dx ${commandName}`
|
|
127
|
+
default:
|
|
128
|
+
return hasVisibleTargets(commandConfig) ? `dx ${commandName} <target>` : `dx ${commandName}`
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasVisibleTargets(commandConfig) {
|
|
133
|
+
if (!isPlainObject(commandConfig)) return false
|
|
134
|
+
|
|
135
|
+
return Object.entries(commandConfig).some(([name, node]) => {
|
|
136
|
+
if (name === 'help') return false
|
|
137
|
+
return isVisibleHelpNode(name, node)
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolveVisibleTargets(commands, commandName, commandConfig, context) {
|
|
142
|
+
void context
|
|
143
|
+
|
|
144
|
+
if (!isPlainObject(commandConfig)) return []
|
|
145
|
+
|
|
146
|
+
return Object.entries(commandConfig)
|
|
147
|
+
.filter(([name]) => name !== 'help')
|
|
148
|
+
.map(([name, node]) => {
|
|
149
|
+
const nodeType = classifyCommandNode(node)
|
|
150
|
+
if (!isVisibleHelpNode(name, node, nodeType)) return null
|
|
151
|
+
|
|
152
|
+
const targetHelp = getTargetHelpConfig(commands, commandName, name)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
name,
|
|
156
|
+
nodeType,
|
|
157
|
+
summary: resolveSummary(node, targetHelp),
|
|
158
|
+
notes: normalizeArray(targetHelp?.notes),
|
|
159
|
+
options: normalizeArray(targetHelp?.options),
|
|
160
|
+
examples: normalizeArray(targetHelp?.examples),
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.filter(Boolean)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getCommandHelpConfig(commands, commandName) {
|
|
167
|
+
const config = commands?.help?.commands?.[commandName]
|
|
168
|
+
return isPlainObject(config) ? config : {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getTargetHelpConfig(commands, commandName, targetName) {
|
|
172
|
+
const config = commands?.help?.targets?.[commandName]?.[targetName]
|
|
173
|
+
return isPlainObject(config) ? config : {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function looksLikeInternalConfigBag(node) {
|
|
177
|
+
if (!isPlainObject(node)) return false
|
|
178
|
+
if (node.command || node.internal || node.concurrent || node.sequential) return false
|
|
179
|
+
if (looksLikeEnvContainer(node)) return false
|
|
180
|
+
|
|
181
|
+
const visibleKeys = getVisibleKeys(node)
|
|
182
|
+
if (visibleKeys.length === 0) return false
|
|
183
|
+
|
|
184
|
+
return visibleKeys.some(key => INTERNAL_CONFIG_KEYS.has(key))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function looksLikeEnvContainer(node) {
|
|
188
|
+
if (!isPlainObject(node)) return false
|
|
189
|
+
const visibleKeys = getVisibleKeys(node)
|
|
190
|
+
if (visibleKeys.length === 0) return false
|
|
191
|
+
|
|
192
|
+
const envKeys = visibleKeys.filter(key => ENVIRONMENT_KEYS.has(key))
|
|
193
|
+
return envKeys.length > 0 && envKeys.length === visibleKeys.length
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function looksLikeCategoryNode(node) {
|
|
197
|
+
if (!isPlainObject(node)) return false
|
|
198
|
+
if (node.command || node.internal || node.concurrent || node.sequential) return false
|
|
199
|
+
if (looksLikeEnvContainer(node) || looksLikeInternalConfigBag(node)) return false
|
|
200
|
+
|
|
201
|
+
const visibleKeys = getVisibleKeys(node)
|
|
202
|
+
if (visibleKeys.length === 0) return false
|
|
203
|
+
|
|
204
|
+
return visibleKeys.every(key => isPlainObject(node[key]))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getVisibleKeys(node) {
|
|
208
|
+
return Object.keys(node).filter(key => !META_KEYS.has(key))
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function normalizeArray(value) {
|
|
212
|
+
return Array.isArray(value) ? value : []
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isPlainObject(value) {
|
|
216
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
217
|
+
}
|