@travetto/cli 8.0.0-alpha.0 → 8.0.0-alpha.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/README.md +59 -50
- package/__index__.ts +0 -1
- package/package.json +3 -3
- package/src/execute.ts +28 -54
- package/src/help.ts +48 -7
- package/src/parse.ts +32 -0
- package/src/registry/decorator.ts +126 -128
- package/src/registry/registry-adapter.ts +13 -6
- package/src/registry/registry-index.ts +4 -6
- package/src/schema-export.ts +4 -3
- package/src/schema.ts +35 -40
- package/src/service.ts +18 -1
- package/src/types.ts +5 -68
- package/src/util.ts +11 -17
- package/support/cli.cli_schema.ts +21 -18
- package/support/cli.main.ts +13 -14
- package/support/cli.service.ts +41 -33
- package/src/error.ts +0 -58
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Class, type ClassInstance, Env, Runtime, RuntimeIndex,
|
|
2
|
-
import {
|
|
1
|
+
import { type Class, type ClassInstance, Env, Runtime, RuntimeIndex, castTo, describeFunction, getClass } from '@travetto/runtime';
|
|
2
|
+
import { SchemaRegistryIndex, type ValidationError } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import type { CliCommandShape } from '../types.ts';
|
|
5
5
|
import { CliCommandRegistryIndex } from './registry-index.ts';
|
|
@@ -7,86 +7,15 @@ import { CliModuleUtil } from '../module.ts';
|
|
|
7
7
|
import { CliParseUtil } from '../parse.ts';
|
|
8
8
|
import { CliUtil } from '../util.ts';
|
|
9
9
|
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
type CliCommandConfigOptions = {
|
|
13
|
-
runTarget?: boolean;
|
|
14
|
-
runtimeModule?: 'current' | 'command';
|
|
15
|
-
with?: {
|
|
16
|
-
/** Application environment */
|
|
17
|
-
profiles?: boolean;
|
|
18
|
-
/** Module to run for */
|
|
19
|
-
module?: boolean;
|
|
20
|
-
/** Should debug invocation trigger via ipc */
|
|
21
|
-
debugIpc?: boolean | 'optional';
|
|
22
|
-
/** Should restart on source change */
|
|
23
|
-
restartOnChange?: boolean | 'optional';
|
|
24
|
-
};
|
|
25
|
-
};
|
|
10
|
+
type CliCommandConfigOptions = { runTarget?: boolean };
|
|
11
|
+
type CliFlagOptions = { full?: string, short?: string, envVars?: string[] };
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
|
|
35
|
-
profiles: (config) => {
|
|
36
|
-
if (!config) { return; }
|
|
37
|
-
return {
|
|
38
|
-
name: 'profiles',
|
|
39
|
-
run: cmd => cmd.profiles && Env.TRV_PROFILES.set([...cmd.profiles, ...(Env.TRV_PROFILES.list ?? [])]),
|
|
40
|
-
field: {
|
|
41
|
-
type: String,
|
|
42
|
-
aliases: ['--profile', '--profiles', CliParseUtil.toEnvField(Env.TRV_PROFILES.key)],
|
|
43
|
-
description: 'Application profiles',
|
|
44
|
-
required: { active: false },
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
module: (config) => {
|
|
49
|
-
if (!config) { return; }
|
|
50
|
-
return {
|
|
51
|
-
name: 'module',
|
|
52
|
-
field: {
|
|
53
|
-
type: String,
|
|
54
|
-
aliases: ['-m', CliParseUtil.toEnvField(Env.TRV_MODULE.key)],
|
|
55
|
-
description: 'Module to run for',
|
|
56
|
-
specifiers: ['module'],
|
|
57
|
-
required: { active: Runtime.monoRoot },
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
debugIpc: (config) => {
|
|
62
|
-
if (!config) { return; }
|
|
63
|
-
return {
|
|
64
|
-
name: 'debugIpc',
|
|
65
|
-
run: cmd => CliUtil.runWithDebugIpc(cmd),
|
|
66
|
-
field: {
|
|
67
|
-
type: Boolean,
|
|
68
|
-
aliases: ['-di', CliParseUtil.toEnvField(Env.TRV_DEBUG_IPC.key)],
|
|
69
|
-
description: 'Should debug invocation trigger via ipc',
|
|
70
|
-
default: config !== 'optional',
|
|
71
|
-
required: { active: false },
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
},
|
|
75
|
-
restartOnChange: (config) => {
|
|
76
|
-
if (!config) { return; }
|
|
77
|
-
return {
|
|
78
|
-
name: 'restartOnChange',
|
|
79
|
-
run: cmd => CliUtil.runWithRestartOnChange(cmd),
|
|
80
|
-
field: {
|
|
81
|
-
type: Boolean,
|
|
82
|
-
aliases: ['-rc'],
|
|
83
|
-
description: 'Should the invocation automatically restart on source changes',
|
|
84
|
-
default: config !== 'optional' && Runtime.localDevelopment,
|
|
85
|
-
required: { active: false },
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
};
|
|
13
|
+
function runBeforeMain<T>(cls: Class, handler: (item: T) => (unknown | Promise<unknown>), runTarget?: boolean): void {
|
|
14
|
+
CliCommandRegistryIndex.getForRegister(cls).register({
|
|
15
|
+
runTarget,
|
|
16
|
+
preMain: [async (cmd): Promise<void> => { await handler(castTo(cmd)); }]
|
|
17
|
+
});
|
|
18
|
+
}
|
|
90
19
|
|
|
91
20
|
/**
|
|
92
21
|
* Decorator to register a CLI command
|
|
@@ -97,70 +26,139 @@ const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
|
|
|
97
26
|
*/
|
|
98
27
|
export function CliCommand(config: CliCommandConfigOptions = {}) {
|
|
99
28
|
return function <T extends CliCommandShape>(target: Class<T>): void {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!target.Ⲑid || description.abstract) {
|
|
104
|
-
return;
|
|
29
|
+
if (target.Ⲑid && !describeFunction(target)?.abstract) {
|
|
30
|
+
CliCommandRegistryIndex.getForRegister(target).register(config);
|
|
105
31
|
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
106
34
|
|
|
107
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Decorator to register a CLI command flag
|
|
37
|
+
* @augments `@travetto/schema:Input`
|
|
38
|
+
* @kind decorator
|
|
39
|
+
*/
|
|
40
|
+
export function CliFlag(config: CliFlagOptions) {
|
|
41
|
+
return function (instance: ClassInstance, property: string): void {
|
|
42
|
+
SchemaRegistryIndex.getForRegister(getClass(instance))
|
|
43
|
+
.registerField(property, CliParseUtil.buildAliases(config));
|
|
44
|
+
};
|
|
45
|
+
}
|
|
108
46
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Decorator to register a CLI command file flag
|
|
49
|
+
* @augments `@travetto/schema:Input`
|
|
50
|
+
* @kind decorator
|
|
51
|
+
*/
|
|
52
|
+
export function CliFileFlag(config: CliFlagOptions & { fileExtensions: string[] }) {
|
|
53
|
+
return function (instance: ClassInstance, property: string): void {
|
|
54
|
+
SchemaRegistryIndex.getForRegister(getClass(instance)).registerField(property, {
|
|
55
|
+
...CliParseUtil.buildAliases(config),
|
|
56
|
+
specifiers: ['file', ...config.fileExtensions.map(ext => `ext:${ext.replace(/[*.]/g, '')}`)]
|
|
116
57
|
});
|
|
58
|
+
};
|
|
59
|
+
}
|
|
117
60
|
|
|
118
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Registers a flag to support profiles via the `TRV_PROFILES` environment variable
|
|
63
|
+
* @augments `@travetto/schema:Input`
|
|
64
|
+
* @kind decorator
|
|
65
|
+
*/
|
|
66
|
+
export function CliProfilesFlag(config: CliFlagOptions = {}) {
|
|
67
|
+
return function <K extends string>(instance: Partial<Record<K, string[]>>, property: K): void {
|
|
68
|
+
const cls = getClass(instance);
|
|
69
|
+
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
70
|
+
...CliParseUtil.buildAliases(config, Env.TRV_PROFILES.key),
|
|
71
|
+
required: { active: false },
|
|
72
|
+
description: 'Application profiles'
|
|
73
|
+
});
|
|
119
74
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
75
|
+
runBeforeMain(cls, (cmd: typeof instance) =>
|
|
76
|
+
Env.TRV_PROFILES.set([...cmd[property] ?? [], ...(Env.TRV_PROFILES.list ?? [])])
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
};
|
|
124
80
|
|
|
125
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Registers a flag to support targeting a specific module
|
|
83
|
+
* @augments `@travetto/schema:Input`
|
|
84
|
+
* @kind decorator
|
|
85
|
+
*/
|
|
86
|
+
export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'command' } = { scope: 'current' }) {
|
|
87
|
+
return function <K extends string>(instance: Partial<Record<K, string>>, property: K): void {
|
|
88
|
+
const cls = getClass(instance);
|
|
89
|
+
const description = describeFunction(cls) ?? {};
|
|
90
|
+
const commandModule = description.module;
|
|
126
91
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
92
|
+
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
93
|
+
...CliParseUtil.buildAliases(config, Env.TRV_MODULE.key),
|
|
94
|
+
description: 'Module to run for',
|
|
95
|
+
specifiers: ['module'],
|
|
96
|
+
required: { active: Runtime.monoRoot },
|
|
97
|
+
});
|
|
131
98
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
99
|
+
SchemaRegistryIndex.getForRegister(cls).register({
|
|
100
|
+
validators: [async (cmd: CliCommandShape): Promise<ValidationError | undefined> => {
|
|
101
|
+
const typed: (typeof cmd) & { [property]?: string } = castTo(cmd);
|
|
102
|
+
const providedModule = typed[property];
|
|
103
|
+
const runModule = (config.scope === 'command' ? commandModule : providedModule) || Runtime.main.name;
|
|
104
|
+
|
|
105
|
+
// If we need to run as a specific module
|
|
106
|
+
if (runModule !== Runtime.main.name) {
|
|
107
|
+
try {
|
|
108
|
+
RuntimeIndex.reinitForModule(runModule);
|
|
109
|
+
} catch {
|
|
110
|
+
return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: property };
|
|
139
111
|
}
|
|
112
|
+
}
|
|
140
113
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
114
|
+
if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
|
|
115
|
+
return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: property };
|
|
116
|
+
}
|
|
117
|
+
}],
|
|
118
|
+
});
|
|
147
119
|
};
|
|
148
120
|
}
|
|
149
121
|
|
|
150
122
|
/**
|
|
151
|
-
*
|
|
123
|
+
* Registers a flag to support restarting on source changes
|
|
152
124
|
* @augments `@travetto/schema:Input`
|
|
153
125
|
* @kind decorator
|
|
154
126
|
*/
|
|
155
|
-
export function
|
|
156
|
-
return function (instance:
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
...
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
127
|
+
export function CliRestartOnChangeFlag(config: CliFlagOptions = {}) {
|
|
128
|
+
return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
|
|
129
|
+
const cls = getClass(instance);
|
|
130
|
+
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
131
|
+
...CliParseUtil.buildAliases(config),
|
|
132
|
+
description: 'Should the invocation automatically restart on source changes',
|
|
133
|
+
default: Runtime.localDevelopment,
|
|
134
|
+
required: { active: false },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
runBeforeMain(cls, (cmd: typeof instance) => CliUtil.runWithRestartOnChange(cmd[property]), true);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Registers a flag to support debugging invocations triggered via IPC
|
|
143
|
+
* @augments `@travetto/schema:Input`
|
|
144
|
+
* @kind decorator
|
|
145
|
+
*/
|
|
146
|
+
export function CliDebugIpcFlag(config: CliFlagOptions = {}) {
|
|
147
|
+
return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
|
|
148
|
+
const cls = getClass(instance);
|
|
149
|
+
SchemaRegistryIndex.getForRegister(cls).registerField(property, {
|
|
150
|
+
...CliParseUtil.buildAliases(config, Env.TRV_DEBUG_IPC.key),
|
|
151
|
+
description: 'Should the invocation automatically restart on source changes',
|
|
152
|
+
default: Runtime.localDevelopment,
|
|
153
|
+
required: { active: false },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
runBeforeMain(cls,
|
|
157
|
+
(cmd: typeof instance & CliCommandShape) => {
|
|
158
|
+
const cliConfig = CliCommandRegistryIndex.get(cls);
|
|
159
|
+
return cmd[property] && CliUtil.runWithDebugIpc(cliConfig.name);
|
|
160
|
+
},
|
|
161
|
+
true
|
|
162
|
+
);
|
|
165
163
|
};
|
|
166
164
|
}
|
|
@@ -11,6 +11,16 @@ const getName = (name: string): string => (name.match(CLI_FILE_REGEX)?.groups?.n
|
|
|
11
11
|
const stripDashes = (flag?: string): string | undefined => flag?.replace(/^-+/, '');
|
|
12
12
|
const toFlagName = (field: string): string => field.replace(/([a-z])([A-Z])/g, (_, left: string, right: string) => `${left}-${right.toLowerCase()}`);
|
|
13
13
|
|
|
14
|
+
function combineClasses(base: CliCommandConfig, ...configs: Partial<CliCommandConfig>[]): CliCommandConfig {
|
|
15
|
+
for (const config of configs) {
|
|
16
|
+
base.runTarget = config.runTarget ?? base.runTarget;
|
|
17
|
+
if (config.preMain) {
|
|
18
|
+
base.preMain = [...base.preMain ?? [], ...config.preMain ?? []];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return base;
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConfig> {
|
|
15
25
|
#cls: Class;
|
|
16
26
|
#config: CliCommandConfig;
|
|
@@ -76,17 +86,14 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
|
|
|
76
86
|
*/
|
|
77
87
|
register(...configs: Partial<CliCommandConfig>[]): CliCommandConfig {
|
|
78
88
|
const metadata = describeFunction(this.#cls);
|
|
79
|
-
this.#config ??= { cls: this.#cls, name: getName(metadata.import) };
|
|
80
|
-
|
|
81
|
-
return this.#config;
|
|
89
|
+
this.#config ??= { cls: this.#cls, name: getName(metadata.import), preMain: [], runTarget: true };
|
|
90
|
+
return combineClasses(this.#config, ...configs);
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
/**
|
|
85
94
|
* Get instance of the command
|
|
86
95
|
*/
|
|
87
96
|
getInstance(): CliCommandShape {
|
|
88
|
-
|
|
89
|
-
instance._cfg = this.#config;
|
|
90
|
-
return instance;
|
|
97
|
+
return classConstruct(this.#cls);
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -3,7 +3,6 @@ import { type RegistryAdapter, type RegistryIndex, RegistryIndexStore, Registry
|
|
|
3
3
|
import { type SchemaClassConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import type { CliCommandConfig, CliCommandShape } from '../types.ts';
|
|
6
|
-
import { CliUnknownCommandError } from '../error.ts';
|
|
7
6
|
import { CliCommandRegistryAdapter } from './registry-adapter.ts';
|
|
8
7
|
|
|
9
8
|
const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
|
|
@@ -11,6 +10,8 @@ const getName = (field: string): string => (field.match(CLI_FILE_REGEX)?.groups?
|
|
|
11
10
|
|
|
12
11
|
type CliCommandLoadResult = { command: string, config: CliCommandConfig, instance: CliCommandShape, schema: SchemaClassConfig };
|
|
13
12
|
|
|
13
|
+
export const UNKNOWN_COMMAND = Symbol();
|
|
14
|
+
|
|
14
15
|
export class CliCommandRegistryIndex implements RegistryIndex {
|
|
15
16
|
|
|
16
17
|
static #instance = Registry.registerIndex(this);
|
|
@@ -57,7 +58,7 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
57
58
|
*/
|
|
58
59
|
async #getInstance(name: string): Promise<CliCommandShape> {
|
|
59
60
|
if (!this.hasCommand(name)) {
|
|
60
|
-
throw
|
|
61
|
+
throw UNKNOWN_COMMAND;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
if (this.#instanceMapping.has(name)) {
|
|
@@ -92,13 +93,10 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
92
93
|
continue;
|
|
93
94
|
}
|
|
94
95
|
const result = config.getInstance();
|
|
95
|
-
if (result.isActive !== undefined && !result.isActive()) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
96
|
this.#instanceMapping.set(name, result);
|
|
99
97
|
return result;
|
|
100
98
|
}
|
|
101
|
-
throw
|
|
99
|
+
throw UNKNOWN_COMMAND;
|
|
102
100
|
}
|
|
103
101
|
|
|
104
102
|
hasCommand(name: string): boolean {
|
package/src/schema-export.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Class, describeFunction } from '@travetto/runtime';
|
|
1
|
+
import { castTo, type Class, describeFunction } from '@travetto/runtime';
|
|
2
2
|
import { type SchemaInputConfig, SchemaRegistryIndex } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { CliCommandRegistryIndex } from '../src/registry/registry-index.ts';
|
|
@@ -9,7 +9,7 @@ import { CliCommandRegistryIndex } from '../src/registry/registry-index.ts';
|
|
|
9
9
|
export type CliCommandInput<K extends string = string> = {
|
|
10
10
|
name: string;
|
|
11
11
|
description?: string;
|
|
12
|
-
type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex' | 'module';
|
|
12
|
+
type: 'string' | 'file' | 'number' | 'bigint' | 'boolean' | 'date' | 'regex' | 'module';
|
|
13
13
|
fileExtensions?: string[];
|
|
14
14
|
choices?: unknown[];
|
|
15
15
|
required?: boolean;
|
|
@@ -37,11 +37,12 @@ export class CliSchemaExportUtil {
|
|
|
37
37
|
* Get the base type for a CLI command input
|
|
38
38
|
*/
|
|
39
39
|
static baseInputType(config: SchemaInputConfig): Pick<CliCommandInput, 'type' | 'fileExtensions'> {
|
|
40
|
-
switch (config.type) {
|
|
40
|
+
switch (castTo<Function>(config.type)) {
|
|
41
41
|
case Date: return { type: 'date' };
|
|
42
42
|
case Boolean: return { type: 'boolean' };
|
|
43
43
|
case Number: return { type: 'number' };
|
|
44
44
|
case RegExp: return { type: 'regex' };
|
|
45
|
+
case BigInt: return { type: 'bigint' };
|
|
45
46
|
case String: {
|
|
46
47
|
switch (true) {
|
|
47
48
|
case config.specifiers?.includes('module'): return { type: 'module' };
|
package/src/schema.ts
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
import { castKey, castTo, getClass } from '@travetto/runtime';
|
|
2
|
-
import { BindUtil, SchemaRegistryIndex, SchemaValidator, ValidationResultError } from '@travetto/schema';
|
|
2
|
+
import { BindUtil, SchemaRegistryIndex, SchemaValidator, ValidationResultError, type ValidationError } from '@travetto/schema';
|
|
3
3
|
|
|
4
|
-
import type { ParsedState, CliCommandShape
|
|
5
|
-
import { CliValidationResultError } from './error.ts';
|
|
4
|
+
import type { ParsedState, CliCommandShape } from './types.ts';
|
|
6
5
|
|
|
7
|
-
const getSource = (source: string | undefined,
|
|
6
|
+
const getSource = (source: string | undefined, defaultSource: ValidationError['source']): ValidationError['source'] => {
|
|
8
7
|
switch (source) {
|
|
9
8
|
case 'custom':
|
|
10
9
|
case 'arg':
|
|
11
10
|
case 'flag': return source;
|
|
12
|
-
case undefined: return
|
|
11
|
+
case undefined: return defaultSource;
|
|
13
12
|
default: return 'custom';
|
|
14
13
|
}
|
|
15
14
|
};
|
|
16
15
|
|
|
16
|
+
const transformErrors = (source: 'arg' | 'flag', error: unknown): ValidationError[] => {
|
|
17
|
+
if (error instanceof ValidationResultError) {
|
|
18
|
+
return error.details.errors.map(value => ({ source: getSource(value.source, source), ...value }));
|
|
19
|
+
} else {
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const transformArgErrors = (error: unknown): ValidationError[] => transformErrors('arg', error);
|
|
25
|
+
const transformFlagErrors = (error: unknown): ValidationError[] => transformErrors('flag', error);
|
|
26
|
+
|
|
17
27
|
/**
|
|
18
28
|
* Allows binding describing/binding inputs for commands
|
|
19
29
|
*/
|
|
@@ -21,16 +31,16 @@ export class CliCommandSchemaUtil {
|
|
|
21
31
|
/**
|
|
22
32
|
* Bind parsed inputs to command
|
|
23
33
|
*/
|
|
24
|
-
static bindInput<T extends CliCommandShape>(
|
|
34
|
+
static bindInput<T extends CliCommandShape>(command: T, state: ParsedState): unknown[] {
|
|
25
35
|
const template: Partial<T> = {};
|
|
26
36
|
const bound: unknown[] = [];
|
|
27
37
|
|
|
28
|
-
for (const
|
|
29
|
-
switch (
|
|
38
|
+
for (const item of state.all) {
|
|
39
|
+
switch (item.type) {
|
|
30
40
|
case 'flag': {
|
|
31
|
-
const key = castKey<T>(
|
|
32
|
-
const value =
|
|
33
|
-
if (
|
|
41
|
+
const key = castKey<T>(item.fieldName);
|
|
42
|
+
const value = item.value!;
|
|
43
|
+
if (item.array) {
|
|
34
44
|
castTo<unknown[]>(template[key] ??= castTo([])).push(value);
|
|
35
45
|
} else {
|
|
36
46
|
template[key] = castTo(value);
|
|
@@ -38,51 +48,36 @@ export class CliCommandSchemaUtil {
|
|
|
38
48
|
break;
|
|
39
49
|
}
|
|
40
50
|
case 'arg': {
|
|
41
|
-
if (
|
|
42
|
-
castTo<unknown[]>(bound[
|
|
51
|
+
if (item.array) {
|
|
52
|
+
castTo<unknown[]>(bound[item.index] ??= []).push(item.input);
|
|
43
53
|
} else {
|
|
44
|
-
bound[
|
|
54
|
+
bound[item.index] = item.input;
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
const cls = getClass(
|
|
51
|
-
BindUtil.bindSchemaToObject(cls,
|
|
60
|
+
const cls = getClass(command);
|
|
61
|
+
BindUtil.bindSchemaToObject(cls, command, template);
|
|
52
62
|
return BindUtil.coerceMethodParams(cls, 'main', bound);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
56
66
|
* Validate command shape with the given arguments
|
|
57
67
|
*/
|
|
58
|
-
static async validate(
|
|
59
|
-
const cls = getClass(
|
|
68
|
+
static async validate(command: CliCommandShape, args: unknown[]): Promise<typeof command> {
|
|
69
|
+
const cls = getClass(command);
|
|
60
70
|
const paramNames = SchemaRegistryIndex.get(cls).getMethod('main').parameters.map(config => config.name!);
|
|
61
71
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const result = await cmd.validate?.(...args);
|
|
67
|
-
if (result) {
|
|
68
|
-
throw new CliValidationResultError(cmd, Array.isArray(result) ? result : [result]);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const SOURCES = ['flag', 'arg', 'custom'] as const;
|
|
74
|
-
|
|
75
|
-
const results = validators.map((validator, i) => validator().catch(error => {
|
|
76
|
-
if (!(error instanceof CliValidationResultError) && !(error instanceof ValidationResultError)) {
|
|
77
|
-
throw error;
|
|
78
|
-
}
|
|
79
|
-
return error.details.errors.map(value => ({ ...value, source: getSource(value.source, SOURCES[i]) }));
|
|
80
|
-
}));
|
|
72
|
+
const results = await Promise.all([
|
|
73
|
+
SchemaValidator.validate(cls, command).then(() => [], transformFlagErrors),
|
|
74
|
+
SchemaValidator.validateMethod(cls, 'main', args, paramNames).then(() => [], transformArgErrors),
|
|
75
|
+
]);
|
|
81
76
|
|
|
82
|
-
const errors =
|
|
77
|
+
const errors = results.flat();
|
|
83
78
|
if (errors.length) {
|
|
84
|
-
throw new
|
|
79
|
+
throw new ValidationResultError(errors);
|
|
85
80
|
}
|
|
86
|
-
return
|
|
81
|
+
return command;
|
|
87
82
|
}
|
|
88
83
|
}
|
package/src/service.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import rl from 'node:readline/promises';
|
|
4
4
|
import net from 'node:net';
|
|
5
5
|
|
|
6
|
-
import { ExecUtil, TimeUtil, Util } from '@travetto/runtime';
|
|
6
|
+
import { ExecUtil, Runtime, RuntimeIndex, TimeUtil, Util } from '@travetto/runtime';
|
|
7
7
|
|
|
8
8
|
const ports = (value: number | `${number}:${number}`): [number, number] =>
|
|
9
9
|
typeof value === 'number' ?
|
|
@@ -36,6 +36,23 @@ export type ServiceAction = 'start' | 'stop' | 'status' | 'restart';
|
|
|
36
36
|
*/
|
|
37
37
|
export class ServiceRunner {
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Find all services
|
|
41
|
+
*/
|
|
42
|
+
static async findServices(services: string[]): Promise<ServiceDescriptor[]> {
|
|
43
|
+
return (await Promise.all(
|
|
44
|
+
RuntimeIndex.find({
|
|
45
|
+
module: module => module.roles.includes('std'),
|
|
46
|
+
folder: folder => folder === 'support',
|
|
47
|
+
file: file => /support\/service[.]/.test(file.sourceFile)
|
|
48
|
+
})
|
|
49
|
+
.map(file => Runtime.importFrom<{ service: ServiceDescriptor }>(file.import).then(value => value.service))
|
|
50
|
+
))
|
|
51
|
+
.filter(file => !!file)
|
|
52
|
+
.filter(file => services?.length ? services.includes(file.name) : true)
|
|
53
|
+
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
#descriptor: ServiceDescriptor;
|
|
40
57
|
constructor(descriptor: ServiceDescriptor) { this.#descriptor = descriptor; }
|
|
41
58
|
|
package/src/types.ts
CHANGED
|
@@ -13,89 +13,26 @@ export type ParsedState = {
|
|
|
13
13
|
unknown: string[];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Constrained version of Schema's Validation Error
|
|
18
|
-
* @concrete
|
|
19
|
-
*/
|
|
20
|
-
export interface CliValidationError {
|
|
21
|
-
/**
|
|
22
|
-
* The error message
|
|
23
|
-
*/
|
|
24
|
-
message: string;
|
|
25
|
-
/**
|
|
26
|
-
* Source of validation
|
|
27
|
-
*/
|
|
28
|
-
source?: 'flag' | 'arg' | 'custom';
|
|
29
|
-
};
|
|
30
|
-
|
|
31
16
|
/**
|
|
32
17
|
* CLI Command Contract
|
|
33
18
|
* @concrete
|
|
34
19
|
*/
|
|
35
|
-
export interface CliCommandShape
|
|
36
|
-
/**
|
|
37
|
-
* Parsed state
|
|
38
|
-
*/
|
|
39
|
-
_parsed?: ParsedState;
|
|
40
|
-
/**
|
|
41
|
-
* Config
|
|
42
|
-
*/
|
|
43
|
-
_cfg?: CliCommandConfig;
|
|
20
|
+
export interface CliCommandShape {
|
|
44
21
|
/**
|
|
45
22
|
* Action target of the command
|
|
46
23
|
*/
|
|
47
|
-
main(...args:
|
|
24
|
+
main(...args: unknown[]): OrProm<undefined | void>;
|
|
48
25
|
/**
|
|
49
26
|
* Run before main runs
|
|
50
27
|
*/
|
|
51
|
-
|
|
28
|
+
finalize?(help?: boolean): OrProm<void>;
|
|
52
29
|
/**
|
|
53
30
|
* Extra help
|
|
54
31
|
*/
|
|
55
32
|
help?(): OrProm<string[]>;
|
|
56
|
-
/**
|
|
57
|
-
* Run before help is displayed
|
|
58
|
-
*/
|
|
59
|
-
preHelp?(): OrProm<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Is the command active/eligible for usage
|
|
62
|
-
*/
|
|
63
|
-
isActive?(): boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Run before binding occurs
|
|
66
|
-
*/
|
|
67
|
-
preBind?(): OrProm<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Run before validation occurs
|
|
70
|
-
*/
|
|
71
|
-
preValidate?(): OrProm<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Validation method
|
|
74
|
-
*/
|
|
75
|
-
validate?(...args: T): OrProm<CliValidationError | CliValidationError[] | undefined>;
|
|
76
33
|
}
|
|
77
34
|
|
|
78
|
-
|
|
79
|
-
* Command shape common fields
|
|
80
|
-
*/
|
|
81
|
-
export type CliCommandShapeFields = {
|
|
82
|
-
/**
|
|
83
|
-
* Profiles to run the application under
|
|
84
|
-
*/
|
|
85
|
-
profiles?: string[];
|
|
86
|
-
/**
|
|
87
|
-
* Should the cli invocation trigger a debug session, via IPC
|
|
88
|
-
*/
|
|
89
|
-
debugIpc?: boolean;
|
|
90
|
-
/**
|
|
91
|
-
* Should the invocation run with auto-restart on source changes
|
|
92
|
-
*/
|
|
93
|
-
restartOnChange?: boolean;
|
|
94
|
-
/**
|
|
95
|
-
* The module to run the command from
|
|
96
|
-
*/
|
|
97
|
-
module?: string;
|
|
98
|
-
};
|
|
35
|
+
type PreMainHandler = (cmd: CliCommandShape) => (unknown | Promise<unknown>);
|
|
99
36
|
|
|
100
37
|
/**
|
|
101
38
|
* CLI Command schema shape
|
|
@@ -104,5 +41,5 @@ export interface CliCommandConfig {
|
|
|
104
41
|
cls: Class<CliCommandShape>;
|
|
105
42
|
name: string;
|
|
106
43
|
runTarget?: boolean;
|
|
107
|
-
preMain?:
|
|
44
|
+
preMain?: PreMainHandler[];
|
|
108
45
|
}
|