@travetto/cli 8.0.0-alpha.9 → 8.0.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.
- package/README.md +111 -85
- package/__index__.ts +10 -9
- package/bin/trv.js +2 -1
- package/package.json +15 -15
- package/src/color.ts +1 -1
- package/src/execute.ts +3 -3
- package/src/help.ts +127 -74
- package/src/module.ts +9 -10
- package/src/parse.ts +57 -42
- package/src/registry/decorator.ts +37 -27
- package/src/registry/registry-adapter.ts +15 -25
- package/src/registry/registry-index.ts +24 -24
- package/src/schema-export.ts +25 -16
- package/src/schema.ts +15 -10
- package/src/scm.ts +20 -10
- package/src/service.ts +34 -33
- package/src/trv.d.ts +5 -5
- package/src/types.ts +7 -5
- package/src/util.ts +16 -18
- package/support/cli.cli_schema.ts +8 -7
- package/support/cli.main.ts +12 -8
- package/support/cli.service.ts +22 -16
- package/support/entry.trv.ts +2 -1
package/src/help.ts
CHANGED
|
@@ -1,72 +1,73 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
2
|
|
|
3
|
-
import { castKey, getClass, JSONUtil, Runtime } from '@travetto/runtime';
|
|
3
|
+
import { CodecUtil, castKey, getClass, JSONUtil, Runtime } from '@travetto/runtime';
|
|
4
4
|
import { SchemaRegistryIndex, ValidationResultError } from '@travetto/schema';
|
|
5
5
|
|
|
6
6
|
import { cliTpl } from './color.ts';
|
|
7
|
-
import type { CliCommandShape } from './types.ts';
|
|
8
7
|
import { CliCommandRegistryIndex, UNKNOWN_COMMAND } from './registry/registry-index.ts';
|
|
9
8
|
import { CliSchemaExportUtil } from './schema-export.ts';
|
|
9
|
+
import { type CliCommandShape, HELP_FLAG } from './types.ts';
|
|
10
10
|
|
|
11
|
-
const validationSourceMap: Record<string, string> = {
|
|
12
|
-
arg: 'Argument',
|
|
13
|
-
flag: 'Flag'
|
|
14
|
-
};
|
|
11
|
+
const validationSourceMap: Record<string, string> = { arg: 'Argument', flag: 'Flag' };
|
|
15
12
|
|
|
16
13
|
const ifDefined = <T>(value: T | null | '' | undefined): T | undefined =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]);
|
|
14
|
+
value === null || value === '' || value === undefined ? undefined : value;
|
|
15
|
+
|
|
16
|
+
const MODULE_TO_COMMAND = {
|
|
17
|
+
'@travetto/doc': ['doc'],
|
|
18
|
+
'@travetto/email-compiler': ['email:compile', 'email:test', 'email:editor'],
|
|
19
|
+
'@travetto/lint': ['lint', 'lint:register'],
|
|
20
|
+
'@travetto/model': ['model:install', 'model:export'],
|
|
21
|
+
'@travetto/openapi': ['openapi:spec', 'openapi:client'],
|
|
22
|
+
'@travetto/pack': ['pack', 'pack:zip', 'pack:docker'],
|
|
23
|
+
'@travetto/repo': ['repo:publish', 'repo:version', 'repo:exec', 'repo:list'],
|
|
24
|
+
'@travetto/test': ['test', 'test:watch', 'test:direct'],
|
|
25
|
+
'@travetto/web-http': ['web:http'],
|
|
26
|
+
'@travetto/web-rpc': ['web:rpc-client']
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const COMMAND_TO_MODULE = Object.fromEntries(Object.entries(MODULE_TO_COMMAND).flatMap(([k, v]) => v.map(sv => [sv, k])));
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
32
|
* Utilities for showing help
|
|
36
33
|
*/
|
|
37
34
|
export class HelpUtil {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
static renderUnknownCommandMessage(cmd: string): string {
|
|
41
|
-
const install = INSTALL_COMMANDS.get(cmd);
|
|
42
|
-
if (install) {
|
|
43
|
-
return cliTpl`
|
|
44
|
-
${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
|
|
45
|
-
${{ identifier: install }}
|
|
46
|
-
`;
|
|
47
|
-
} else {
|
|
48
|
-
return cliTpl`${{ subtitle: 'Unknown command' }}: ${{ input: cmd }}`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Render command-specific help
|
|
54
|
-
* @param command
|
|
55
|
-
*/
|
|
56
|
-
static async renderCommandHelp(command: CliCommandShape): Promise<string> {
|
|
35
|
+
/** Get usage help for a command */
|
|
36
|
+
static getUsageMessage(command: CliCommandShape): string[] {
|
|
57
37
|
const schema = SchemaRegistryIndex.getConfig(getClass(command));
|
|
58
38
|
const { name: commandName } = CliCommandRegistryIndex.get(getClass(command));
|
|
59
|
-
const args = schema.methods.main?.parameters ?? [];
|
|
60
39
|
|
|
61
|
-
|
|
40
|
+
const usage: string[] = [];
|
|
41
|
+
|
|
42
|
+
usage.push(cliTpl`${{ title: 'Usage:' }} ${{ param: commandName }} ${{ input: '[options]' }}`);
|
|
62
43
|
|
|
63
|
-
|
|
64
|
-
for (const field of
|
|
65
|
-
const type =
|
|
44
|
+
// Ensure finalized
|
|
45
|
+
for (const field of schema.methods.main?.parameters ?? []) {
|
|
46
|
+
const type =
|
|
47
|
+
field.type === String && field.enum && field.enum?.values.length <= 7
|
|
48
|
+
? field.enum?.values?.join('|')
|
|
49
|
+
: field.type.name.toLowerCase();
|
|
66
50
|
const arg = `${field.name}${field.array ? '...' : ''}:${type}`;
|
|
67
51
|
usage.push(cliTpl`${{ input: field.required?.active !== false ? `<${arg}>` : `[${arg}]` }}`);
|
|
68
52
|
}
|
|
69
53
|
|
|
54
|
+
return [usage.join(' '), ''];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Get description help for a command */
|
|
58
|
+
static getDescriptionMessage(command: CliCommandShape): string[] {
|
|
59
|
+
const schema = SchemaRegistryIndex.getConfig(getClass(command));
|
|
60
|
+
const description: string[] = [];
|
|
61
|
+
|
|
62
|
+
if (schema.description) {
|
|
63
|
+
description.push(cliTpl`${{ title: 'Description:' }}`, ...schema.description.split('\n').map(line => ` ${line}`), '');
|
|
64
|
+
}
|
|
65
|
+
return description;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Get options help for a command */
|
|
69
|
+
static getOptionsMessage(command: CliCommandShape): string[] {
|
|
70
|
+
const schema = SchemaRegistryIndex.getConfig(getClass(command));
|
|
70
71
|
const params: string[] = [];
|
|
71
72
|
const descriptions: string[] = [];
|
|
72
73
|
|
|
@@ -75,9 +76,7 @@ ${{ identifier: install }}
|
|
|
75
76
|
const defaultValue = ifDefined(command[key]) ?? ifDefined(field.default);
|
|
76
77
|
const aliases = (field.aliases ?? [])
|
|
77
78
|
.filter(flag => flag.startsWith('-'))
|
|
78
|
-
.filter(flag =>
|
|
79
|
-
(field.type !== Boolean) || ((defaultValue !== true || field.name === 'help') ? !flag.startsWith('--no-') : flag.startsWith('--'))
|
|
80
|
-
);
|
|
79
|
+
.filter(flag => field.type !== Boolean || (defaultValue !== true ? !flag.startsWith('--no-') : flag.startsWith('--')));
|
|
81
80
|
let type: string | undefined;
|
|
82
81
|
|
|
83
82
|
if (field.type === String && field.enum && field.enum.values.length <= 3) {
|
|
@@ -86,41 +85,93 @@ ${{ identifier: install }}
|
|
|
86
85
|
({ type } = CliSchemaExportUtil.baseInputType(field));
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
const
|
|
90
|
-
cliTpl`${{ param: aliases.join(', ') }}`,
|
|
91
|
-
...(type ? [cliTpl`${{ type: `<${type}>` }}`] : []),
|
|
92
|
-
];
|
|
88
|
+
const parameter = [cliTpl`${{ param: aliases.join(', ') }}`, ...(type ? [cliTpl`${{ type: `<${type}>` }}`] : [])];
|
|
93
89
|
|
|
94
|
-
params.push(
|
|
95
|
-
const
|
|
90
|
+
params.push(parameter.join(' '));
|
|
91
|
+
const parts = [cliTpl`${{ title: field.description }}`];
|
|
96
92
|
|
|
97
|
-
if (
|
|
98
|
-
|
|
93
|
+
if (defaultValue !== undefined) {
|
|
94
|
+
parts.push(cliTpl`(default: ${{ input: JSONUtil.toUTF8(defaultValue) }})`);
|
|
99
95
|
}
|
|
100
|
-
descriptions.push(
|
|
96
|
+
descriptions.push(parts.join(' '));
|
|
101
97
|
}
|
|
102
98
|
|
|
99
|
+
params.push(cliTpl`${{ param: HELP_FLAG }}`);
|
|
100
|
+
descriptions.push('display help for command');
|
|
101
|
+
|
|
103
102
|
const paramWidths = params.map(item => util.stripVTControlCharacters(item).length);
|
|
104
103
|
const descWidths = descriptions.map(item => util.stripVTControlCharacters(item).length);
|
|
105
104
|
|
|
106
105
|
const paramWidth = Math.max(...paramWidths);
|
|
107
106
|
const descWidth = Math.max(...descWidths);
|
|
108
107
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
const options: string[] = [
|
|
109
|
+
cliTpl`${{ title: 'Options:' }}`,
|
|
110
|
+
...params.map(
|
|
111
|
+
(_, i) =>
|
|
112
|
+
` ${params[i]}${' '.repeat(paramWidth - paramWidths[i])} ${descriptions[i].padEnd(descWidth)}${' '.repeat(descWidth - descWidths[i])}`
|
|
113
|
+
),
|
|
114
|
+
''
|
|
115
|
+
];
|
|
116
|
+
return options;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Get extended help for a command */
|
|
120
|
+
static async getExtendedHelpMessage(command: CliCommandShape): Promise<string[]> {
|
|
121
|
+
const extendedHelpText = await (command.help?.() ?? []);
|
|
122
|
+
if (extendedHelpText.length && extendedHelpText.at(-1) !== '') {
|
|
123
|
+
extendedHelpText.push('');
|
|
124
|
+
}
|
|
125
|
+
return extendedHelpText;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Get examples for a command */
|
|
129
|
+
static getExamplesMessage(command: CliCommandShape): string[] {
|
|
130
|
+
const schema = SchemaRegistryIndex.getConfig(getClass(command));
|
|
131
|
+
const examples: string[] = [];
|
|
132
|
+
if (schema.examples) {
|
|
133
|
+
examples.push(cliTpl`${{ title: 'Examples:' }}`);
|
|
134
|
+
for (const example of schema.examples) {
|
|
135
|
+
for (const line of example.split('\n')) {
|
|
136
|
+
examples.push(
|
|
137
|
+
line.trim().startsWith('>')
|
|
138
|
+
? cliTpl` ${{ input: line.substring(line.indexOf('> ') + 2).trim() }}`
|
|
139
|
+
: cliTpl` ${{ subtitle: line.trim() }}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
examples.push('');
|
|
144
|
+
}
|
|
145
|
+
return examples;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Render the unknown command message */
|
|
149
|
+
static renderUnknownCommandMessage(command: string): string {
|
|
150
|
+
const module = COMMAND_TO_MODULE[command];
|
|
151
|
+
if (module) {
|
|
152
|
+
return cliTpl`
|
|
153
|
+
${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: command }} please run:\n
|
|
154
|
+
${{ identifier: Runtime.getInstallCommand(module) }}
|
|
155
|
+
`;
|
|
156
|
+
} else {
|
|
157
|
+
return cliTpl`${{ subtitle: 'Unknown command' }}: ${{ input: command }}`;
|
|
112
158
|
}
|
|
159
|
+
}
|
|
113
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Render command-specific help
|
|
163
|
+
* @param command
|
|
164
|
+
*/
|
|
165
|
+
static async renderCommandHelp(command: CliCommandShape): Promise<string> {
|
|
114
166
|
return [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
...
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
].map(line => line.trimEnd()).join('\n');
|
|
167
|
+
...this.getUsageMessage(command),
|
|
168
|
+
...this.getDescriptionMessage(command),
|
|
169
|
+
...this.getOptionsMessage(command),
|
|
170
|
+
...(await this.getExtendedHelpMessage(command)),
|
|
171
|
+
...this.getExamplesMessage(command)
|
|
172
|
+
]
|
|
173
|
+
.map(line => line.trimEnd())
|
|
174
|
+
.join('\n');
|
|
124
175
|
}
|
|
125
176
|
|
|
126
177
|
/**
|
|
@@ -136,11 +187,13 @@ ${{ identifier: install }}
|
|
|
136
187
|
for (const { command: cmd, schema } of resolved) {
|
|
137
188
|
try {
|
|
138
189
|
if (schema && !schema.private) {
|
|
139
|
-
|
|
190
|
+
const description = CodecUtil.readFirstLine(schema.description, '');
|
|
191
|
+
rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ title: description }}`);
|
|
140
192
|
}
|
|
141
193
|
} catch (error) {
|
|
142
194
|
if (error instanceof Error) {
|
|
143
|
-
|
|
195
|
+
const failure = CodecUtil.readFirstLine(error.message);
|
|
196
|
+
rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ failure }}`);
|
|
144
197
|
} else {
|
|
145
198
|
throw error;
|
|
146
199
|
}
|
|
@@ -166,7 +219,7 @@ ${{ identifier: install }}
|
|
|
166
219
|
}
|
|
167
220
|
return cliTpl` * ${{ failure: error.message }}`;
|
|
168
221
|
}),
|
|
169
|
-
''
|
|
222
|
+
''
|
|
170
223
|
].join('\n');
|
|
171
224
|
}
|
|
172
225
|
|
|
@@ -185,4 +238,4 @@ ${{ identifier: install }}
|
|
|
185
238
|
}
|
|
186
239
|
console.error!();
|
|
187
240
|
}
|
|
188
|
-
}
|
|
241
|
+
}
|
package/src/module.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
2
1
|
import type { IndexedModule } from '@travetto/manifest';
|
|
2
|
+
import { Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import { CliScmUtil } from './scm.ts';
|
|
5
5
|
|
|
6
|
-
type ModuleGraphEntry = { children: Set<string
|
|
6
|
+
type ModuleGraphEntry = { children: Set<string>; name: string; active: Set<string>; parents?: string[] };
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Simple utilities for understanding modules for CLI use cases
|
|
10
10
|
*/
|
|
11
11
|
export class CliModuleUtil {
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* Find modules that changed, and the dependent modules
|
|
15
14
|
* @param fromHash
|
|
@@ -34,8 +33,7 @@ export class CliModuleUtil {
|
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
return [...out.values()]
|
|
38
|
-
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
36
|
+
return [...out.values()].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/**
|
|
@@ -45,9 +43,10 @@ export class CliModuleUtil {
|
|
|
45
43
|
* @returns
|
|
46
44
|
*/
|
|
47
45
|
static async findModules(mode: 'all' | 'changed' | 'workspace', fromHash?: string, toHash?: string): Promise<IndexedModule[]> {
|
|
48
|
-
return (
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
return (
|
|
47
|
+
mode === 'changed'
|
|
48
|
+
? await this.findChangedModulesRecursive(fromHash, toHash, true)
|
|
49
|
+
: [...RuntimeIndex.getModuleList(mode)].map(name => RuntimeIndex.getModule(name)!)
|
|
51
50
|
).filter(module => module.sourcePath !== Runtime.workspace.path);
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -103,7 +102,7 @@ export class CliModuleUtil {
|
|
|
103
102
|
/**
|
|
104
103
|
* Find changed paths, either files between two git commits, or all folders for changed modules
|
|
105
104
|
*/
|
|
106
|
-
static async findChangedPaths(config: { since?: string
|
|
105
|
+
static async findChangedPaths(config: { since?: string; changed?: boolean; logError?: boolean } = {}): Promise<string[]> {
|
|
107
106
|
if (config.since) {
|
|
108
107
|
try {
|
|
109
108
|
const files = await CliScmUtil.findChangedFiles(config.since, 'HEAD');
|
|
@@ -119,4 +118,4 @@ export class CliModuleUtil {
|
|
|
119
118
|
return modules.map(module => module.sourcePath);
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
|
-
}
|
|
121
|
+
}
|
package/src/parse.ts
CHANGED
|
@@ -4,13 +4,12 @@ import path from 'node:path';
|
|
|
4
4
|
import { Runtime } from '@travetto/runtime';
|
|
5
5
|
import type { SchemaClassConfig, SchemaFieldConfig, SchemaInputConfig } from '@travetto/schema';
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { HELP_FLAG, type ParsedState } from './types.ts';
|
|
8
8
|
|
|
9
9
|
type ParsedInput = ParsedState['all'][number];
|
|
10
10
|
|
|
11
11
|
const RAW_SEPARATOR = '--';
|
|
12
12
|
const VALID_FLAG = /^-{1,2}[a-z]/i;
|
|
13
|
-
const HELP_FLAG = /^(-h|--help)$/;
|
|
14
13
|
const LONG_FLAG_WITH_EQ = /^--[a-z][^= ]+=\S+/i;
|
|
15
14
|
const CONFIG_PREFIX = '+=';
|
|
16
15
|
const SPACE = new Set([32, 7, 13, 10]);
|
|
@@ -26,32 +25,35 @@ const STATE_SYMBOL = Symbol();
|
|
|
26
25
|
* Parsing support for the cli
|
|
27
26
|
*/
|
|
28
27
|
export class CliParseUtil {
|
|
29
|
-
|
|
30
28
|
static toEnvField(key: string): string {
|
|
31
29
|
return key.startsWith(ENV_PREFIX) ? key : `${ENV_PREFIX}${key}`;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
static readToken(text: string, start = 0): { next: number
|
|
32
|
+
static readToken(text: string, start = 0): { next: number; value?: string } {
|
|
35
33
|
const collected: number[] = [];
|
|
36
34
|
let i = start;
|
|
37
35
|
let done = false;
|
|
38
36
|
let quote: number | undefined;
|
|
39
37
|
let escaped = false;
|
|
40
|
-
|
|
38
|
+
for (; i < text.length; i += 1) {
|
|
41
39
|
const ch = text.charCodeAt(i);
|
|
42
40
|
const space = SPACE.has(ch);
|
|
43
41
|
if (escaped) {
|
|
44
42
|
escaped = false;
|
|
45
43
|
collected.push(ch);
|
|
46
44
|
} else if (done && !space) {
|
|
47
|
-
break
|
|
45
|
+
break;
|
|
48
46
|
} else if (!quote && space) {
|
|
49
47
|
done = true;
|
|
50
48
|
} else {
|
|
51
49
|
switch (ch) {
|
|
52
|
-
case 92:
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
case 92:
|
|
51
|
+
/* Backslash */ escaped = true;
|
|
52
|
+
break;
|
|
53
|
+
case 39: /* Single quote */
|
|
54
|
+
case 34 /* Double quote */:
|
|
55
|
+
if (quote === ch) {
|
|
56
|
+
// End quote
|
|
55
57
|
quote = undefined;
|
|
56
58
|
} else if (!quote) {
|
|
57
59
|
quote = ch;
|
|
@@ -59,7 +61,8 @@ export class CliParseUtil {
|
|
|
59
61
|
collected.push(ch);
|
|
60
62
|
}
|
|
61
63
|
break;
|
|
62
|
-
default:
|
|
64
|
+
default:
|
|
65
|
+
collected.push(ch);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -74,10 +77,9 @@ export class CliParseUtil {
|
|
|
74
77
|
const input = Object.values(schema.fields).find(config => config.specifiers?.includes('module'));
|
|
75
78
|
const envKey = input?.aliases?.filter(alias => alias.startsWith(ENV_PREFIX)).map(alias => alias.replace(ENV_PREFIX, ''))[0] ?? '';
|
|
76
79
|
const flags = new Set(input?.aliases ?? []);
|
|
77
|
-
const check = (key?: string, value?: string): string | undefined => flags.has(key!) ? value : undefined;
|
|
80
|
+
const check = (key?: string, value?: string): string | undefined => (flags.has(key!) ? value : undefined);
|
|
78
81
|
return args.reduce(
|
|
79
|
-
(name, value, i, values) =>
|
|
80
|
-
(i < separatorIndex ? check(values[i - 1], value) ?? check(...value.split('=')) : undefined) ?? name,
|
|
82
|
+
(name, value, i, values) => (i < separatorIndex ? (check(values[i - 1], value) ?? check(...value.split('='))) : undefined) ?? name,
|
|
81
83
|
process.env[envKey]
|
|
82
84
|
);
|
|
83
85
|
}
|
|
@@ -90,18 +92,20 @@ export class CliParseUtil {
|
|
|
90
92
|
const overrides = { '@': module ?? Runtime.main.name };
|
|
91
93
|
|
|
92
94
|
// We have a file
|
|
93
|
-
const relativePath = (key.includes('/') ? key : `@#support/pack.${key}.flags`)
|
|
94
|
-
|
|
95
|
+
const relativePath = (key.includes('/') ? key : `@#support/pack.${key}.flags`).replace(
|
|
96
|
+
/^(@[^#]*)#(.*)$/,
|
|
97
|
+
(_, imp, rest) => `${Runtime.modulePath(imp, overrides)}/${rest}`
|
|
98
|
+
);
|
|
95
99
|
|
|
96
100
|
const file = path.resolve(relativePath);
|
|
97
101
|
|
|
98
|
-
if (!await fs.stat(file, { throwIfNoEntry: false })) {
|
|
102
|
+
if (!(await fs.stat(file, { throwIfNoEntry: false }))) {
|
|
99
103
|
throw new Error(`Missing flag file: ${key}, unable to proceed`);
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
const data = await fs.readFile(file, 'utf8');
|
|
103
107
|
const args: string[] = [];
|
|
104
|
-
let token: { next: number
|
|
108
|
+
let token: { next: number; value?: string } = { next: 0 };
|
|
105
109
|
while (token.next < data.length) {
|
|
106
110
|
token = this.readToken(data, token.next);
|
|
107
111
|
if (token.value !== undefined) {
|
|
@@ -115,7 +119,7 @@ export class CliParseUtil {
|
|
|
115
119
|
* Parse args to extract command from argv along with other params. Will skip
|
|
116
120
|
* argv[0] and argv[1] if equal to process.argv[0:2]
|
|
117
121
|
*/
|
|
118
|
-
static getArgs(argv: string[]): { cmd?: string
|
|
122
|
+
static getArgs(argv: string[]): { cmd?: string; args: string[]; help?: boolean } {
|
|
119
123
|
let offset = 0;
|
|
120
124
|
if (argv[0] === process.argv[0] && argv[1] === process.argv[1]) {
|
|
121
125
|
offset = 2;
|
|
@@ -124,9 +128,9 @@ export class CliParseUtil {
|
|
|
124
128
|
const max = out.includes(RAW_SEPARATOR) ? out.indexOf(RAW_SEPARATOR) : out.length;
|
|
125
129
|
const valid = out.slice(0, max);
|
|
126
130
|
const cmd = valid.length > 0 && !valid[0].startsWith('-') ? valid[0] : undefined;
|
|
127
|
-
const
|
|
131
|
+
const help = valid.includes(HELP_FLAG);
|
|
128
132
|
const args = out.slice(cmd ? 1 : 0);
|
|
129
|
-
const result = { cmd, args, help
|
|
133
|
+
const result = { cmd, args, help };
|
|
130
134
|
return result;
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -136,11 +140,12 @@ export class CliParseUtil {
|
|
|
136
140
|
static async expandArgs(schema: SchemaClassConfig, args: string[]): Promise<string[]> {
|
|
137
141
|
const separatorIndex = args.includes(RAW_SEPARATOR) ? args.indexOf(RAW_SEPARATOR) : args.length;
|
|
138
142
|
const module = this.getSpecifiedModule(schema, args);
|
|
139
|
-
return Promise
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
return Promise.all(
|
|
144
|
+
args.map(
|
|
145
|
+
async (arg, i) =>
|
|
146
|
+
await (arg.startsWith(CONFIG_PREFIX) && (i < separatorIndex || separatorIndex < 0) ? this.readFlagFile(arg, module) : arg)
|
|
142
147
|
)
|
|
143
|
-
|
|
148
|
+
).then(expanded => expanded.flat());
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
/**
|
|
@@ -160,7 +165,9 @@ export class CliParseUtil {
|
|
|
160
165
|
if (simple in process.env) {
|
|
161
166
|
const value: string = process.env[simple]!;
|
|
162
167
|
if (field.array) {
|
|
163
|
-
out.push(
|
|
168
|
+
out.push(
|
|
169
|
+
...value.split(/\s*,\s*/g).map(item => ({ type: 'flag', fieldName: field.name, input: envName, value: item }) as const)
|
|
170
|
+
);
|
|
164
171
|
} else {
|
|
165
172
|
out.push({ type: 'flag', fieldName: field.name, input: envName, value });
|
|
166
173
|
}
|
|
@@ -173,7 +180,8 @@ export class CliParseUtil {
|
|
|
173
180
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
174
181
|
const input = inputs[i];
|
|
175
182
|
|
|
176
|
-
if (input === RAW_SEPARATOR) {
|
|
183
|
+
if (input === RAW_SEPARATOR) {
|
|
184
|
+
// Raw separator
|
|
177
185
|
out.push(...inputs.slice(i + 1).map((arg, idx) => ({ type: 'unknown', input: arg, index: argIdx + idx }) as const));
|
|
178
186
|
break;
|
|
179
187
|
} else if (LONG_FLAG_WITH_EQ.test(input)) {
|
|
@@ -184,7 +192,8 @@ export class CliParseUtil {
|
|
|
184
192
|
} else {
|
|
185
193
|
out.push({ type: 'unknown', input });
|
|
186
194
|
}
|
|
187
|
-
} else if (VALID_FLAG.test(input)) {
|
|
195
|
+
} else if (VALID_FLAG.test(input)) {
|
|
196
|
+
// Flag
|
|
188
197
|
const field = flagMap.get(input);
|
|
189
198
|
if (!field) {
|
|
190
199
|
out.push({ type: 'unknown', input });
|
|
@@ -225,27 +234,33 @@ export class CliParseUtil {
|
|
|
225
234
|
* Parse aliases into categories for registration
|
|
226
235
|
*/
|
|
227
236
|
static parseAliases(aliases: string[]): AliasesParseResult {
|
|
228
|
-
return aliases.reduce<AliasesParseResult>(
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
237
|
+
return aliases.reduce<AliasesParseResult>(
|
|
238
|
+
(result, alias) => {
|
|
239
|
+
if (VALID_FLAG.test(alias)) {
|
|
240
|
+
if (alias.startsWith('--')) {
|
|
241
|
+
result.long.push(alias);
|
|
242
|
+
} else {
|
|
243
|
+
result.short.push(alias);
|
|
244
|
+
}
|
|
245
|
+
} else if (alias.startsWith(ENV_PREFIX)) {
|
|
246
|
+
result.env.push(alias);
|
|
232
247
|
} else {
|
|
233
|
-
result.
|
|
248
|
+
result.raw.push(alias);
|
|
234
249
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
return result;
|
|
241
|
-
}, { long: [], short: [], raw: [], env: [] });
|
|
250
|
+
return result;
|
|
251
|
+
},
|
|
252
|
+
{ long: [], short: [], raw: [], env: [] }
|
|
253
|
+
);
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
/**
|
|
245
257
|
* Build aliases for a schema config
|
|
246
258
|
*/
|
|
247
|
-
static buildAliases(
|
|
248
|
-
|
|
259
|
+
static buildAliases(
|
|
260
|
+
config: { full?: string; short?: string; envVars?: string[] },
|
|
261
|
+
...extraEnvVars: string[]
|
|
262
|
+
): Partial<SchemaFieldConfig> {
|
|
263
|
+
const envVars = [...(config.envVars ?? []), ...extraEnvVars];
|
|
249
264
|
return {
|
|
250
265
|
aliases: [
|
|
251
266
|
...(config.full ? [config.full.startsWith('-') ? config.full : `--${config.full}`] : []),
|
|
@@ -270,4 +285,4 @@ export class CliParseUtil {
|
|
|
270
285
|
const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
|
|
271
286
|
local[STATE_SYMBOL] = state;
|
|
272
287
|
}
|
|
273
|
-
}
|
|
288
|
+
}
|