@travetto/cli 8.0.0-alpha.27 → 8.0.0-alpha.29
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 +20 -26
- package/__index__.ts +10 -9
- package/bin/trv.js +2 -1
- package/package.json +3 -3
- package/src/color.ts +1 -1
- package/src/execute.ts +2 -3
- package/src/help.ts +25 -27
- package/src/module.ts +9 -10
- package/src/parse.ts +54 -38
- package/src/registry/decorator.ts +36 -26
- package/src/registry/registry-adapter.ts +13 -11
- package/src/registry/registry-index.ts +24 -24
- package/src/schema-export.ts +24 -15
- package/src/schema.ts +15 -10
- package/src/scm.ts +19 -10
- package/src/service.ts +34 -33
- package/src/types.ts +5 -5
- package/src/util.ts +16 -18
- package/support/cli.cli_schema.ts +4 -6
- package/support/cli.main.ts +8 -7
- package/support/cli.service.ts +18 -15
- package/support/entry.trv.ts +2 -1
package/src/parse.ts
CHANGED
|
@@ -25,32 +25,35 @@ const STATE_SYMBOL = Symbol();
|
|
|
25
25
|
* Parsing support for the cli
|
|
26
26
|
*/
|
|
27
27
|
export class CliParseUtil {
|
|
28
|
-
|
|
29
28
|
static toEnvField(key: string): string {
|
|
30
29
|
return key.startsWith(ENV_PREFIX) ? key : `${ENV_PREFIX}${key}`;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
static readToken(text: string, start = 0): { next: number
|
|
32
|
+
static readToken(text: string, start = 0): { next: number; value?: string } {
|
|
34
33
|
const collected: number[] = [];
|
|
35
34
|
let i = start;
|
|
36
35
|
let done = false;
|
|
37
36
|
let quote: number | undefined;
|
|
38
37
|
let escaped = false;
|
|
39
|
-
|
|
38
|
+
for (; i < text.length; i += 1) {
|
|
40
39
|
const ch = text.charCodeAt(i);
|
|
41
40
|
const space = SPACE.has(ch);
|
|
42
41
|
if (escaped) {
|
|
43
42
|
escaped = false;
|
|
44
43
|
collected.push(ch);
|
|
45
44
|
} else if (done && !space) {
|
|
46
|
-
break
|
|
45
|
+
break;
|
|
47
46
|
} else if (!quote && space) {
|
|
48
47
|
done = true;
|
|
49
48
|
} else {
|
|
50
49
|
switch (ch) {
|
|
51
|
-
case 92:
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
54
57
|
quote = undefined;
|
|
55
58
|
} else if (!quote) {
|
|
56
59
|
quote = ch;
|
|
@@ -58,7 +61,8 @@ export class CliParseUtil {
|
|
|
58
61
|
collected.push(ch);
|
|
59
62
|
}
|
|
60
63
|
break;
|
|
61
|
-
default:
|
|
64
|
+
default:
|
|
65
|
+
collected.push(ch);
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
}
|
|
@@ -73,10 +77,9 @@ export class CliParseUtil {
|
|
|
73
77
|
const input = Object.values(schema.fields).find(config => config.specifiers?.includes('module'));
|
|
74
78
|
const envKey = input?.aliases?.filter(alias => alias.startsWith(ENV_PREFIX)).map(alias => alias.replace(ENV_PREFIX, ''))[0] ?? '';
|
|
75
79
|
const flags = new Set(input?.aliases ?? []);
|
|
76
|
-
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);
|
|
77
81
|
return args.reduce(
|
|
78
|
-
(name, value, i, values) =>
|
|
79
|
-
(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,
|
|
80
83
|
process.env[envKey]
|
|
81
84
|
);
|
|
82
85
|
}
|
|
@@ -89,18 +92,20 @@ export class CliParseUtil {
|
|
|
89
92
|
const overrides = { '@': module ?? Runtime.main.name };
|
|
90
93
|
|
|
91
94
|
// We have a file
|
|
92
|
-
const relativePath = (key.includes('/') ? key : `@#support/pack.${key}.flags`)
|
|
93
|
-
|
|
95
|
+
const relativePath = (key.includes('/') ? key : `@#support/pack.${key}.flags`).replace(
|
|
96
|
+
/^(@[^#]*)#(.*)$/,
|
|
97
|
+
(_, imp, rest) => `${Runtime.modulePath(imp, overrides)}/${rest}`
|
|
98
|
+
);
|
|
94
99
|
|
|
95
100
|
const file = path.resolve(relativePath);
|
|
96
101
|
|
|
97
|
-
if (!await fs.stat(file, { throwIfNoEntry: false })) {
|
|
102
|
+
if (!(await fs.stat(file, { throwIfNoEntry: false }))) {
|
|
98
103
|
throw new Error(`Missing flag file: ${key}, unable to proceed`);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
const data = await fs.readFile(file, 'utf8');
|
|
102
107
|
const args: string[] = [];
|
|
103
|
-
let token: { next: number
|
|
108
|
+
let token: { next: number; value?: string } = { next: 0 };
|
|
104
109
|
while (token.next < data.length) {
|
|
105
110
|
token = this.readToken(data, token.next);
|
|
106
111
|
if (token.value !== undefined) {
|
|
@@ -114,7 +119,7 @@ export class CliParseUtil {
|
|
|
114
119
|
* Parse args to extract command from argv along with other params. Will skip
|
|
115
120
|
* argv[0] and argv[1] if equal to process.argv[0:2]
|
|
116
121
|
*/
|
|
117
|
-
static getArgs(argv: string[]): { cmd?: string
|
|
122
|
+
static getArgs(argv: string[]): { cmd?: string; args: string[]; help?: boolean } {
|
|
118
123
|
let offset = 0;
|
|
119
124
|
if (argv[0] === process.argv[0] && argv[1] === process.argv[1]) {
|
|
120
125
|
offset = 2;
|
|
@@ -135,11 +140,12 @@ export class CliParseUtil {
|
|
|
135
140
|
static async expandArgs(schema: SchemaClassConfig, args: string[]): Promise<string[]> {
|
|
136
141
|
const separatorIndex = args.includes(RAW_SEPARATOR) ? args.indexOf(RAW_SEPARATOR) : args.length;
|
|
137
142
|
const module = this.getSpecifiedModule(schema, args);
|
|
138
|
-
return Promise
|
|
139
|
-
|
|
140
|
-
|
|
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)
|
|
141
147
|
)
|
|
142
|
-
|
|
148
|
+
).then(expanded => expanded.flat());
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
/**
|
|
@@ -159,7 +165,9 @@ export class CliParseUtil {
|
|
|
159
165
|
if (simple in process.env) {
|
|
160
166
|
const value: string = process.env[simple]!;
|
|
161
167
|
if (field.array) {
|
|
162
|
-
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
|
+
);
|
|
163
171
|
} else {
|
|
164
172
|
out.push({ type: 'flag', fieldName: field.name, input: envName, value });
|
|
165
173
|
}
|
|
@@ -172,7 +180,8 @@ export class CliParseUtil {
|
|
|
172
180
|
for (let i = 0; i < inputs.length; i += 1) {
|
|
173
181
|
const input = inputs[i];
|
|
174
182
|
|
|
175
|
-
if (input === RAW_SEPARATOR) {
|
|
183
|
+
if (input === RAW_SEPARATOR) {
|
|
184
|
+
// Raw separator
|
|
176
185
|
out.push(...inputs.slice(i + 1).map((arg, idx) => ({ type: 'unknown', input: arg, index: argIdx + idx }) as const));
|
|
177
186
|
break;
|
|
178
187
|
} else if (LONG_FLAG_WITH_EQ.test(input)) {
|
|
@@ -183,7 +192,8 @@ export class CliParseUtil {
|
|
|
183
192
|
} else {
|
|
184
193
|
out.push({ type: 'unknown', input });
|
|
185
194
|
}
|
|
186
|
-
} else if (VALID_FLAG.test(input)) {
|
|
195
|
+
} else if (VALID_FLAG.test(input)) {
|
|
196
|
+
// Flag
|
|
187
197
|
const field = flagMap.get(input);
|
|
188
198
|
if (!field) {
|
|
189
199
|
out.push({ type: 'unknown', input });
|
|
@@ -224,27 +234,33 @@ export class CliParseUtil {
|
|
|
224
234
|
* Parse aliases into categories for registration
|
|
225
235
|
*/
|
|
226
236
|
static parseAliases(aliases: string[]): AliasesParseResult {
|
|
227
|
-
return aliases.reduce<AliasesParseResult>(
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
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);
|
|
231
247
|
} else {
|
|
232
|
-
result.
|
|
248
|
+
result.raw.push(alias);
|
|
233
249
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
return result;
|
|
240
|
-
}, { long: [], short: [], raw: [], env: [] });
|
|
250
|
+
return result;
|
|
251
|
+
},
|
|
252
|
+
{ long: [], short: [], raw: [], env: [] }
|
|
253
|
+
);
|
|
241
254
|
}
|
|
242
255
|
|
|
243
256
|
/**
|
|
244
257
|
* Build aliases for a schema config
|
|
245
258
|
*/
|
|
246
|
-
static buildAliases(
|
|
247
|
-
|
|
259
|
+
static buildAliases(
|
|
260
|
+
config: { full?: string; short?: string; envVars?: string[] },
|
|
261
|
+
...extraEnvVars: string[]
|
|
262
|
+
): Partial<SchemaFieldConfig> {
|
|
263
|
+
const envVars = [...(config.envVars ?? []), ...extraEnvVars];
|
|
248
264
|
return {
|
|
249
265
|
aliases: [
|
|
250
266
|
...(config.full ? [config.full.startsWith('-') ? config.full : `--${config.full}`] : []),
|
|
@@ -269,4 +285,4 @@ export class CliParseUtil {
|
|
|
269
285
|
const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
|
|
270
286
|
local[STATE_SYMBOL] = state;
|
|
271
287
|
}
|
|
272
|
-
}
|
|
288
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { type Class, type ClassInstance,
|
|
1
|
+
import { type Class, type ClassInstance, castTo, describeFunction, Env, getClass, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
2
2
|
import { SchemaRegistryIndex, type ValidationError } from '@travetto/schema';
|
|
3
3
|
|
|
4
|
-
import type { CliCommandShape } from '../types.ts';
|
|
5
|
-
import { CliCommandRegistryIndex } from './registry-index.ts';
|
|
6
4
|
import { CliModuleUtil } from '../module.ts';
|
|
7
5
|
import { CliParseUtil } from '../parse.ts';
|
|
6
|
+
import type { CliCommandShape } from '../types.ts';
|
|
8
7
|
import { CliUtil } from '../util.ts';
|
|
8
|
+
import { CliCommandRegistryIndex } from './registry-index.ts';
|
|
9
9
|
|
|
10
10
|
type CliCommandConfigOptions = { runTarget?: boolean };
|
|
11
|
-
type CliFlagOptions = { full?: string
|
|
11
|
+
type CliFlagOptions = { full?: string; short?: string; envVars?: string[] };
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Decorator to register a CLI command
|
|
@@ -32,8 +32,7 @@ export function CliCommand(config: CliCommandConfigOptions = {}) {
|
|
|
32
32
|
*/
|
|
33
33
|
export function CliFlag(config: CliFlagOptions) {
|
|
34
34
|
return function (instance: ClassInstance, property: string): void {
|
|
35
|
-
SchemaRegistryIndex.getForRegister(getClass(instance))
|
|
36
|
-
.registerField(property, CliParseUtil.buildAliases(config));
|
|
35
|
+
SchemaRegistryIndex.getForRegister(getClass(instance)).registerField(property, CliParseUtil.buildAliases(config));
|
|
37
36
|
};
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -65,9 +64,9 @@ export function CliProfilesFlag(config: CliFlagOptions = {}) {
|
|
|
65
64
|
description: 'Application profiles'
|
|
66
65
|
});
|
|
67
66
|
|
|
68
|
-
CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 1, cmd => Env.TRV_PROFILES.add(...cmd[property] ?? []));
|
|
67
|
+
CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 1, cmd => Env.TRV_PROFILES.add(...(cmd[property] ?? [])));
|
|
69
68
|
};
|
|
70
|
-
}
|
|
69
|
+
}
|
|
71
70
|
|
|
72
71
|
/**
|
|
73
72
|
* Registers a flag to support targeting a specific module
|
|
@@ -84,28 +83,35 @@ export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'co
|
|
|
84
83
|
...CliParseUtil.buildAliases(config, Env.TRV_MODULE.key),
|
|
85
84
|
description: 'Module to run for',
|
|
86
85
|
specifiers: ['module'],
|
|
87
|
-
required: { active: Runtime.monoRoot && config.scope !== 'command' }
|
|
86
|
+
required: { active: Runtime.monoRoot && config.scope !== 'command' }
|
|
88
87
|
});
|
|
89
88
|
|
|
90
89
|
SchemaRegistryIndex.getForRegister(cls).register({
|
|
91
|
-
validators: [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
validators: [
|
|
91
|
+
async (cmd: CliCommandShape): Promise<ValidationError | undefined> => {
|
|
92
|
+
const typed: typeof cmd & { [property]?: string } = castTo(cmd);
|
|
93
|
+
const providedModule = typed[property];
|
|
94
|
+
const runModule = (config.scope === 'command' ? commandModule : providedModule) || Runtime.main.name;
|
|
95
|
+
|
|
96
|
+
// If we need to run as a specific module
|
|
97
|
+
if (runModule !== Runtime.main.name && RuntimeIndex.getModule(runModule) === undefined) {
|
|
98
|
+
return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: property };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
|
|
102
|
+
return {
|
|
103
|
+
source: 'flag',
|
|
104
|
+
message: `${runModule} does not have ${commandModule} as a dependency`,
|
|
105
|
+
kind: 'custom',
|
|
106
|
+
path: property
|
|
107
|
+
};
|
|
108
|
+
}
|
|
99
109
|
}
|
|
100
|
-
|
|
101
|
-
if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
|
|
102
|
-
return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: property };
|
|
103
|
-
}
|
|
104
|
-
}],
|
|
110
|
+
]
|
|
105
111
|
});
|
|
106
112
|
|
|
107
113
|
CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 5, cmd => {
|
|
108
|
-
const typed:
|
|
114
|
+
const typed: typeof cmd & { [property]?: string } = castTo(cmd);
|
|
109
115
|
const providedModule = typed[property];
|
|
110
116
|
const runModule = (config.scope === 'command' ? commandModule : providedModule) || Runtime.main.name;
|
|
111
117
|
if (runModule !== Runtime.main.name) {
|
|
@@ -123,7 +129,9 @@ export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'co
|
|
|
123
129
|
export function CliRestartOnChangeFlag(config: CliFlagOptions = {}) {
|
|
124
130
|
return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
|
|
125
131
|
const cls = getClass(instance);
|
|
126
|
-
if (Runtime.production) {
|
|
132
|
+
if (Runtime.production) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
127
135
|
|
|
128
136
|
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
129
137
|
...CliParseUtil.buildAliases(config),
|
|
@@ -142,7 +150,9 @@ export function CliRestartOnChangeFlag(config: CliFlagOptions = {}) {
|
|
|
142
150
|
*/
|
|
143
151
|
export function CliDebugIpcFlag(config: CliFlagOptions = {}) {
|
|
144
152
|
return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
|
|
145
|
-
if (Runtime.production) {
|
|
153
|
+
if (Runtime.production) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
146
156
|
|
|
147
157
|
const cls = getClass(instance);
|
|
148
158
|
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
@@ -156,4 +166,4 @@ export function CliDebugIpcFlag(config: CliFlagOptions = {}) {
|
|
|
156
166
|
return cmd[property] && CliUtil.runWithDebugIpc(cliConfig.name);
|
|
157
167
|
});
|
|
158
168
|
};
|
|
159
|
-
}
|
|
169
|
+
}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { type Class, classConstruct, describeFunction } from '@travetto/runtime';
|
|
2
1
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
2
|
+
import { type Class, classConstruct, describeFunction } from '@travetto/runtime';
|
|
3
3
|
import { SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
|
-
import type { CliCommandConfig, CliCommandShape } from '../types.ts';
|
|
6
5
|
import { CliParseUtil, ENV_PREFIX } from '../parse.ts';
|
|
6
|
+
import type { CliCommandConfig, CliCommandShape } from '../types.ts';
|
|
7
7
|
|
|
8
8
|
const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
|
|
9
9
|
|
|
10
10
|
const getName = (name: string): string => (name.match(CLI_FILE_REGEX)?.groups?.name ?? name).replaceAll('_', ':');
|
|
11
11
|
const stripDashes = (flag?: string): string | undefined => flag?.replace(/^-+/, '');
|
|
12
|
-
const toFlagName = (field: string): string =>
|
|
12
|
+
const toFlagName = (field: string): string =>
|
|
13
|
+
field.replace(/([a-z])([A-Z])/g, (_, left: string, right: string) => `${left}-${right.toLowerCase()}`);
|
|
13
14
|
|
|
14
15
|
function combineClasses(base: CliCommandConfig, ...configs: Partial<CliCommandConfig>[]): CliCommandConfig {
|
|
15
16
|
for (const config of configs) {
|
|
16
17
|
base.runTarget = config.runTarget ?? base.runTarget;
|
|
17
18
|
if (config.preMain) {
|
|
18
|
-
base.preMain = [...base.preMain ?? [], ...config.preMain ?? []];
|
|
19
|
+
base.preMain = [...(base.preMain ?? []), ...(config.preMain ?? [])];
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
return base;
|
|
@@ -33,10 +34,11 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
|
|
|
33
34
|
finalize(parent?: CliCommandConfig): void {
|
|
34
35
|
// Add help command
|
|
35
36
|
const schema = SchemaRegistryIndex.getConfig(this.#cls);
|
|
36
|
-
const used = new Set(
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const used = new Set(
|
|
38
|
+
Object.values(schema.fields)
|
|
39
|
+
.flatMap(field => field.aliases ?? [])
|
|
40
|
+
.filter(alias => !alias.startsWith(ENV_PREFIX))
|
|
41
|
+
.map(stripDashes)
|
|
40
42
|
);
|
|
41
43
|
|
|
42
44
|
for (const field of Object.values(schema.fields)) {
|
|
@@ -45,7 +47,7 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
|
|
|
45
47
|
|
|
46
48
|
let short = stripDashes(shortAliases?.[0]) ?? rawAliases.find(alias => alias.length <= 2);
|
|
47
49
|
const long = stripDashes(longAliases?.[0]) ?? rawAliases.find(alias => alias.length >= 3) ?? toFlagName(fieldName);
|
|
48
|
-
const aliases: string[] = field.aliases = [...envAliases];
|
|
50
|
+
const aliases: string[] = (field.aliases = [...envAliases]);
|
|
49
51
|
|
|
50
52
|
if (short === undefined) {
|
|
51
53
|
short = fieldName.charAt(0);
|
|
@@ -65,7 +67,7 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
if (parent) {
|
|
68
|
-
this.#config.preMain = [...this.#config.preMain, ...parent?.preMain ?? []];
|
|
70
|
+
this.#config.preMain = [...this.#config.preMain, ...(parent?.preMain ?? [])];
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
// Sort
|
|
@@ -91,4 +93,4 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
|
|
|
91
93
|
getInstance(): CliCommandShape {
|
|
92
94
|
return classConstruct(this.#cls);
|
|
93
95
|
}
|
|
94
|
-
}
|
|
96
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Registry, type RegistryAdapter, type RegistryIndex, RegistryIndexStore } from '@travetto/registry';
|
|
1
2
|
import { type Any, type Class, getClass, getParentClass, isClass, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
2
|
-
import { type RegistryAdapter, type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
3
3
|
import { type SchemaClassConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import type { CliCommandConfig, CliCommandShape, PreMainHandler } from '../types.ts';
|
|
@@ -8,12 +8,11 @@ import { CliCommandRegistryAdapter } from './registry-adapter.ts';
|
|
|
8
8
|
const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
|
|
9
9
|
const getName = (field: string): string => (field.match(CLI_FILE_REGEX)?.groups?.name ?? field).replaceAll('_', ':');
|
|
10
10
|
|
|
11
|
-
type CliCommandLoadResult = { command: string
|
|
11
|
+
type CliCommandLoadResult = { command: string; config: CliCommandConfig; instance: CliCommandShape; schema: SchemaClassConfig };
|
|
12
12
|
|
|
13
13
|
export const UNKNOWN_COMMAND = Symbol();
|
|
14
14
|
|
|
15
15
|
export class CliCommandRegistryIndex implements RegistryIndex {
|
|
16
|
-
|
|
17
16
|
static #instance = Registry.registerIndex(this);
|
|
18
17
|
|
|
19
18
|
static getForRegister(cls: Class): RegistryAdapter<CliCommandConfig> {
|
|
@@ -37,7 +36,9 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
37
36
|
|
|
38
37
|
store = new RegistryIndexStore(CliCommandRegistryAdapter);
|
|
39
38
|
|
|
40
|
-
/** @private */ constructor(source: unknown) {
|
|
39
|
+
/** @private */ constructor(source: unknown) {
|
|
40
|
+
Registry.validateConstructor(source);
|
|
41
|
+
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Get list of all commands available
|
|
@@ -71,19 +72,16 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
71
72
|
|
|
72
73
|
const found = this.#commandMapping.get(name)!;
|
|
73
74
|
const values = Object.values(await Runtime.importFrom<Record<string, Class>>(found));
|
|
74
|
-
const filtered = values
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const uninitialized = filtered
|
|
86
|
-
.filter(cls => !this.store.finalized(cls));
|
|
75
|
+
const filtered = values.filter(isClass).reduce<Class[]>((classes, cls) => {
|
|
76
|
+
const parent = getParentClass(cls);
|
|
77
|
+
if (parent && !classes.includes(parent)) {
|
|
78
|
+
classes.push(parent);
|
|
79
|
+
}
|
|
80
|
+
classes.push(cls);
|
|
81
|
+
return classes;
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const uninitialized = filtered.filter(cls => !this.store.finalized(cls));
|
|
87
85
|
|
|
88
86
|
// Initialize any uninitialized commands
|
|
89
87
|
if (uninitialized.length) {
|
|
@@ -110,13 +108,15 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
110
108
|
async load(names?: string[]): Promise<CliCommandLoadResult[]> {
|
|
111
109
|
const keys = names ?? [...this.#commandMapping.keys()];
|
|
112
110
|
|
|
113
|
-
const list = await Promise.all(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
const list = await Promise.all(
|
|
112
|
+
keys.map(async key => {
|
|
113
|
+
const instance = await this.#getInstance(key);
|
|
114
|
+
const config = this.store.get(getClass(instance)).get();
|
|
115
|
+
const schema = SchemaRegistryIndex.getConfig(getClass(instance));
|
|
116
|
+
return { command: key, instance, config, schema };
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
119
|
|
|
120
120
|
return list.sort((a, b) => a.command.localeCompare(b.command));
|
|
121
121
|
}
|
|
122
|
-
}
|
|
122
|
+
}
|
package/src/schema-export.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Class, castTo, describeFunction } from '@travetto/runtime';
|
|
2
2
|
import { type SchemaInputConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { CliCommandRegistryIndex } from './registry/registry-index.ts';
|
|
@@ -32,24 +32,30 @@ export interface CliCommandSchema<K extends string = string> {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export class CliSchemaExportUtil {
|
|
35
|
-
|
|
36
35
|
/**
|
|
37
36
|
* Get the base type for a CLI command input
|
|
38
37
|
*/
|
|
39
38
|
static baseInputType(config: SchemaInputConfig): Pick<CliCommandInput, 'type' | 'fileExtensions'> {
|
|
40
39
|
switch (castTo<Function>(config.type)) {
|
|
41
|
-
case Date:
|
|
42
|
-
|
|
43
|
-
case
|
|
44
|
-
|
|
45
|
-
case
|
|
40
|
+
case Date:
|
|
41
|
+
return { type: 'date' };
|
|
42
|
+
case Boolean:
|
|
43
|
+
return { type: 'boolean' };
|
|
44
|
+
case Number:
|
|
45
|
+
return { type: 'number' };
|
|
46
|
+
case RegExp:
|
|
47
|
+
return { type: 'regex' };
|
|
48
|
+
case BigInt:
|
|
49
|
+
return { type: 'bigint' };
|
|
46
50
|
case String: {
|
|
47
51
|
switch (true) {
|
|
48
|
-
case config.specifiers?.includes('module'):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
case config.specifiers?.includes('module'):
|
|
53
|
+
return { type: 'module' };
|
|
54
|
+
case config.specifiers?.includes('file'):
|
|
55
|
+
return {
|
|
56
|
+
type: 'file',
|
|
57
|
+
fileExtensions: config.specifiers?.map(specifier => specifier.split('ext:')[1]).filter(specifier => !!specifier)
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -62,14 +68,17 @@ export class CliSchemaExportUtil {
|
|
|
62
68
|
static processInput(config: SchemaInputConfig): CliCommandInput {
|
|
63
69
|
return {
|
|
64
70
|
...this.baseInputType(config),
|
|
65
|
-
...(
|
|
71
|
+
...('name' in config && typeof config.name === 'string' ? { name: config.name } : { name: '' }),
|
|
66
72
|
description: config.description,
|
|
67
73
|
array: config.array,
|
|
68
74
|
required: config.required?.active !== false,
|
|
69
75
|
choices: config.enum?.values,
|
|
70
76
|
default: Array.isArray(config.default) ? config.default.slice(0) : config.default,
|
|
71
77
|
flagNames: (config.aliases ?? []).slice(0).filter(value => !value.startsWith('env.')),
|
|
72
|
-
envVars: (config.aliases ?? [])
|
|
78
|
+
envVars: (config.aliases ?? [])
|
|
79
|
+
.slice(0)
|
|
80
|
+
.filter(value => value.startsWith('env.'))
|
|
81
|
+
.map(value => value.replace('env.', ''))
|
|
73
82
|
};
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -87,4 +96,4 @@ export class CliSchemaExportUtil {
|
|
|
87
96
|
runTarget: config.runTarget ?? false
|
|
88
97
|
};
|
|
89
98
|
}
|
|
90
|
-
}
|
|
99
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { castKey, castTo, getClass } from '@travetto/runtime';
|
|
2
|
-
import { BindUtil, SchemaRegistryIndex, SchemaValidator,
|
|
2
|
+
import { BindUtil, SchemaRegistryIndex, SchemaValidator, type ValidationError, ValidationResultError } from '@travetto/schema';
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { CliCommandShape, ParsedState } from './types.ts';
|
|
5
5
|
|
|
6
6
|
const getSource = (source: string | undefined, defaultSource: ValidationError['source']): ValidationError['source'] => {
|
|
7
7
|
switch (source) {
|
|
8
8
|
case 'custom':
|
|
9
9
|
case 'arg':
|
|
10
|
-
case 'flag':
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
case 'flag':
|
|
11
|
+
return source;
|
|
12
|
+
case undefined:
|
|
13
|
+
return defaultSource;
|
|
14
|
+
default:
|
|
15
|
+
return 'custom';
|
|
13
16
|
}
|
|
14
17
|
};
|
|
15
18
|
|
|
@@ -41,7 +44,7 @@ export class CliCommandSchemaUtil {
|
|
|
41
44
|
const key = castKey<T>(item.fieldName);
|
|
42
45
|
const value = item.value!;
|
|
43
46
|
if (item.array) {
|
|
44
|
-
castTo<unknown[]>(template[key] ??= castTo([])).push(value);
|
|
47
|
+
castTo<unknown[]>((template[key] ??= castTo([]))).push(value);
|
|
45
48
|
} else {
|
|
46
49
|
template[key] = castTo(value);
|
|
47
50
|
}
|
|
@@ -49,7 +52,7 @@ export class CliCommandSchemaUtil {
|
|
|
49
52
|
}
|
|
50
53
|
case 'arg': {
|
|
51
54
|
if (item.array) {
|
|
52
|
-
castTo<unknown[]>(bound[item.index] ??= []).push(item.input);
|
|
55
|
+
castTo<unknown[]>((bound[item.index] ??= [])).push(item.input);
|
|
53
56
|
} else {
|
|
54
57
|
bound[item.index] = item.input;
|
|
55
58
|
}
|
|
@@ -67,11 +70,13 @@ export class CliCommandSchemaUtil {
|
|
|
67
70
|
*/
|
|
68
71
|
static async validate(command: CliCommandShape, args: unknown[]): Promise<typeof command> {
|
|
69
72
|
const cls = getClass(command);
|
|
70
|
-
const paramNames = SchemaRegistryIndex.get(cls)
|
|
73
|
+
const paramNames = SchemaRegistryIndex.get(cls)
|
|
74
|
+
.getMethod('main')
|
|
75
|
+
.parameters.map(config => config.name!);
|
|
71
76
|
|
|
72
77
|
const results = await Promise.all([
|
|
73
78
|
SchemaValidator.validate(cls, command).then(() => [], transformFlagErrors),
|
|
74
|
-
SchemaValidator.validateMethod(cls, 'main', args, paramNames).then(() => [], transformArgErrors)
|
|
79
|
+
SchemaValidator.validateMethod(cls, 'main', args, paramNames).then(() => [], transformArgErrors)
|
|
75
80
|
]);
|
|
76
81
|
|
|
77
82
|
const errors = results.flat();
|
|
@@ -80,4 +85,4 @@ export class CliCommandSchemaUtil {
|
|
|
80
85
|
}
|
|
81
86
|
return command;
|
|
82
87
|
}
|
|
83
|
-
}
|
|
88
|
+
}
|