@travetto/cli 3.4.0 → 3.4.2
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/execute.ts +1 -10
- package/src/help.ts +1 -1
- package/src/module.ts +8 -7
- package/src/registry.ts +3 -3
- package/src/schema.ts +120 -96
- package/src/scm.ts +9 -8
package/LICENSE
CHANGED
package/package.json
CHANGED
package/src/execute.ts
CHANGED
|
@@ -19,15 +19,6 @@ export class ExecutionManager {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
static async #bindAndValidateArgs(cmd: CliCommandShape, args: string[]): Promise<unknown[]> {
|
|
23
|
-
await cmd.initialize?.();
|
|
24
|
-
const remainingArgs = await CliCommandSchemaUtil.bindFlags(cmd, args);
|
|
25
|
-
const [known, unknown] = await CliCommandSchemaUtil.bindArgs(cmd, remainingArgs);
|
|
26
|
-
await cmd.finalize?.(unknown);
|
|
27
|
-
await CliCommandSchemaUtil.validate(cmd, known);
|
|
28
|
-
return known;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
/**
|
|
32
23
|
* Run help
|
|
33
24
|
*/
|
|
@@ -41,7 +32,7 @@ export class ExecutionManager {
|
|
|
41
32
|
* Run the given command object with the given arguments
|
|
42
33
|
*/
|
|
43
34
|
static async command(cmd: CliCommandShape, args: string[]): Promise<void> {
|
|
44
|
-
const known = await
|
|
35
|
+
const known = await CliCommandSchemaUtil.bindAndValidateArgs(cmd, args);
|
|
45
36
|
await this.#envInit(cmd);
|
|
46
37
|
const cfg = CliCommandRegistry.getConfig(cmd);
|
|
47
38
|
await cfg?.preMain?.(cmd);
|
package/src/help.ts
CHANGED
|
@@ -105,7 +105,7 @@ export class HelpUtil {
|
|
|
105
105
|
if (inst) {
|
|
106
106
|
const cfg = await CliCommandRegistry.getConfig(inst);
|
|
107
107
|
if (!cfg.hidden) {
|
|
108
|
-
const schema = await CliCommandSchemaUtil.getSchema(
|
|
108
|
+
const schema = await CliCommandSchemaUtil.getSchema(cfg.cls);
|
|
109
109
|
rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ title: schema.title }}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
package/src/module.ts
CHANGED
|
@@ -11,19 +11,20 @@ export class CliModuleUtil {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Find modules that changed, and the dependent modules
|
|
14
|
-
* @param
|
|
14
|
+
* @param fromHash
|
|
15
|
+
* @param toHash
|
|
15
16
|
* @param transitive
|
|
16
17
|
* @returns
|
|
17
18
|
*/
|
|
18
|
-
static async findChangedModulesRecursive(
|
|
19
|
-
|
|
19
|
+
static async findChangedModulesRecursive(fromHash?: string, toHash?: string, transitive = true): Promise<IndexedModule[]> {
|
|
20
|
+
fromHash ??= await CliScmUtil.findLastRelease();
|
|
20
21
|
|
|
21
|
-
if (!
|
|
22
|
+
if (!fromHash) {
|
|
22
23
|
return RootIndex.getLocalModules();
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const out = new Map<string, IndexedModule>();
|
|
26
|
-
for (const mod of await CliScmUtil.
|
|
27
|
+
for (const mod of await CliScmUtil.findChangedModules(fromHash, toHash)) {
|
|
27
28
|
out.set(mod.name, mod);
|
|
28
29
|
if (transitive) {
|
|
29
30
|
for (const sub of await RootIndex.getDependentModules(mod)) {
|
|
@@ -42,9 +43,9 @@ export class CliModuleUtil {
|
|
|
42
43
|
* @param transitive
|
|
43
44
|
* @returns
|
|
44
45
|
*/
|
|
45
|
-
static async findModules(mode: 'all' | 'changed',
|
|
46
|
+
static async findModules(mode: 'all' | 'changed', fromHash?: string, toHash?: string): Promise<IndexedModule[]> {
|
|
46
47
|
return (mode === 'changed' ?
|
|
47
|
-
await this.findChangedModulesRecursive(
|
|
48
|
+
await this.findChangedModulesRecursive(fromHash, toHash) :
|
|
48
49
|
[...RootIndex.getModuleList('all')].map(x => RootIndex.getModule(x)!)
|
|
49
50
|
).filter(x => x.sourcePath !== RootIndex.manifest.workspacePath);
|
|
50
51
|
}
|
package/src/registry.ts
CHANGED
|
@@ -29,7 +29,7 @@ class $CliCommandRegistry {
|
|
|
29
29
|
#commands = new Map<Class, CliCommandConfig>();
|
|
30
30
|
#fileMapping: Map<string, string>;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
getByClass(cls: Class): CliCommandConfig | undefined {
|
|
33
33
|
return this.#commands.get(cls);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -75,7 +75,7 @@ class $CliCommandRegistry {
|
|
|
75
75
|
* Get config for a given instance
|
|
76
76
|
*/
|
|
77
77
|
getConfig(cmd: CliCommandShape): CliCommandConfig {
|
|
78
|
-
return this
|
|
78
|
+
return this.getByClass(this.#getClass(cmd))!;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
@@ -97,7 +97,7 @@ class $CliCommandRegistry {
|
|
|
97
97
|
if (found) {
|
|
98
98
|
const values = Object.values<Class>(await import(found));
|
|
99
99
|
for (const v of values) {
|
|
100
|
-
const cfg = this
|
|
100
|
+
const cfg = this.getByClass(v);
|
|
101
101
|
if (cfg) {
|
|
102
102
|
const inst = new cfg.cls();
|
|
103
103
|
if (!inst.isActive || inst.isActive()) {
|
package/src/schema.ts
CHANGED
|
@@ -5,14 +5,34 @@ import { CliCommandRegistry } from './registry';
|
|
|
5
5
|
import { CliCommandInput, CliCommandSchema, CliCommandShape } from './types';
|
|
6
6
|
import { CliValidationResultError } from './error';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const VALID_FLAG = /^-{1,2}[a-z]/i;
|
|
9
|
+
const LONG_FLAG = /^--[a-z][^= ]+/i;
|
|
10
|
+
const LONG_FLAG_WITH_EQ = /^--[a-z][^= ]+=\S+/i;
|
|
11
|
+
const SHORT_FLAG = /^-[a-z]/i;
|
|
12
|
+
|
|
13
|
+
type ParsedInput =
|
|
14
|
+
{ type: 'unknown', input: string } |
|
|
15
|
+
{ type: 'arg', input: string, array?: boolean } |
|
|
16
|
+
{ type: 'flag', input: string, array?: boolean, fieldName: string, value?: unknown };
|
|
17
|
+
|
|
18
|
+
const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
|
|
19
|
+
|
|
20
|
+
const getInput = (cfg: { field?: CliCommandInput, rawText?: string, input: string, value?: string }): ParsedInput => {
|
|
21
|
+
const { field, input, rawText = input, value } = cfg;
|
|
22
|
+
if (!field) {
|
|
23
|
+
return { type: 'unknown', input: rawText };
|
|
24
|
+
} else if (!field.flagNames?.length) {
|
|
25
|
+
return { type: 'arg', input: field ? input : rawText ?? input, array: field.array };
|
|
12
26
|
} else {
|
|
13
|
-
return
|
|
27
|
+
return {
|
|
28
|
+
type: 'flag',
|
|
29
|
+
fieldName: field.name,
|
|
30
|
+
array: field.array,
|
|
31
|
+
input: field ? input : rawText ?? input,
|
|
32
|
+
value: value ?? (isBoolFlag(field) ? !input.startsWith('--no-') : undefined)
|
|
33
|
+
};
|
|
14
34
|
}
|
|
15
|
-
}
|
|
35
|
+
};
|
|
16
36
|
|
|
17
37
|
function fieldToInput(x: FieldConfig): CliCommandInput {
|
|
18
38
|
const type = x.type === Date ? 'date' :
|
|
@@ -34,12 +54,6 @@ function fieldToInput(x: FieldConfig): CliCommandInput {
|
|
|
34
54
|
});
|
|
35
55
|
}
|
|
36
56
|
|
|
37
|
-
const VALID_FLAG = /^-{1,2}[a-z]/i;
|
|
38
|
-
const LONG_FLAG = /^--[a-z]/i;
|
|
39
|
-
const SHORT_FLAG = /^-[a-z]/i;
|
|
40
|
-
|
|
41
|
-
const isBoolFlag = (x: CliCommandInput): boolean => x.type === 'boolean' && !x.array;
|
|
42
|
-
|
|
43
57
|
/**
|
|
44
58
|
* Allows binding describing/binding inputs for commands
|
|
45
59
|
*/
|
|
@@ -50,10 +64,9 @@ export class CliCommandSchemaUtil {
|
|
|
50
64
|
/**
|
|
51
65
|
* Get schema for a given command
|
|
52
66
|
*/
|
|
53
|
-
static async getSchema(
|
|
67
|
+
static async getSchema(src: Class | CliCommandShape): Promise<CliCommandSchema> {
|
|
54
68
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
55
|
-
const cls =
|
|
56
|
-
|
|
69
|
+
const cls = 'main' in src ? src.constructor as Class : src;
|
|
57
70
|
if (this.#schemas.has(cls)) {
|
|
58
71
|
return this.#schemas.get(cls)!;
|
|
59
72
|
}
|
|
@@ -110,7 +123,7 @@ export class CliCommandSchemaUtil {
|
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
const fullSchema = SchemaRegistry.get(cls);
|
|
113
|
-
const { cls: _cls, preMain: _preMain, ...meta } = CliCommandRegistry.
|
|
126
|
+
const { cls: _cls, preMain: _preMain, ...meta } = CliCommandRegistry.getByClass(cls)!;
|
|
114
127
|
const cfg: CliCommandSchema = {
|
|
115
128
|
...meta,
|
|
116
129
|
args: method,
|
|
@@ -123,111 +136,107 @@ export class CliCommandSchemaUtil {
|
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
/**
|
|
126
|
-
*
|
|
139
|
+
* Parse inputs to command
|
|
127
140
|
*/
|
|
128
|
-
static async
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
let i = 0;
|
|
136
|
-
|
|
137
|
-
for (const el of schema.args) {
|
|
138
|
-
// Siphon off unrecognized flags, in order
|
|
139
|
-
while (i < copy.length && VALID_FLAG.test(copy[i])) {
|
|
140
|
-
i += 1;
|
|
141
|
-
}
|
|
141
|
+
static async parse<T extends CliCommandShape>(cls: Class<T>, inputs: string[]): Promise<ParsedInput[]> {
|
|
142
|
+
const schema = await this.getSchema(cls);
|
|
143
|
+
const flagMap = new Map<string, CliCommandInput>(
|
|
144
|
+
schema.flags.flatMap(f => (f.flagNames ?? []).map(name => [name, f]))
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const out: ParsedInput[] = [];
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
// Load env vars to front
|
|
150
|
+
for (const field of schema.flags) {
|
|
151
|
+
for (const envName of field.envVars ?? []) {
|
|
152
|
+
if (envName in process.env) {
|
|
153
|
+
const value: string = process.env[envName]!;
|
|
154
|
+
if (field.array) {
|
|
155
|
+
out.push(...value.split(/\s*,\s*/g).map(v => getInput({ field, input: `env.${envName}`, value: v })));
|
|
156
|
+
} else {
|
|
157
|
+
out.push(getInput({ field, input: `env.${envName}`, value }));
|
|
151
158
|
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let argIdx = 0;
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
166
|
+
const input = inputs[i];
|
|
167
|
+
|
|
168
|
+
if (input === '--') { // Raw separator
|
|
169
|
+
out.push(...inputs.slice(i + 1).map(x => getInput({ input: x })));
|
|
170
|
+
break;
|
|
171
|
+
} else if (LONG_FLAG_WITH_EQ.test(input)) {
|
|
172
|
+
const [k, ...v] = input.split('=');
|
|
173
|
+
const field = flagMap.get(k);
|
|
174
|
+
out.push(getInput({ field, rawText: input, input: k, value: v.join('=') }));
|
|
175
|
+
} else if (VALID_FLAG.test(input)) { // Flag
|
|
176
|
+
const field = flagMap.get(input);
|
|
177
|
+
const next = inputs[i + 1];
|
|
178
|
+
if ((next && (VALID_FLAG.test(next) || next === '--')) || isBoolFlag(field)) {
|
|
179
|
+
out.push(getInput({ field, input }));
|
|
180
|
+
} else {
|
|
181
|
+
out.push(getInput({ field, input, value: next }));
|
|
152
182
|
i += 1;
|
|
153
183
|
}
|
|
154
|
-
out.push(sub);
|
|
155
184
|
} else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
185
|
+
const field = schema.args[argIdx];
|
|
186
|
+
out.push(getInput({ field, input }));
|
|
187
|
+
// Move argIdx along if not in a vararg situation
|
|
188
|
+
if (!field?.array) {
|
|
189
|
+
argIdx += 1;
|
|
190
|
+
}
|
|
159
191
|
}
|
|
160
192
|
}
|
|
161
193
|
|
|
162
|
-
|
|
163
|
-
return [
|
|
164
|
-
BindUtil.coerceMethodParams(cls, 'main', out, true),
|
|
165
|
-
final
|
|
166
|
-
];
|
|
194
|
+
return out;
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
/**
|
|
170
198
|
* Bind arguments to command
|
|
171
199
|
*/
|
|
172
|
-
static async bindFlags<T extends CliCommandShape>(cmd: T, args:
|
|
173
|
-
const schema = await this.getSchema(cmd);
|
|
174
|
-
|
|
175
|
-
const [base, extra] = split(args);
|
|
176
|
-
const copy = base.flatMap(k => (k.startsWith('--') && k.includes('=')) ? k.split('=') : [k]);
|
|
177
|
-
|
|
200
|
+
static async bindFlags<T extends CliCommandShape>(cmd: T, args: ParsedInput[]): Promise<void> {
|
|
178
201
|
const template: Partial<T> = {};
|
|
179
202
|
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
flagMap.set(name, flag);
|
|
184
|
-
}
|
|
185
|
-
for (const envName of flag.envVars ?? []) {
|
|
186
|
-
if (envName in process.env) {
|
|
187
|
-
let val: string | string[] = process.env[envName]!;
|
|
188
|
-
if (flag.array) {
|
|
189
|
-
val = val.split(/\s*,\s*/g);
|
|
190
|
-
}
|
|
203
|
+
for (const arg of args) {
|
|
204
|
+
switch (arg.type) {
|
|
205
|
+
case 'flag': {
|
|
191
206
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
192
|
-
|
|
207
|
+
const key = arg.fieldName as keyof T;
|
|
208
|
+
const value = arg.value!;
|
|
209
|
+
if (arg.array) {
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
211
|
+
((template[key] as unknown[]) ??= []).push(value);
|
|
212
|
+
} else {
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
214
|
+
template[key] = value as unknown as T[typeof key];
|
|
215
|
+
}
|
|
193
216
|
}
|
|
194
217
|
}
|
|
195
218
|
}
|
|
196
219
|
|
|
197
|
-
const out = [];
|
|
198
|
-
for (let i = 0; i < copy.length; i += 1) {
|
|
199
|
-
const arg = copy[i];
|
|
200
|
-
const next = copy[i + 1];
|
|
201
|
-
|
|
202
|
-
const input = flagMap.get(arg);
|
|
203
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
204
|
-
const key = input?.name as keyof T;
|
|
205
|
-
if (!input) {
|
|
206
|
-
out.push(arg);
|
|
207
|
-
} else if (isBoolFlag(input)) {
|
|
208
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
209
|
-
template[key] = !arg.startsWith('--no') as T[typeof key];
|
|
210
|
-
} else if (next === undefined || VALID_FLAG.test(next)) {
|
|
211
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
212
|
-
template[key] = null as T[typeof key];
|
|
213
|
-
} else if (input.array) {
|
|
214
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
215
|
-
const arr = template[key] ??= [] as T[typeof key];
|
|
216
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
217
|
-
(arr as unknown[]).push(next);
|
|
218
|
-
i += 1; // Skip next
|
|
219
|
-
} else {
|
|
220
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
221
|
-
template[key] = next as T[typeof key];
|
|
222
|
-
i += 1; // Skip next
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
220
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
227
221
|
const cls = cmd.constructor as Class<CliCommandShape>;
|
|
228
222
|
BindUtil.bindSchemaToObject(cls, cmd, template);
|
|
223
|
+
}
|
|
229
224
|
|
|
230
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Produce the arguments into the final argument set
|
|
227
|
+
*/
|
|
228
|
+
static async bindArgs(cmd: CliCommandShape, args: ParsedInput[]): Promise<unknown[]> {
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
230
|
+
const cls = cmd.constructor as Class<CliCommandShape>;
|
|
231
|
+
const out = args.filter(x => x.type === 'arg').map(x => x.input);
|
|
232
|
+
return BindUtil.coerceMethodParams(cls, 'main', out, true);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get the unused arguments
|
|
237
|
+
*/
|
|
238
|
+
static getUnusedArgs(args: ParsedInput[]): string[] {
|
|
239
|
+
return args.filter(x => x.type === 'unknown').map(x => x.input);
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
/**
|
|
@@ -265,4 +274,19 @@ export class CliCommandSchemaUtil {
|
|
|
265
274
|
}
|
|
266
275
|
return cmd;
|
|
267
276
|
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Bind and validate a command with a given set of arguments
|
|
280
|
+
*/
|
|
281
|
+
static async bindAndValidateArgs(cmd: CliCommandShape, args: string[]): Promise<unknown[]> {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
283
|
+
const cls = cmd.constructor as Class;
|
|
284
|
+
await cmd.initialize?.();
|
|
285
|
+
const parsed = await this.parse(cls, args);
|
|
286
|
+
await this.bindFlags(cmd, parsed);
|
|
287
|
+
const known = await this.bindArgs(cmd, parsed);
|
|
288
|
+
await cmd.finalize?.(this.getUnusedArgs(parsed));
|
|
289
|
+
await this.validate(cmd, known);
|
|
290
|
+
return known;
|
|
291
|
+
}
|
|
268
292
|
}
|
package/src/scm.ts
CHANGED
|
@@ -41,13 +41,13 @@ export class CliScmUtil {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Find all source files that changed
|
|
45
|
-
* @param
|
|
44
|
+
* Find all source files that changed between from and to hashes
|
|
45
|
+
* @param fromHash
|
|
46
46
|
* @returns
|
|
47
47
|
*/
|
|
48
|
-
static async
|
|
48
|
+
static async findChangedFiles(fromHash: string, toHash: string = 'HEAD'): Promise<string[]> {
|
|
49
49
|
const ws = RootIndex.manifest.workspacePath;
|
|
50
|
-
const res = await ExecUtil.spawn('git', ['diff', '--name-only',
|
|
50
|
+
const res = await ExecUtil.spawn('git', ['diff', '--name-only', `${fromHash}..${toHash}`, ':!**/DOC.*', ':!**/README.*'], { cwd: ws }).result;
|
|
51
51
|
const out = new Set<string>();
|
|
52
52
|
for (const line of res.stdout.split(/\n/g)) {
|
|
53
53
|
const entry = RootIndex.getEntry(path.resolve(ws, line));
|
|
@@ -59,12 +59,13 @@ export class CliScmUtil {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Find all modules that changed
|
|
63
|
-
* @param
|
|
62
|
+
* Find all modules that changed between from and to hashes
|
|
63
|
+
* @param fromHash
|
|
64
|
+
* @param toHash
|
|
64
65
|
* @returns
|
|
65
66
|
*/
|
|
66
|
-
static async
|
|
67
|
-
const files = await this.
|
|
67
|
+
static async findChangedModules(fromHash: string, toHash?: string): Promise<IndexedModule[]> {
|
|
68
|
+
const files = await this.findChangedFiles(fromHash, toHash);
|
|
68
69
|
const mods = files
|
|
69
70
|
.map(x => RootIndex.getFromSource(x))
|
|
70
71
|
.filter((x): x is IndexedFile => !!x)
|