@travetto/cli 6.0.0 → 7.0.0-rc.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.
@@ -0,0 +1,153 @@
1
+ import { Class, ClassInstance, Env, Runtime, RuntimeIndex, describeFunction, getClass } from '@travetto/runtime';
2
+ import { SchemaFieldConfig, SchemaRegistryIndex, ValidationError } from '@travetto/schema';
3
+
4
+ import { CliCommandShape } from '../types.ts';
5
+ import { CliCommandRegistryIndex } from './registry-index.ts';
6
+ import { CliModuleUtil } from '../module.ts';
7
+ import { CliParseUtil } from '../parse.ts';
8
+ import { CliUtil } from '../util.ts';
9
+
10
+ type Cmd = CliCommandShape & { env?: string };
11
+
12
+ type CliCommandConfigOptions = {
13
+ runTarget?: boolean;
14
+ runtimeModule?: 'current' | 'command';
15
+ with?: {
16
+ /** Application environment */
17
+ env?: boolean;
18
+ /** Module to run for */
19
+ module?: boolean;
20
+ /** Should debug invocation trigger via ipc */
21
+ debugIpc?: boolean;
22
+ /** Should the invocation automatically restart on exit */
23
+ canRestart?: boolean;
24
+ };
25
+ };
26
+
27
+ const FIELD_CONFIG: {
28
+ name: keyof Exclude<CliCommandConfigOptions['with'], undefined>;
29
+ field: Partial<SchemaFieldConfig>;
30
+ run: (cmd: Cmd) => (Promise<unknown> | unknown);
31
+ }[] =
32
+ [
33
+ {
34
+ name: 'env',
35
+ run: cmd => Env.TRV_ENV.set(cmd.env || Runtime.env),
36
+ field: {
37
+ type: String,
38
+ aliases: ['-e', CliParseUtil.toEnvField(Env.TRV_ENV.key)],
39
+ description: 'Application environment',
40
+ required: { active: false },
41
+ },
42
+ },
43
+ {
44
+ name: 'module',
45
+ run: (): void => { },
46
+ field: {
47
+ type: String,
48
+ aliases: ['-m', CliParseUtil.toEnvField(Env.TRV_MODULE.key)],
49
+ description: 'Module to run for',
50
+ specifiers: ['module'],
51
+ required: { active: Runtime.monoRoot },
52
+ },
53
+ },
54
+ {
55
+ name: 'debugIpc',
56
+ run: cmd => CliUtil.debugIfIpc(cmd).then((v) => v && process.exit(0)),
57
+ field: {
58
+ type: Boolean,
59
+ aliases: ['-di'],
60
+ description: 'Should debug invocation trigger via ipc',
61
+ default: true,
62
+ required: { active: false },
63
+ },
64
+ },
65
+ {
66
+ name: 'canRestart',
67
+ run: cmd => CliUtil.runWithRestart(cmd)?.then((v) => v && process.exit(0)),
68
+ field: {
69
+ type: Boolean,
70
+ aliases: ['-cr'],
71
+ description: 'Should the invocation automatically restart on exit',
72
+ default: false,
73
+ required: { active: false },
74
+ },
75
+ }
76
+ ];
77
+
78
+ /**
79
+ * Decorator to register a CLI command
80
+ *
81
+ * @augments `@travetto/schema:Schema`
82
+ * @example main
83
+ * @kind decorator
84
+ */
85
+ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
86
+ return function <T extends CliCommandShape>(target: Class<T>): void {
87
+ const adapter = SchemaRegistryIndex.getForRegister(target);
88
+ const description = describeFunction(target) ?? {};
89
+
90
+
91
+ if (!target.Ⲑid || description.abstract) {
92
+ return;
93
+ }
94
+
95
+ const VALID_FIELDS = FIELD_CONFIG.filter(f => !!cfg.with?.[f.name]);
96
+
97
+ CliCommandRegistryIndex.getForRegister(target).register({
98
+ runTarget: cfg.runTarget,
99
+ preMain: async (cmd: Cmd) => {
100
+ for (const field of VALID_FIELDS) {
101
+ await field.run(cmd);
102
+ }
103
+ }
104
+ });
105
+
106
+ const commandModule = description.module;
107
+
108
+ for (const { name, field: { type, ...field } } of VALID_FIELDS) {
109
+ adapter.registerField(name, field, { type });
110
+ }
111
+
112
+ const runtimeModule = cfg.runtimeModule ?? (cfg.with?.module ? 'current' : undefined);
113
+
114
+ if (runtimeModule) { // Validate module
115
+ adapter.register({
116
+ validators: [async ({ module: mod }): Promise<ValidationError | undefined> => {
117
+ const runModule = (runtimeModule === 'command' ? commandModule : mod) || Runtime.main.name;
118
+
119
+ // If we need to run as a specific module
120
+ if (runModule !== Runtime.main.name) {
121
+ try {
122
+ RuntimeIndex.reinitForModule(runModule);
123
+ } catch {
124
+ return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
125
+ }
126
+ }
127
+
128
+ if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
129
+ return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: '.' };
130
+ }
131
+ }],
132
+ });
133
+ }
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Decorator to register a CLI command flag
139
+ * @augments `@travetto/schema:Input`
140
+ * @kind decorator
141
+ */
142
+ export function CliFlag(cfg: { full?: string, short?: string, fileExtensions?: string[], envVars?: string[] } = {}) {
143
+ return function (instance: ClassInstance, property: string | symbol): void {
144
+ const aliases = [
145
+ ...(cfg.full ? [cfg.full.startsWith('-') ? cfg.full : `--${cfg.full}`] : []),
146
+ ...(cfg.short ? [cfg.short.startsWith('-') ? cfg.short : `-${cfg.short}`] : []),
147
+ ...(cfg.envVars ? cfg.envVars.map(CliParseUtil.toEnvField) : [])
148
+ ];
149
+ const specifiers = cfg.fileExtensions?.length ? ['file', ...cfg.fileExtensions.map(x => `ext:${x.replace(/[*.]/g, '')}`)] : [];
150
+
151
+ SchemaRegistryIndex.getForRegister(getClass(instance)).registerField(property, { aliases, specifiers });
152
+ };
153
+ }
@@ -0,0 +1,96 @@
1
+ import { Class, classConstruct, describeFunction } from '@travetto/runtime';
2
+ import { RegistryAdapter } from '@travetto/registry';
3
+ import { SchemaRegistryIndex } from '@travetto/schema';
4
+
5
+ import { CliCommandConfig, CliCommandShape } from '../types.ts';
6
+ import { CliParseUtil, ENV_PREFIX } from '../parse.ts';
7
+
8
+ const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
9
+
10
+ const getName = (s: string): string => (s.match(CLI_FILE_REGEX)?.groups?.name ?? s).replaceAll('_', ':');
11
+ const stripDashes = (x?: string): string | undefined => x?.replace(/^-+/, '');
12
+ const toFlagName = (x: string): string => x.replace(/([a-z])([A-Z])/g, (_, l: string, r: string) => `${l}-${r.toLowerCase()}`);
13
+
14
+ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConfig> {
15
+ #cls: Class;
16
+ #config: CliCommandConfig;
17
+
18
+ constructor(cls: Class) {
19
+ this.#cls = cls;
20
+ }
21
+
22
+ finalize(): void {
23
+ // Add help command
24
+ const schema = SchemaRegistryIndex.getConfig(this.#cls);
25
+
26
+ // Add help to every command
27
+ (schema.fields ??= {}).help = {
28
+ type: Boolean,
29
+ name: 'help',
30
+ owner: this.#cls,
31
+ description: 'display help for command',
32
+ required: { active: false },
33
+ default: false,
34
+ access: 'readonly',
35
+ aliases: ['-h', '--help']
36
+ };
37
+
38
+ const used = new Set(Object.values(schema.fields)
39
+ .flatMap(f => f.aliases ?? [])
40
+ .filter(x => !x.startsWith(ENV_PREFIX))
41
+ .map(stripDashes)
42
+ );
43
+
44
+ for (const field of Object.values(schema.fields)) {
45
+ const fieldName = field.name.toString();
46
+ const { long: longAliases, short: shortAliases, raw: rawAliases, env: envAliases } = CliParseUtil.parseAliases(field.aliases ?? []);
47
+
48
+ let short = stripDashes(shortAliases?.[0]) ?? rawAliases.find(x => x.length <= 2);
49
+ const long = stripDashes(longAliases?.[0]) ?? rawAliases.find(x => x.length >= 3) ?? toFlagName(fieldName);
50
+ const aliases: string[] = field.aliases = [...envAliases];
51
+
52
+ if (short === undefined) {
53
+ short = fieldName.charAt(0);
54
+ if (!used.has(short)) {
55
+ aliases.push(`-${short}`);
56
+ used.add(short);
57
+ }
58
+ } else {
59
+ aliases.push(`-${short}`);
60
+ }
61
+
62
+ aliases.push(`--${long}`);
63
+
64
+ if (field.type === Boolean) {
65
+ aliases.push(`--no-${long}`);
66
+ }
67
+ }
68
+ }
69
+
70
+ get(): CliCommandConfig {
71
+ return this.#config;
72
+ }
73
+
74
+ /**
75
+ * Registers a cli command
76
+ */
77
+ register(...cfg: Partial<CliCommandConfig>[]): CliCommandConfig {
78
+ const meta = describeFunction(this.#cls);
79
+ this.#config ??= {
80
+ cls: this.#cls,
81
+ preMain: undefined,
82
+ name: getName(meta.import),
83
+ };
84
+ Object.assign(this.#config, ...cfg);
85
+ return this.#config;
86
+ }
87
+
88
+ /**
89
+ * Get instance of the command
90
+ */
91
+ getInstance(): CliCommandShape {
92
+ const instance: CliCommandShape = classConstruct(this.#cls);
93
+ instance._cfg = this.#config;
94
+ return instance;
95
+ }
96
+ }
@@ -0,0 +1,124 @@
1
+ import { Class, getClass, getParentClass, Runtime, RuntimeIndex } from '@travetto/runtime';
2
+ import { RegistryAdapter, RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
3
+ import { SchemaClassConfig, SchemaRegistryIndex } from '@travetto/schema';
4
+
5
+ import { CliCommandConfig, CliCommandShape } from '../types.ts';
6
+ import { CliUnknownCommandError } from '../error.ts';
7
+ import { CliCommandRegistryAdapter } from './registry-adapter.ts';
8
+
9
+ const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
10
+ const getName = (s: string): string => (s.match(CLI_FILE_REGEX)?.groups?.name ?? s).replaceAll('_', ':');
11
+
12
+ type CliCommandLoadResult = { command: string, config: CliCommandConfig, instance: CliCommandShape, schema: SchemaClassConfig };
13
+
14
+ export class CliCommandRegistryIndex implements RegistryIndex {
15
+
16
+ static #instance = Registry.registerIndex(this);
17
+
18
+ static getForRegister(cls: Class): RegistryAdapter<CliCommandConfig> {
19
+ return this.#instance.store.getForRegister(cls);
20
+ }
21
+
22
+ static get(cls: Class): CliCommandConfig {
23
+ return this.#instance.store.get(cls).get();
24
+ }
25
+
26
+ static load(names?: string[]): Promise<CliCommandLoadResult[]> {
27
+ return this.#instance.load(names);
28
+ }
29
+
30
+ #fileMapping: Map<string, string>;
31
+ #instanceMapping: Map<string, CliCommandShape> = new Map();
32
+
33
+ store = new RegistryIndexStore(CliCommandRegistryAdapter);
34
+
35
+ /**
36
+ * Get list of all commands available
37
+ */
38
+ get #commandMapping(): Map<string, string> {
39
+ if (!this.#fileMapping) {
40
+ const all = new Map<string, string>();
41
+ for (const e of RuntimeIndex.find({
42
+ module: m => !Runtime.production || m.prod,
43
+ folder: f => f === 'support',
44
+ file: f => f.role === 'std' && CLI_FILE_REGEX.test(f.sourceFile)
45
+ })) {
46
+ all.set(getName(e.sourceFile), e.import);
47
+ }
48
+ this.#fileMapping = all;
49
+ }
50
+ return this.#fileMapping;
51
+ }
52
+
53
+
54
+ process(): void {
55
+ // Do nothing for now?
56
+ }
57
+
58
+ /**
59
+ * Import command into an instance
60
+ */
61
+ async #getInstance(name: string): Promise<CliCommandShape> {
62
+ if (!this.hasCommand(name)) {
63
+ throw new CliUnknownCommandError(name);
64
+ }
65
+
66
+ if (this.#instanceMapping.has(name)) {
67
+ return this.#instanceMapping.get(name)!;
68
+ }
69
+
70
+ const found = this.#commandMapping.get(name)!;
71
+ const values = Object.values(await Runtime.importFrom<Record<string, Class>>(found));
72
+ const filtered = values
73
+ .filter((v): v is Class => typeof v === 'function')
74
+ .reduce<Class[]>((acc, v) => {
75
+ const parent = getParentClass(v);
76
+ if (parent && !acc.includes(parent)) {
77
+ acc.push(parent);
78
+ }
79
+ acc.push(v);
80
+ return acc;
81
+ }, []);
82
+
83
+ const uninitialized = filtered
84
+ .filter(v => !this.store.finalized(v));
85
+
86
+
87
+ // Initialize any uninitialized commands
88
+ if (uninitialized.length) {
89
+ // Ensure processed
90
+ Registry.process(uninitialized.map(v => ({ type: 'added', curr: v })));
91
+ }
92
+
93
+ for (const v of values) {
94
+ const cfg = this.store.get(v);
95
+ if (!cfg) {
96
+ continue;
97
+ }
98
+ const result = cfg.getInstance();
99
+ if (result.isActive !== undefined && !result.isActive()) {
100
+ continue;
101
+ }
102
+ this.#instanceMapping.set(name, result);
103
+ return result;
104
+ }
105
+ throw new CliUnknownCommandError(name);
106
+ }
107
+
108
+ hasCommand(name: string): boolean {
109
+ return this.#commandMapping.has(name);
110
+ }
111
+
112
+ async load(names?: string[]): Promise<CliCommandLoadResult[]> {
113
+ const keys = names ?? [...this.#commandMapping.keys()];
114
+
115
+ const list = await Promise.all(keys.map(async x => {
116
+ const instance = await this.#getInstance(x);
117
+ const config = this.store.get(getClass(instance)).get();
118
+ const schema = SchemaRegistryIndex.getConfig(getClass(instance));
119
+ return { command: x, instance, config, schema };
120
+ }));
121
+
122
+ return list.sort((a, b) => a.command.localeCompare(b.command));
123
+ }
124
+ }
@@ -0,0 +1,90 @@
1
+ import { Class, describeFunction } from '@travetto/runtime';
2
+ import { SchemaInputConfig, SchemaRegistryIndex } from '@travetto/schema';
3
+
4
+ import { CliCommandRegistryIndex } from '../src/registry/registry-index.ts';
5
+
6
+ /**
7
+ * CLI Command argument/flag shape
8
+ */
9
+ export type CliCommandInput<K extends string = string> = {
10
+ name: string;
11
+ description?: string;
12
+ type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex' | 'module';
13
+ fileExtensions?: string[];
14
+ choices?: unknown[];
15
+ required?: boolean;
16
+ array?: boolean;
17
+ default?: unknown;
18
+ flagNames?: K[];
19
+ envVars?: string[];
20
+ };
21
+
22
+ /**
23
+ * CLI Command schema shape
24
+ */
25
+ export interface CliCommandSchema<K extends string = string> {
26
+ name: string;
27
+ module: string;
28
+ commandModule: string;
29
+ runTarget?: boolean;
30
+ description?: string;
31
+ args: CliCommandInput[];
32
+ flags: CliCommandInput<K>[];
33
+ }
34
+
35
+ export class CliSchemaExportUtil {
36
+
37
+ /**
38
+ * Get the base type for a CLI command input
39
+ */
40
+ static baseInputType(x: SchemaInputConfig): Pick<CliCommandInput, 'type' | 'fileExtensions'> {
41
+ switch (x.type) {
42
+ case Date: return { type: 'date' };
43
+ case Boolean: return { type: 'boolean' };
44
+ case Number: return { type: 'number' };
45
+ case RegExp: return { type: 'regex' };
46
+ case String: {
47
+ switch (true) {
48
+ case x.specifiers?.includes('module'): return { type: 'module' };
49
+ case x.specifiers?.includes('file'): return {
50
+ type: 'file',
51
+ fileExtensions: x.specifiers?.map(s => s.split('ext:')[1]).filter(s => !!s)
52
+ };
53
+ }
54
+ }
55
+ }
56
+ return { type: 'string' };
57
+ }
58
+
59
+ /**
60
+ * Process input configuration for CLI commands
61
+ */
62
+ static processInput(x: SchemaInputConfig): CliCommandInput {
63
+ return {
64
+ ...this.baseInputType(x),
65
+ ...(('name' in x && typeof x.name === 'string') ? { name: x.name } : { name: '' }),
66
+ description: x.description,
67
+ array: x.array,
68
+ required: x.required?.active !== false,
69
+ choices: x.enum?.values,
70
+ default: Array.isArray(x.default) ? x.default.slice(0) : x.default,
71
+ flagNames: (x.aliases ?? []).slice(0).filter(v => !v.startsWith('env.')),
72
+ envVars: (x.aliases ?? []).slice(0).filter(v => v.startsWith('env.')).map(v => v.replace('env.', ''))
73
+ };
74
+ }
75
+
76
+ static exportSchema(cls: Class): CliCommandSchema {
77
+ const schema = SchemaRegistryIndex.getConfig(cls);
78
+ const config = CliCommandRegistryIndex.get(cls);
79
+ const processed = Object.values(schema.fields).map(v => this.processInput(v));
80
+ return {
81
+ name: config.name,
82
+ module: describeFunction(config.cls).module,
83
+ description: schema.description,
84
+ flags: processed.filter(v => v.flagNames && v.flagNames.length > 0),
85
+ args: processed.filter(v => !v.flagNames || v.flagNames.length === 0),
86
+ runTarget: config.runTarget ?? false,
87
+ commandModule: describeFunction(cls).module,
88
+ };
89
+ }
90
+ }
package/src/schema.ts CHANGED
@@ -1,125 +1,17 @@
1
- import { castKey, castTo, Class } from '@travetto/runtime';
2
- import { BindUtil, FieldConfig, SchemaRegistry, SchemaValidator, ValidationResultError } from '@travetto/schema';
1
+ import { castKey, castTo, getClass } from '@travetto/runtime';
2
+ import { BindUtil, SchemaRegistryIndex, SchemaValidator, ValidationResultError } from '@travetto/schema';
3
3
 
4
- import { CliCommandRegistry } from './registry.ts';
5
- import { ParsedState, CliCommandInput, CliCommandSchema, CliCommandShape } from './types.ts';
4
+ import { ParsedState, CliCommandShape } from './types.ts';
6
5
  import { CliValidationResultError } from './error.ts';
7
6
 
8
- const LONG_FLAG = /^--[a-z][^= ]+/i;
9
- const SHORT_FLAG = /^-[a-z]/i;
10
-
11
- const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
12
-
13
- function baseType(x: FieldConfig): Pick<CliCommandInput, 'type' | 'fileExtensions'> {
14
- switch (x.type) {
15
- case Date: return { type: 'date' };
16
- case Boolean: return { type: 'boolean' };
17
- case Number: return { type: 'number' };
18
- case RegExp: return { type: 'regex' };
19
- case String: {
20
- switch (true) {
21
- case x.specifiers?.includes('module'): return { type: 'module' };
22
- case x.specifiers?.includes('file'): return {
23
- type: 'file',
24
- fileExtensions: x.specifiers?.map(s => s.split('ext:')[1]).filter(s => !!s)
25
- };
26
- }
27
- }
28
- }
29
- return { type: 'string' };
30
- }
31
-
32
- const fieldToInput = (x: FieldConfig): CliCommandInput => ({
33
- ...baseType(x),
34
- name: x.name,
35
- description: x.description,
36
- array: x.array,
37
- required: x.required?.active,
38
- choices: x.enum?.values,
39
- default: Array.isArray(x.default) ? x.default.slice(0) : x.default,
40
- flagNames: (x.aliases ?? []).slice(0).filter(v => !v.startsWith('env.')),
41
- envVars: (x.aliases ?? []).slice(0).filter(v => v.startsWith('env.')).map(v => v.replace('env.', ''))
42
- });
43
-
44
7
  /**
45
8
  * Allows binding describing/binding inputs for commands
46
9
  */
47
10
  export class CliCommandSchemaUtil {
48
-
49
- static #schemas = new Map<Class, CliCommandSchema>();
50
-
51
- /**
52
- * Get schema for a given command
53
- */
54
- static async getSchema(src: Class | CliCommandShape): Promise<CliCommandSchema> {
55
- const cls = 'main' in src ? CliCommandRegistry.getClass(src) : src;
56
- if (this.#schemas.has(cls)) {
57
- return this.#schemas.get(cls)!;
58
- }
59
-
60
- // Ensure finalized
61
- const parent = SchemaRegistry.getParentClass(cls);
62
- if (parent?.Ⲑid) {
63
- SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
64
- }
65
- SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
66
-
67
- const schema = await SchemaRegistry.getViewSchema(cls);
68
- const flags = Object.values(schema.schema).map(fieldToInput);
69
-
70
- // Add help command
71
- flags.push({ name: 'help', flagNames: ['h'], description: 'display help for command', type: 'boolean' });
72
-
73
- const method = SchemaRegistry.getMethodSchema(cls, 'main').map(fieldToInput);
74
-
75
- const used = new Set(flags
76
- .flatMap(f => f.flagNames ?? [])
77
- .filter(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)
78
- .map(x => x.replace(/^-+/, ''))
79
- );
80
-
81
- for (const flag of flags) {
82
- let short = (flag.flagNames ?? []).find(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)?.replace(/^-+/, '');
83
- const long = (flag.flagNames ?? []).find(x => LONG_FLAG.test(x) || x.replaceAll('-', '').length > 2)?.replace(/^-+/, '') ??
84
- flag.name.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
85
- const aliases: string[] = flag.flagNames = [];
86
-
87
- if (short === undefined) {
88
- if (!(isBoolFlag(flag) && flag.default === true)) {
89
- short = flag.name.charAt(0);
90
- if (!used.has(short)) {
91
- aliases.push(`-${short}`);
92
- used.add(short);
93
- }
94
- }
95
- } else {
96
- aliases.push(`-${short}`);
97
- }
98
-
99
- aliases.push(`--${long}`);
100
-
101
- if (isBoolFlag(flag)) {
102
- aliases.push(`--no-${long}`);
103
- }
104
- }
105
-
106
- const fullSchema = SchemaRegistry.get(cls);
107
- const { cls: _cls, preMain: _preMain, ...meta } = CliCommandRegistry.getByClass(cls)!;
108
- const cfg: CliCommandSchema = {
109
- ...meta,
110
- args: method,
111
- flags,
112
- title: fullSchema.title ?? cls.name,
113
- description: fullSchema.description ?? ''
114
- };
115
- this.#schemas.set(cls, cfg);
116
- return cfg;
117
- }
118
-
119
11
  /**
120
12
  * Bind parsed inputs to command
121
13
  */
122
- static async bindInput<T extends CliCommandShape>(cmd: T, state: ParsedState): Promise<unknown[]> {
14
+ static bindInput<T extends CliCommandShape>(cmd: T, state: ParsedState): unknown[] {
123
15
  const template: Partial<T> = {};
124
16
  const bound: unknown[] = [];
125
17
 
@@ -145,7 +37,7 @@ export class CliCommandSchemaUtil {
145
37
  }
146
38
  }
147
39
 
148
- const cls = CliCommandRegistry.getClass(cmd);
40
+ const cls = getClass(cmd);
149
41
  BindUtil.bindSchemaToObject(cls, cmd, template);
150
42
  return BindUtil.coerceMethodParams(cls, 'main', bound);
151
43
  }
@@ -154,8 +46,8 @@ export class CliCommandSchemaUtil {
154
46
  * Validate command shape with the given arguments
155
47
  */
156
48
  static async validate(cmd: CliCommandShape, args: unknown[]): Promise<typeof cmd> {
157
- const cls = CliCommandRegistry.getClass(cmd);
158
- const paramNames = SchemaRegistry.getMethodSchema(cls, 'main').map(x => x.name);
49
+ const cls = getClass(cmd);
50
+ const paramNames = SchemaRegistryIndex.getMethodConfig(cls, 'main').parameters.map(x => x.name!);
159
51
 
160
52
  const validators = [
161
53
  (): Promise<void> => SchemaValidator.validate(cls, cmd).then(() => { }),
@@ -168,7 +60,7 @@ export class CliCommandSchemaUtil {
168
60
  },
169
61
  ];
170
62
 
171
- const SOURCES = ['flag', 'arg', 'custom'] as const;
63
+ const SOURCES = ['flag', 'arg', 'custom'];
172
64
 
173
65
  const results = validators.map((x, i) => x().catch(err => {
174
66
  if (!(err instanceof CliValidationResultError) && !(err instanceof ValidationResultError)) {
package/src/service.ts CHANGED
@@ -68,7 +68,7 @@ export class ServiceRunner {
68
68
  }
69
69
 
70
70
  async #hasImage(): Promise<boolean> {
71
- const result = await ExecUtil.getResult(spawn('docker', ['image', 'inspect', this.svc.image], { shell: false }), { catch: true });
71
+ const result = await ExecUtil.getResult(spawn('docker', ['image', 'inspect', this.svc.image]), { catch: true });
72
72
  return result.valid;
73
73
  }
74
74
 
@@ -96,15 +96,15 @@ export class ServiceRunner {
96
96
  await fs.mkdir(item, { recursive: true });
97
97
  }
98
98
 
99
- return (await ExecUtil.getResult(spawn('docker', args, { shell: false, stdio: [0, 'pipe', 2] }))).stdout;
99
+ return (await ExecUtil.getResult(spawn('docker', args, { stdio: [0, 'pipe', 2] }))).stdout;
100
100
  }
101
101
 
102
102
  async #getContainerId(): Promise<string | undefined> {
103
- return (await ExecUtil.getResult(spawn('docker', ['ps', '-q', '--filter', `label=trv-${this.svc.name}`], { shell: false }))).stdout.trim();
103
+ return (await ExecUtil.getResult(spawn('docker', ['ps', '-q', '--filter', `label=trv-${this.svc.name}`]))).stdout.trim();
104
104
  }
105
105
 
106
106
  async #killContainer(cid: string): Promise<void> {
107
- await ExecUtil.getResult(spawn('docker', ['kill', cid], { shell: false }));
107
+ await ExecUtil.getResult(spawn('docker', ['kill', cid]));
108
108
  }
109
109
 
110
110
  async * action(op: ServiceAction): AsyncIterable<['success' | 'failure' | 'message', string]> {