@travetto/cli 6.0.1 → 7.0.0-rc.1

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,120 +1,27 @@
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, CliValidationError } 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
- }
7
+ const getSource = (source: string | undefined, def: CliValidationError['source']): CliValidationError['source'] => {
8
+ switch (source) {
9
+ case 'custom':
10
+ case 'arg':
11
+ case 'flag': return source;
12
+ case undefined: return def;
13
+ default: return 'custom';
28
14
  }
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
- });
15
+ };
43
16
 
44
17
  /**
45
18
  * Allows binding describing/binding inputs for commands
46
19
  */
47
20
  export class CliCommandSchemaUtil {
48
-
49
- /**
50
- * Get schema for a given command
51
- */
52
- static async getSchema(src: Class | CliCommandShape): Promise<CliCommandSchema> {
53
- const cls = 'main' in src ? CliCommandRegistry.getClass(src) : src;
54
-
55
- // Ensure finalized
56
- const parent = SchemaRegistry.getParentClass(cls);
57
- if (parent?.Ⲑid) {
58
- SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
59
- }
60
- SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
61
-
62
- const schema = await SchemaRegistry.getViewSchema(cls);
63
- const flags = Object.values(schema.schema).map(fieldToInput);
64
-
65
- // Add help command
66
- flags.push({ name: 'help', flagNames: ['h'], description: 'display help for command', type: 'boolean' });
67
-
68
- const method = SchemaRegistry.getMethodSchema(cls, 'main').map(fieldToInput);
69
-
70
- const used = new Set(flags
71
- .flatMap(f => f.flagNames ?? [])
72
- .filter(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)
73
- .map(x => x.replace(/^-+/, ''))
74
- );
75
-
76
- for (const flag of flags) {
77
- let short = (flag.flagNames ?? []).find(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)?.replace(/^-+/, '');
78
- const long = (flag.flagNames ?? []).find(x => LONG_FLAG.test(x) || x.replaceAll('-', '').length > 2)?.replace(/^-+/, '') ??
79
- flag.name.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
80
- const aliases: string[] = flag.flagNames = [];
81
-
82
- if (short === undefined) {
83
- if (!(isBoolFlag(flag) && flag.default === true)) {
84
- short = flag.name.charAt(0);
85
- if (!used.has(short)) {
86
- aliases.push(`-${short}`);
87
- used.add(short);
88
- }
89
- }
90
- } else {
91
- aliases.push(`-${short}`);
92
- }
93
-
94
- aliases.push(`--${long}`);
95
-
96
- if (isBoolFlag(flag)) {
97
- aliases.push(`--no-${long}`);
98
- }
99
- }
100
-
101
- const fullSchema = SchemaRegistry.get(cls);
102
- const { cls: _cls, preMain: _preMain, ...meta } = CliCommandRegistry.getByClass(cls)!;
103
- const cfg: CliCommandSchema = {
104
- ...meta,
105
- args: method,
106
- flags,
107
- title: fullSchema.title ?? cls.name,
108
- description: fullSchema.description ?? ''
109
- };
110
-
111
- return cfg;
112
- }
113
-
114
21
  /**
115
22
  * Bind parsed inputs to command
116
23
  */
117
- static async bindInput<T extends CliCommandShape>(cmd: T, state: ParsedState): Promise<unknown[]> {
24
+ static bindInput<T extends CliCommandShape>(cmd: T, state: ParsedState): unknown[] {
118
25
  const template: Partial<T> = {};
119
26
  const bound: unknown[] = [];
120
27
 
@@ -140,7 +47,7 @@ export class CliCommandSchemaUtil {
140
47
  }
141
48
  }
142
49
 
143
- const cls = CliCommandRegistry.getClass(cmd);
50
+ const cls = getClass(cmd);
144
51
  BindUtil.bindSchemaToObject(cls, cmd, template);
145
52
  return BindUtil.coerceMethodParams(cls, 'main', bound);
146
53
  }
@@ -149,8 +56,8 @@ export class CliCommandSchemaUtil {
149
56
  * Validate command shape with the given arguments
150
57
  */
151
58
  static async validate(cmd: CliCommandShape, args: unknown[]): Promise<typeof cmd> {
152
- const cls = CliCommandRegistry.getClass(cmd);
153
- const paramNames = SchemaRegistry.getMethodSchema(cls, 'main').map(x => x.name);
59
+ const cls = getClass(cmd);
60
+ const paramNames = SchemaRegistryIndex.get(cls).getMethod('main').parameters.map(x => x.name!);
154
61
 
155
62
  const validators = [
156
63
  (): Promise<void> => SchemaValidator.validate(cls, cmd).then(() => { }),
@@ -169,7 +76,7 @@ export class CliCommandSchemaUtil {
169
76
  if (!(err instanceof CliValidationResultError) && !(err instanceof ValidationResultError)) {
170
77
  throw err;
171
78
  }
172
- return err.details.errors.map(v => ({ source: SOURCES[i], ...v }));
79
+ return err.details.errors.map(v => ({ ...v, source: getSource(v.source, SOURCES[i]) }));
173
80
  }));
174
81
 
175
82
  const errors = (await Promise.all(results)).flatMap(x => (x ?? []));
package/src/types.ts CHANGED
@@ -6,18 +6,6 @@ type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
6
6
  type ParsedUnknown = { type: 'unknown', input: string };
7
7
  type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
8
8
 
9
- /**
10
- * Command configuration
11
- */
12
- export type CliCommandConfig = {
13
- name: string;
14
- commandModule: string;
15
- runTarget?: boolean;
16
- cls: Class<CliCommandShape>;
17
- hidden?: boolean;
18
- preMain?: (cmd: CliCommandShape) => void | Promise<void>;
19
- };
20
-
21
9
  export type ParsedState = {
22
10
  inputs: string[];
23
11
  all: ParsedInput[];
@@ -45,7 +33,6 @@ export interface CliValidationError {
45
33
  * @concrete
46
34
  */
47
35
  export interface CliCommandShape<T extends unknown[] = unknown[]> {
48
-
49
36
  /**
50
37
  * Parsed state
51
38
  */
@@ -110,31 +97,12 @@ export type CliCommandShapeFields = {
110
97
  module?: string;
111
98
  };
112
99
 
113
- /**
114
- * CLI Command argument/flag shape
115
- */
116
- export type CliCommandInput<K extends string = string> = {
117
- name: string;
118
- description?: string;
119
- type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex' | 'module';
120
- fileExtensions?: string[];
121
- choices?: unknown[];
122
- required?: boolean;
123
- array?: boolean;
124
- default?: unknown;
125
- flagNames?: K[];
126
- envVars?: string[];
127
- };
128
-
129
100
  /**
130
101
  * CLI Command schema shape
131
102
  */
132
- export interface CliCommandSchema<K extends string = string> {
103
+ export interface CliCommandConfig {
104
+ cls: Class<CliCommandShape>;
133
105
  name: string;
134
- title: string;
135
- commandModule: string;
136
106
  runTarget?: boolean;
137
- description?: string;
138
- args: CliCommandInput[];
139
- flags: CliCommandInput<K>[];
140
- }
107
+ preMain?: (cmd: CliCommandShape) => void | Promise<void>;
108
+ }