@travetto/cli 8.0.0-alpha.1 → 8.0.0-alpha.10

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 CHANGED
@@ -59,7 +59,7 @@ This module also has a tight integration with the [VSCode plugin](https://market
59
59
  At it's heart, a cli command is the contract defined by what flags, and what arguments the command supports. Within the framework this requires three criteria to be met:
60
60
  * The file must be located in the `support/` folder, and have a name that matches `cli.*.ts`
61
61
  * The file must be a class that has a main method
62
- * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator
62
+ * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L20) decorator
63
63
 
64
64
  **Code: Basic Command**
65
65
  ```typescript
@@ -94,7 +94,7 @@ Examples of mappings:
94
94
  The pattern is that underscores(_) translate to colons (:), and the `cli.` prefix, and `.ts` suffix are dropped.
95
95
 
96
96
  ## Binding Flags
97
- [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) class. The fields of the class represent the flags that are available to the command.
97
+ [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L20) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L20) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) class. The fields of the class represent the flags that are available to the command.
98
98
 
99
99
  **Code: Basic Command with Flag**
100
100
  ```typescript
@@ -131,7 +131,7 @@ $ trv basic:flag --loud
131
131
  HELLO
132
132
  ```
133
133
 
134
- The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) supports the following data types for flags:
134
+ The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L20) supports the following data types for flags:
135
135
  * Boolean values
136
136
  * Number values. The [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L172), [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L179), [@Precision](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L165), [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99) and [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110) decorators help provide additional validation.
137
137
  * String values. [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99), [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110), [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L90) and [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L64) provide additional constraints
@@ -390,7 +390,7 @@ npx trv call:db --host localhost --port 3306 --username app --password <custom>
390
390
  ```
391
391
 
392
392
  ## VSCode Integration
393
- By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative support for creating Web Applications") does expose a cli target `web:http` that will show up, to help run/debug a web application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) decorator. This means the target will be visible within the editor tooling.
393
+ By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative support for creating Web Applications") does expose a cli target `web:http` that will show up, to help run/debug a web application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L20) decorator. This means the target will be visible within the editor tooling.
394
394
 
395
395
  **Code: Simple Run Target**
396
396
  ```typescript
@@ -460,7 +460,7 @@ export class WebHttpCommand implements CliCommandShape {
460
460
  profile: string[];
461
461
 
462
462
  @CliRestartOnChangeFlag()
463
- restartOnChange: boolean = true;
463
+ restartOnChange: boolean = Runtime.localDevelopment;
464
464
 
465
465
  @CliDebugIpcFlag()
466
466
  debugIpc?: boolean;
@@ -542,7 +542,7 @@ A simple example of the validation can be found in the `doc` command:
542
542
  ```typescript
543
543
  @Validator(async (cmd) => {
544
544
  const docFile = path.resolve(cmd.input);
545
- if (!(await fs.stat(docFile).catch(() => false))) {
545
+ if (!(await fs.stat(docFile, { throwIfNoEntry: false }))) {
546
546
  return { message: `input: ${cmd.input} does not exist`, path: 'input', source: 'flag', kind: 'invalid' };
547
547
  }
548
548
  })
@@ -558,7 +558,8 @@ $ trv service --help
558
558
  Usage: service [options] <action:restart|start|status|stop> [services...:string]
559
559
 
560
560
  Options:
561
- -h, --help display help for command
561
+ -q, --quiet (default: false)
562
+ -h, --help display help for command
562
563
 
563
564
  Available Services
564
565
  --------------------
package/__index__.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { } from './src/trv.d.ts';
2
2
  export * from './src/types.ts';
3
3
  export * from './src/execute.ts';
4
- export * from './src/error.ts';
5
4
  export * from './src/schema.ts';
6
5
  export * from './src/schema-export.ts';
7
6
  export * from './src/registry/decorator.ts';
package/bin/trv.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
+ import '@travetto/runtime/support/patch.js';
3
4
  import '@travetto/compiler/bin/hook.js';
4
5
  const { invoke } = await import('@travetto/compiler/support/invoke.ts');
5
6
  await invoke('exec', ['@travetto/cli/support/entry.trv.ts', ...process.argv.slice(2)]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "version": "8.0.0-alpha.1",
3
+ "version": "8.0.0-alpha.10",
4
4
  "type": "module",
5
5
  "description": "CLI infrastructure for Travetto framework",
6
6
  "keywords": [
@@ -29,8 +29,8 @@
29
29
  "directory": "module/cli"
30
30
  },
31
31
  "dependencies": {
32
- "@travetto/schema": "^8.0.0-alpha.1",
33
- "@travetto/terminal": "^8.0.0-alpha.1"
32
+ "@travetto/schema": "^8.0.0-alpha.5",
33
+ "@travetto/terminal": "^8.0.0-alpha.5"
34
34
  },
35
35
  "travetto": {
36
36
  "displayName": "Command Line Interface",
package/src/execute.ts CHANGED
@@ -3,7 +3,6 @@ import { ConsoleManager, getClass, Runtime, ShutdownManager, Util } from '@trave
3
3
  import { HelpUtil } from './help.ts';
4
4
  import { CliCommandRegistryIndex } from './registry/registry-index.ts';
5
5
  import { CliCommandSchemaUtil } from './schema.ts';
6
- import { CliUnknownCommandError, CliValidationResultError } from './error.ts';
7
6
  import { CliParseUtil } from './parse.ts';
8
7
  import type { CliCommandShape } from './types.ts';
9
8
 
@@ -12,51 +11,52 @@ import type { CliCommandShape } from './types.ts';
12
11
  */
13
12
  export class ExecutionManager {
14
13
 
15
- /** Error handler */
16
- static async #onError(error: unknown): Promise<void> {
17
- process.exitCode ??= 1;
18
- if (error instanceof CliValidationResultError) {
19
- console.error!(HelpUtil.renderValidationError(error));
20
- console.error!(await HelpUtil.renderCommandHelp(error.command));
21
- } else if (error instanceof CliUnknownCommandError) {
22
- if (error.help) {
23
- console.error!(error.help);
24
- } else {
25
- console.error!(error.defaultMessage, '\n');
26
- console.error!(await HelpUtil.renderAllHelp(''));
27
- }
28
- } else {
29
- console.error!(error);
14
+ /** Command Execution */
15
+ static async execute(instance: CliCommandShape, args: unknown[]): Promise<void> {
16
+ const config = CliCommandRegistryIndex.get(getClass(instance));
17
+
18
+ for (const item of config.preMain) {
19
+ await item.handler(instance);
30
20
  }
31
- console.error!();
21
+
22
+ // Wait 50ms to allow stdout to flush on shutdown
23
+ ShutdownManager.signal.addEventListener('abort', () => Util.blockingTimeout(50));
24
+ ConsoleManager.debug(Runtime.debug);
25
+ await instance.main(...args);
32
26
  }
33
27
 
34
- /** Bind command */
35
- static async #bindCommand(cmd: string, args: string[]): Promise<{ command: CliCommandShape, boundArgs: unknown[] }> {
36
- const [{ instance: command, schema }] = await CliCommandRegistryIndex.load([cmd]);
37
- const fullArgs = await CliParseUtil.expandArgs(schema, args);
28
+ /** Extract configuration and show help as needed */
29
+ static async getExecutionCommand(argv: string[]): Promise<(() => Promise<void>) | undefined> {
30
+ let command: CliCommandShape | undefined;
38
31
 
39
- const state = await CliParseUtil.parse(schema, fullArgs);
40
- CliParseUtil.setState(command, state);
32
+ const { cmd, args, help } = CliParseUtil.getArgs(argv);
33
+ if (!cmd) {
34
+ console.info!(await HelpUtil.renderAllHelp());
35
+ return;
36
+ }
41
37
 
42
- const boundArgs = CliCommandSchemaUtil.bindInput(command, state);
43
- return { command, boundArgs };
44
- }
38
+ try {
39
+ const [{ instance, schema }] = await CliCommandRegistryIndex.load([cmd]);
40
+ command = instance;
41
+ const fullArgs = await CliParseUtil.expandArgs(schema, args);
45
42
 
46
- /** Run command */
47
- static async #runCommand(cmd: string, args: string[]): Promise<void> {
48
- const { command, boundArgs } = await this.#bindCommand(cmd, args);
43
+ const state = await CliParseUtil.parse(schema, fullArgs);
44
+ CliParseUtil.setState(instance, state);
49
45
 
50
- await CliCommandSchemaUtil.validate(command, boundArgs);
51
- const config = CliCommandRegistryIndex.get(getClass(command));
46
+ const boundArgs = CliCommandSchemaUtil.bindInput(instance, state);
47
+ await instance.finalize?.(help);
52
48
 
53
- for (const preMain of config.preMain ?? []) {
54
- await preMain(command);
55
- }
56
- await command.finalize?.();
49
+ if (help) {
50
+ console.log!(await HelpUtil.renderCommandHelp(instance));
51
+ return;
52
+ }
57
53
 
58
- ConsoleManager.debug(Runtime.debug);
59
- await command.main(...boundArgs);
54
+ await CliCommandSchemaUtil.validate(command, boundArgs);
55
+
56
+ return this.execute.bind(this, instance, boundArgs);
57
+ } catch (error) {
58
+ await HelpUtil.renderError(error, cmd, command);
59
+ }
60
60
  }
61
61
 
62
62
  /**
@@ -65,20 +65,10 @@ export class ExecutionManager {
65
65
  */
66
66
  static async run(argv: string[]): Promise<void> {
67
67
  try {
68
- // Wait 50ms to allow stdout to flush on shutdown
69
- ShutdownManager.signal.addEventListener('abort', () => Util.blockingTimeout(50));
70
-
71
- const { cmd, args, help } = CliParseUtil.getArgs(argv);
72
- if (!cmd) {
73
- console.info!(await HelpUtil.renderAllHelp());
74
- } else if (help) {
75
- const { command } = await this.#bindCommand(cmd, args);
76
- console.log!(await HelpUtil.renderCommandHelp(command));
77
- } else {
78
- await this.#runCommand(cmd, args);
79
- }
68
+ const execute = await this.getExecutionCommand(argv);
69
+ await execute?.();
80
70
  } catch (error) {
81
- await this.#onError(error);
71
+ console.error!(error);
82
72
  } finally {
83
73
  await ShutdownManager.shutdown();
84
74
  }
package/src/help.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import util from 'node:util';
2
2
 
3
- import { castKey, getClass, JSONUtil } from '@travetto/runtime';
4
- import { SchemaRegistryIndex } from '@travetto/schema';
3
+ import { castKey, getClass, JSONUtil, Runtime } from '@travetto/runtime';
4
+ import { SchemaRegistryIndex, ValidationResultError } from '@travetto/schema';
5
5
 
6
6
  import { cliTpl } from './color.ts';
7
7
  import type { CliCommandShape } from './types.ts';
8
- import { CliCommandRegistryIndex } from './registry/registry-index.ts';
9
- import type { CliValidationResultError } from './error.ts';
8
+ import { CliCommandRegistryIndex, UNKNOWN_COMMAND } from './registry/registry-index.ts';
10
9
  import { CliSchemaExportUtil } from './schema-export.ts';
11
10
 
12
11
  const validationSourceMap: Record<string, string> = {
@@ -17,11 +16,39 @@ const validationSourceMap: Record<string, string> = {
17
16
  const ifDefined = <T>(value: T | null | '' | undefined): T | undefined =>
18
17
  (value === null || value === '' || value === undefined) ? undefined : value;
19
18
 
19
+ const toItem = (name: string, pkg: string, prod?: boolean) => [name, Runtime.getInstallCommand(pkg, prod)] as const;
20
+
21
+ const INSTALL_COMMANDS = new Map<string, string>([
22
+ ...['test', 'test:watch', 'test:direct'].map(item => toItem(item, '@travetto/test')),
23
+ ...['lint', 'lint:register', 'eslint', 'eslint:register'].map(item => toItem(item, '@travetto/eslint')),
24
+ ...['model:install', 'model:export'].map(item => toItem(item, '@travetto/model', true)),
25
+ ...['openapi:spec', 'openapi:client'].map(item => toItem(item, '@travetto/openapi', true)),
26
+ ...['email:compile', 'email:test', 'email:editor'].map(item => toItem(item, '@travetto/email-compiler')),
27
+ ...['pack', 'pack:zip', 'pack:docker'].map(item => toItem(item, '@travetto/pack')),
28
+ ...['repo:publish', 'repo:version', 'repo:exec', 'repo:list', 'repo:version-sync'].map(item => toItem(item, '@travetto/repo')),
29
+ toItem('web:http', '@travetto/web-http', true),
30
+ toItem('doc', '@travetto/doc'),
31
+ toItem('web:rpc-client', '@travetto/web-rpc', true),
32
+ ]);
33
+
20
34
  /**
21
35
  * Utilities for showing help
22
36
  */
23
37
  export class HelpUtil {
24
38
 
39
+ /** Render the unknown command message */
40
+ static renderUnknownCommandMessage(cmd: string): string {
41
+ const install = INSTALL_COMMANDS.get(cmd);
42
+ if (install) {
43
+ return cliTpl`
44
+ ${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
45
+ ${{ identifier: install }}
46
+ `;
47
+ } else {
48
+ return cliTpl`${{ subtitle: 'Unknown command' }}: ${{ input: cmd }}`;
49
+ }
50
+ }
51
+
25
52
  /**
26
53
  * Render command-specific help
27
54
  * @param command
@@ -31,8 +58,6 @@ export class HelpUtil {
31
58
  const { name: commandName } = CliCommandRegistryIndex.get(getClass(command));
32
59
  const args = schema.methods.main?.parameters ?? [];
33
60
 
34
- await command.finalize?.(true);
35
-
36
61
  // Ensure finalized
37
62
 
38
63
  const usage: string[] = [cliTpl`${{ title: 'Usage:' }} ${{ param: commandName }} ${{ input: '[options]' }}`,];
@@ -132,7 +157,7 @@ export class HelpUtil {
132
157
  /**
133
158
  * Render validation error to a string
134
159
  */
135
- static renderValidationError(validationError: CliValidationResultError): string {
160
+ static renderValidationError(validationError: ValidationResultError): string {
136
161
  return [
137
162
  cliTpl`${{ failure: 'Execution failed' }}:`,
138
163
  ...validationError.details.errors.map(error => {
@@ -144,4 +169,20 @@ export class HelpUtil {
144
169
  '',
145
170
  ].join('\n');
146
171
  }
172
+
173
+ /** Error handler */
174
+ static async renderError(error: unknown, cmd: string, command?: CliCommandShape): Promise<void> {
175
+ process.exitCode ??= 1;
176
+ if (error instanceof ValidationResultError) {
177
+ console.error!(this.renderValidationError(error));
178
+ } else if (error instanceof Error) {
179
+ console.error!(cliTpl`${{ failure: error.stack }}\n`);
180
+ }
181
+ if (command) {
182
+ console.error!(await this.renderCommandHelp(command));
183
+ } else if (error === UNKNOWN_COMMAND) {
184
+ console.error!(this.renderUnknownCommandMessage(cmd));
185
+ }
186
+ console.error!();
187
+ }
147
188
  }
package/src/parse.ts CHANGED
@@ -95,7 +95,7 @@ export class CliParseUtil {
95
95
 
96
96
  const file = path.resolve(relativePath);
97
97
 
98
- if (!await fs.stat(file).catch(() => false)) {
98
+ if (!await fs.stat(file, { throwIfNoEntry: false })) {
99
99
  throw new Error(`Missing flag file: ${key}, unable to proceed`);
100
100
  }
101
101
 
@@ -10,13 +10,6 @@ import { CliUtil } from '../util.ts';
10
10
  type CliCommandConfigOptions = { runTarget?: boolean };
11
11
  type CliFlagOptions = { full?: string, short?: string, envVars?: string[] };
12
12
 
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
- }
19
-
20
13
  /**
21
14
  * Decorator to register a CLI command
22
15
  *
@@ -72,9 +65,7 @@ export function CliProfilesFlag(config: CliFlagOptions = {}) {
72
65
  description: 'Application profiles'
73
66
  });
74
67
 
75
- runBeforeMain(cls, (cmd: typeof instance) =>
76
- Env.TRV_PROFILES.set([...cmd[property] ?? [], ...(Env.TRV_PROFILES.list ?? [])])
77
- );
68
+ CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 1, cmd => Env.TRV_PROFILES.add(...cmd[property] ?? []));
78
69
  };
79
70
  };
80
71
 
@@ -103,12 +94,8 @@ export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'co
103
94
  const runModule = (config.scope === 'command' ? commandModule : providedModule) || Runtime.main.name;
104
95
 
105
96
  // 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 };
111
- }
97
+ if (runModule !== Runtime.main.name && RuntimeIndex.getModule(runModule) === undefined) {
98
+ return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: property };
112
99
  }
113
100
 
114
101
  if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
@@ -116,6 +103,15 @@ export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'co
116
103
  }
117
104
  }],
118
105
  });
106
+
107
+ CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 5, cmd => {
108
+ const typed: (typeof cmd) & { [property]?: string } = castTo(cmd);
109
+ const providedModule = typed[property];
110
+ const runModule = (config.scope === 'command' ? commandModule : providedModule) || Runtime.main.name;
111
+ if (runModule !== Runtime.main.name) {
112
+ RuntimeIndex.reinitForModule(runModule);
113
+ }
114
+ });
119
115
  };
120
116
  }
121
117
 
@@ -127,14 +123,15 @@ export function CliModuleFlag(config: CliFlagOptions & { scope?: 'current' | 'co
127
123
  export function CliRestartOnChangeFlag(config: CliFlagOptions = {}) {
128
124
  return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
129
125
  const cls = getClass(instance);
126
+ if (Runtime.production) { return; }
127
+
130
128
  SchemaRegistryIndex.getForRegister(cls).registerField(property, {
131
129
  ...CliParseUtil.buildAliases(config),
132
- description: 'Should the invocation automatically restart on source changes',
133
- default: Runtime.localDevelopment,
134
- required: { active: false },
130
+ description: 'Should the invocation automatically restart on source changes'
135
131
  });
136
132
 
137
- runBeforeMain(cls, (cmd: typeof instance) => CliUtil.runWithRestartOnChange(cmd[property]), true);
133
+ CliCommandRegistryIndex.getForRegister(cls).register({ runTarget: true });
134
+ CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 20, cmd => CliUtil.runWithRestartOnChange(cmd[property]));
138
135
  };
139
136
  }
140
137
 
@@ -145,20 +142,18 @@ export function CliRestartOnChangeFlag(config: CliFlagOptions = {}) {
145
142
  */
146
143
  export function CliDebugIpcFlag(config: CliFlagOptions = {}) {
147
144
  return function <K extends string, T extends Partial<Record<K, boolean>>>(instance: T, property: K): void {
145
+ if (Runtime.production) { return; }
146
+
148
147
  const cls = getClass(instance);
149
148
  SchemaRegistryIndex.getForRegister(cls).registerField(property, {
150
149
  ...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 },
150
+ description: 'Should the invocation automatically restart on source changes'
154
151
  });
155
152
 
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
- );
153
+ CliCommandRegistryIndex.getForRegister(cls).register({ runTarget: true });
154
+ CliCommandRegistryIndex.registerPreMain<typeof instance>(cls, 10, cmd => {
155
+ const cliConfig = CliCommandRegistryIndex.get(cls);
156
+ return cmd[property] && CliUtil.runWithDebugIpc(cliConfig.name);
157
+ });
163
158
  };
164
159
  }
@@ -29,7 +29,7 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
29
29
  this.#cls = cls;
30
30
  }
31
31
 
32
- finalize(): void {
32
+ finalize(parent?: CliCommandConfig): void {
33
33
  // Add help command
34
34
  const schema = SchemaRegistryIndex.getConfig(this.#cls);
35
35
 
@@ -75,6 +75,13 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
75
75
  aliases.push(`--no-${long}`);
76
76
  }
77
77
  }
78
+
79
+ if (parent) {
80
+ this.#config.preMain = [...this.#config.preMain, ...parent?.preMain ?? []];
81
+ }
82
+
83
+ // Sort
84
+ this.#config.preMain = this.#config.preMain.toSorted((left, right) => left.priority - right.priority);
78
85
  }
79
86
 
80
87
  get(): CliCommandConfig {
@@ -86,7 +93,7 @@ export class CliCommandRegistryAdapter implements RegistryAdapter<CliCommandConf
86
93
  */
87
94
  register(...configs: Partial<CliCommandConfig>[]): CliCommandConfig {
88
95
  const metadata = describeFunction(this.#cls);
89
- this.#config ??= { cls: this.#cls, name: getName(metadata.import), preMain: [], runTarget: true };
96
+ this.#config ??= { cls: this.#cls, name: getName(metadata.import), preMain: [], runTarget: false };
90
97
  return combineClasses(this.#config, ...configs);
91
98
  }
92
99
 
@@ -1,9 +1,8 @@
1
- import { type Class, getClass, getParentClass, isClass, Runtime, RuntimeIndex } from '@travetto/runtime';
1
+ import { type Any, type Class, getClass, getParentClass, isClass, Runtime, RuntimeIndex } from '@travetto/runtime';
2
2
  import { type RegistryAdapter, type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
3
3
  import { type SchemaClassConfig, SchemaRegistryIndex } from '@travetto/schema';
4
4
 
5
- import type { CliCommandConfig, CliCommandShape } from '../types.ts';
6
- import { CliUnknownCommandError } from '../error.ts';
5
+ import type { CliCommandConfig, CliCommandShape, PreMainHandler } from '../types.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);
@@ -27,6 +28,10 @@ export class CliCommandRegistryIndex implements RegistryIndex {
27
28
  return this.#instance.load(names);
28
29
  }
29
30
 
31
+ static registerPreMain<T = Any>(cls: Class, priority: number, handler: PreMainHandler<T>['handler']): void {
32
+ CliCommandRegistryIndex.getForRegister(cls).register({ preMain: [{ handler, priority }] });
33
+ }
34
+
30
35
  #fileMapping: Map<string, string>;
31
36
  #instanceMapping: Map<string, CliCommandShape> = new Map();
32
37
 
@@ -57,7 +62,7 @@ export class CliCommandRegistryIndex implements RegistryIndex {
57
62
  */
58
63
  async #getInstance(name: string): Promise<CliCommandShape> {
59
64
  if (!this.hasCommand(name)) {
60
- throw new CliUnknownCommandError(name);
65
+ throw UNKNOWN_COMMAND;
61
66
  }
62
67
 
63
68
  if (this.#instanceMapping.has(name)) {
@@ -95,7 +100,7 @@ export class CliCommandRegistryIndex implements RegistryIndex {
95
100
  this.#instanceMapping.set(name, result);
96
101
  return result;
97
102
  }
98
- throw new CliUnknownCommandError(name);
103
+ throw UNKNOWN_COMMAND;
99
104
  }
100
105
 
101
106
  hasCommand(name: string): boolean {
@@ -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
@@ -2,7 +2,6 @@ import { castKey, castTo, getClass } from '@travetto/runtime';
2
2
  import { BindUtil, SchemaRegistryIndex, SchemaValidator, ValidationResultError, type ValidationError } from '@travetto/schema';
3
3
 
4
4
  import type { ParsedState, CliCommandShape } from './types.ts';
5
- import { CliValidationResultError } from './error.ts';
6
5
 
7
6
  const getSource = (source: string | undefined, defaultSource: ValidationError['source']): ValidationError['source'] => {
8
7
  switch (source) {
@@ -15,7 +14,7 @@ const getSource = (source: string | undefined, defaultSource: ValidationError['s
15
14
  };
16
15
 
17
16
  const transformErrors = (source: 'arg' | 'flag', error: unknown): ValidationError[] => {
18
- if (error instanceof CliValidationResultError || error instanceof ValidationResultError) {
17
+ if (error instanceof ValidationResultError) {
19
18
  return error.details.errors.map(value => ({ source: getSource(value.source, source), ...value }));
20
19
  } else {
21
20
  throw error;
@@ -32,16 +31,16 @@ export class CliCommandSchemaUtil {
32
31
  /**
33
32
  * Bind parsed inputs to command
34
33
  */
35
- static bindInput<T extends CliCommandShape>(cmd: T, state: ParsedState): unknown[] {
34
+ static bindInput<T extends CliCommandShape>(command: T, state: ParsedState): unknown[] {
36
35
  const template: Partial<T> = {};
37
36
  const bound: unknown[] = [];
38
37
 
39
- for (const arg of state.all) {
40
- switch (arg.type) {
38
+ for (const item of state.all) {
39
+ switch (item.type) {
41
40
  case 'flag': {
42
- const key = castKey<T>(arg.fieldName);
43
- const value = arg.value!;
44
- if (arg.array) {
41
+ const key = castKey<T>(item.fieldName);
42
+ const value = item.value!;
43
+ if (item.array) {
45
44
  castTo<unknown[]>(template[key] ??= castTo([])).push(value);
46
45
  } else {
47
46
  template[key] = castTo(value);
@@ -49,36 +48,36 @@ export class CliCommandSchemaUtil {
49
48
  break;
50
49
  }
51
50
  case 'arg': {
52
- if (arg.array) {
53
- castTo<unknown[]>(bound[arg.index] ??= []).push(arg.input);
51
+ if (item.array) {
52
+ castTo<unknown[]>(bound[item.index] ??= []).push(item.input);
54
53
  } else {
55
- bound[arg.index] = arg.input;
54
+ bound[item.index] = item.input;
56
55
  }
57
56
  }
58
57
  }
59
58
  }
60
59
 
61
- const cls = getClass(cmd);
62
- BindUtil.bindSchemaToObject(cls, cmd, template);
60
+ const cls = getClass(command);
61
+ BindUtil.bindSchemaToObject(cls, command, template);
63
62
  return BindUtil.coerceMethodParams(cls, 'main', bound);
64
63
  }
65
64
 
66
65
  /**
67
66
  * Validate command shape with the given arguments
68
67
  */
69
- static async validate(cmd: CliCommandShape, args: unknown[]): Promise<typeof cmd> {
70
- const cls = getClass(cmd);
68
+ static async validate(command: CliCommandShape, args: unknown[]): Promise<typeof command> {
69
+ const cls = getClass(command);
71
70
  const paramNames = SchemaRegistryIndex.get(cls).getMethod('main').parameters.map(config => config.name!);
72
71
 
73
72
  const results = await Promise.all([
74
- SchemaValidator.validate(cls, cmd).then(() => [], transformFlagErrors),
73
+ SchemaValidator.validate(cls, command).then(() => [], transformFlagErrors),
75
74
  SchemaValidator.validateMethod(cls, 'main', args, paramNames).then(() => [], transformArgErrors),
76
75
  ]);
77
76
 
78
77
  const errors = results.flat();
79
78
  if (errors.length) {
80
- throw new CliValidationResultError(cmd, errors);
79
+ throw new ValidationResultError(errors);
81
80
  }
82
- return cmd;
81
+ return command;
83
82
  }
84
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
@@ -1,4 +1,4 @@
1
- import type { Class } from '@travetto/runtime';
1
+ import type { Any, Class } from '@travetto/runtime';
2
2
 
3
3
  type OrProm<T> = T | Promise<T>;
4
4
  type ParsedFlag = { type: 'flag', input: string, array?: boolean, fieldName: string, value?: unknown };
@@ -32,7 +32,7 @@ export interface CliCommandShape {
32
32
  help?(): OrProm<string[]>;
33
33
  }
34
34
 
35
- type PreMainHandler = (cmd: CliCommandShape) => (unknown | Promise<unknown>);
35
+ export type PreMainHandler<T extends Any = Any> = { priority: number, handler: (cmd: T) => Any };
36
36
 
37
37
  /**
38
38
  * CLI Command schema shape
@@ -41,5 +41,5 @@ export interface CliCommandConfig {
41
41
  cls: Class<CliCommandShape>;
42
42
  name: string;
43
43
  runTarget?: boolean;
44
- preMain?: PreMainHandler[];
44
+ preMain: PreMainHandler[];
45
45
  }
package/src/util.ts CHANGED
@@ -2,11 +2,12 @@ import { spawn, type ChildProcess } from 'node:child_process';
2
2
 
3
3
  import { RuntimeError, JSONUtil, Env, ExecUtil, Runtime, ShutdownManager, Util, WatchUtil } from '@travetto/runtime';
4
4
 
5
- const IPC_ALLOWED_ENV = new Set(['NODE_OPTIONS']);
6
- const IPC_INVALID_ENV = new Set(['PS1', 'INIT_CWD', 'COLOR', 'LANGUAGE', 'PROFILEHOME', '_']);
7
- const validEnv = (key: string): boolean => IPC_ALLOWED_ENV.has(key) || (
8
- !IPC_INVALID_ENV.has(key) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(key) && !/VSCODE/.test(key)
9
- );
5
+ const IPC_VALID_ENV = new Set(['NODE_OPTIONS', 'PATH', Env.DEBUG.key, Env.NODE_ENV.key]);
6
+ const IPC_INVALID_ENV = new Set([
7
+ Env.TRV_CLI_IPC, Env.TRV_DEBUG_IPC, Env.TRV_DEBUG_BREAK, Env.TRV_MANIFEST, Env.TRV_MODULE, Env.TRV_RESTART_TARGET
8
+ ].map(item => item.key));
9
+ const validEnv = ([key]: [key: string, value: unknown]): boolean =>
10
+ IPC_VALID_ENV.has(key) || (key.startsWith('TRV_') && !IPC_INVALID_ENV.has(key));
10
11
 
11
12
  export class CliUtil {
12
13
  /**
@@ -14,8 +15,7 @@ export class CliUtil {
14
15
  */
15
16
  static getSimpleModuleName(placeholder: string, module?: string): string {
16
17
  const simple = (module ?? Runtime.main.name).replace(/[\/]/, '_').replace(/@/, '');
17
- const targetModule = !simple || (!module && Runtime.monoRoot) ? '<module>' : simple;
18
- return placeholder.replace('<module>', targetModule);
18
+ return simple ? placeholder.replace('<module>', simple) : placeholder;
19
19
  }
20
20
 
21
21
  /**
@@ -33,10 +33,11 @@ export class CliUtil {
33
33
  ShutdownManager.disableInterrupt();
34
34
 
35
35
  let child: ChildProcess | undefined;
36
- void WatchUtil.watchCompilerEvents('file', () => ShutdownManager.shutdownChild(child!, { reason: 'restart', mode: 'exit' }));
36
+ await WatchUtil.watchCompilerEvents('file', () => ShutdownManager.shutdownChild(child!, { reason: 'restart', mode: 'exit' }));
37
+
37
38
  process
38
39
  .on('SIGINT', () => ShutdownManager.shutdownChild(child!, { mode: 'exit' }))
39
- .on('message', msg => child?.send?.(msg!));
40
+ .on('message', message => child?.send?.(message!));
40
41
 
41
42
  const env = { ...process.env, ...Env.TRV_RESTART_TARGET.export(true) };
42
43
 
@@ -77,32 +78,32 @@ export class CliUtil {
77
78
  return; // Server not running, run normal
78
79
  }
79
80
 
80
- const env: Record<string, string> = {};
81
81
  const request = {
82
82
  type: '@travetto/cli:run',
83
83
  data: {
84
84
  name,
85
- env,
85
+ env: Object.fromEntries(Object.entries(process.env).filter(validEnv)),
86
86
  cwd: process.cwd(),
87
87
  args: process.argv.slice(3),
88
88
  }
89
89
  };
90
- console.log('Triggering IPC request', request);
91
90
 
92
- Object.entries(process.env).forEach(([key, value]) => validEnv(key) && (env[key] = value!));
91
+ console.log('Triggering IPC request', request);
93
92
  const sent = await doFetch({ method: 'POST', body: JSONUtil.toUTF8(request) });
94
93
 
95
94
  if (!sent.ok) {
96
95
  throw new RuntimeError(`IPC Request failed: ${sent.status} ${await sent.text()}`);
97
96
  }
97
+
98
+ await ShutdownManager.shutdown({ mode: 'exit' });
98
99
  }
99
100
 
100
101
  /**
101
102
  * Write data to channel and ensure its flushed before continuing
102
103
  */
103
104
  static async writeAndEnsureComplete(data: unknown, channel: 'stdout' | 'stderr' = 'stdout'): Promise<void> {
104
- return await new Promise(resolve => process[channel].write(typeof data === 'string' ? data :
105
- JSONUtil.toUTF8Pretty(data), () => resolve()));
105
+ await new Promise<unknown>(resolve => process[channel].write(typeof data === 'string' ? data :
106
+ JSONUtil.toUTF8Pretty(data), resolve));
106
107
  }
107
108
 
108
109
  /**
@@ -1,13 +1,14 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+
1
3
  import { type CliCommandShape, CliCommand, cliTpl } from '@travetto/cli';
2
4
  import { Terminal } from '@travetto/terminal';
3
5
  import { AsyncQueue, Util } from '@travetto/runtime';
4
6
  import { MethodValidator, type ValidationError } from '@travetto/schema';
5
7
 
6
8
  import { ServiceRunner, type ServiceAction } from '../src/service.ts';
7
- import { getServices } from './bin/util.ts';
8
9
 
9
- async function validateService(action: ServiceAction, services: string[]): Promise<ValidationError | undefined> {
10
- const all = await getServices(services);
10
+ async function validateService(_: ServiceAction, services: string[]): Promise<ValidationError | undefined> {
11
+ const all = await ServiceRunner.findServices(services);
11
12
 
12
13
  if (!all.length) {
13
14
  return { message: 'No services found', source: 'arg', kind: 'invalid', path: 'services' };
@@ -20,8 +21,10 @@ async function validateService(action: ServiceAction, services: string[]): Promi
20
21
  @CliCommand()
21
22
  export class CliServiceCommand implements CliCommandShape {
22
23
 
24
+ quiet = false;
25
+
23
26
  async help(): Promise<string[]> {
24
- const all = await getServices([]);
27
+ const all = await ServiceRunner.findServices([]);
25
28
  return [
26
29
  cliTpl`${{ title: 'Available Services' }}`,
27
30
  '-'.repeat(20),
@@ -31,32 +34,48 @@ export class CliServiceCommand implements CliCommandShape {
31
34
 
32
35
  @MethodValidator(validateService)
33
36
  async main(action: ServiceAction, services: string[] = []): Promise<void> {
34
- const all = await getServices(services);
37
+ const all = await ServiceRunner.findServices(services);
35
38
  const maxName = Math.max(...all.map(service => service.name.length), 'Service'.length) + 3;
36
39
  const maxVersion = Math.max(...all.map(service => `${service.version}`.length), 'Version'.length) + 3;
37
40
  const maxStatus = 20;
38
41
  const queue = new AsyncQueue<{ idx: number, text: string, done?: boolean }>();
39
42
 
43
+ const failureMessages: string[] = [];
44
+
40
45
  const jobs = all.map(async (descriptor, i) => {
41
46
  const identifier = descriptor.name.padEnd(maxName);
42
47
  const type = `${descriptor.version}`.padStart(maxVersion - 3).padEnd(maxVersion);
43
- let msg: string;
48
+ let message: string;
44
49
  for await (const [valueType, value] of new ServiceRunner(descriptor).action(action)) {
45
50
  const details = { [valueType === 'message' ? 'subtitle' : valueType]: value };
46
- queue.add({ idx: i, text: msg = cliTpl`${{ identifier }} ${{ type }} ${details}` });
51
+ queue.add({ idx: i, text: message = cliTpl`${{ identifier }} ${{ type }} ${details}` });
52
+ if (valueType === 'failure') {
53
+ failureMessages.push(message);
54
+ }
47
55
  }
48
- queue.add({ idx: i, done: true, text: msg! });
56
+ queue.add({ idx: i, done: true, text: message! });
49
57
  });
50
58
 
51
59
  Promise.all(jobs).then(() => Util.queueMacroTask()).then(() => queue.close());
52
60
 
53
- const term = new Terminal();
54
- await term.writer.writeLines([
55
- '',
56
- cliTpl`${{ title: 'Service'.padEnd(maxName) }} ${{ title: 'Version'.padEnd(maxVersion) }} ${{ title: 'Status' }}`,
57
- ''.padEnd(maxName + maxVersion + maxStatus + 3, '-'),
58
- ]).commit();
59
61
 
60
- await term.streamList(queue);
62
+ if (this.quiet) {
63
+ for await (const _ of queue) { }
64
+ if (failureMessages.length) {
65
+ console.error('Failure');
66
+ failureMessages.map(stripVTControlCharacters).map(item => console.error(item));
67
+ }
68
+ } else {
69
+ const term = new Terminal();
70
+ await term.writer.writeLines([
71
+ '',
72
+ cliTpl`${{ title: 'Service'.padEnd(maxName) }} ${{ title: 'Version'.padEnd(maxVersion) }} ${{ title: 'Status' }}`,
73
+ ''.padEnd(maxName + maxVersion + maxStatus + 3, '-'),
74
+ ]).commit();
75
+
76
+ await term.streamList(queue);
77
+ }
78
+
79
+ process.exitCode = failureMessages.length ? 1 : 0;
61
80
  }
62
81
  }
@@ -1,4 +1,4 @@
1
1
  // @trv-no-transform
2
- import '@travetto/runtime/support/polyfill.js';
2
+ import '@travetto/runtime/support/patch.js';
3
3
  import { ExecutionManager } from '@travetto/cli';
4
4
  ExecutionManager.run(process.argv);
package/src/error.ts DELETED
@@ -1,59 +0,0 @@
1
- import { RuntimeError, Runtime } from '@travetto/runtime';
2
- import type { ValidationError } from '@travetto/schema';
3
-
4
- import { cliTpl } from './color.ts';
5
- import type { CliCommandShape } from './types.ts';
6
-
7
- const COMMAND_PACKAGE = [
8
- [/^test(:watch)?$/, 'test', false],
9
- [/^lint(:register)?$/, 'eslint', false],
10
- [/^model:(install|export)$/, 'model', true],
11
- [/^openapi:(spec|client)$/, 'openapi', true],
12
- [/^email:(compile|editor)$/, 'email-compiler', false],
13
- [/^pack(:zip|:docker)?$/, 'pack', false],
14
- [/^web:http$/, 'web-http', true],
15
- [/^web:rpc-client$/, 'web-rpc', true],
16
- ] as const;
17
-
18
- /**
19
- * Provides a contract for unknown commands
20
- */
21
- export class CliUnknownCommandError extends Error {
22
-
23
- #getMissingCommandHelp(cmd: string): string | undefined {
24
- const matchedConfig = COMMAND_PACKAGE.find(([regex]) => regex.test(cmd));
25
- if (matchedConfig) {
26
- const [, pkg, production] = matchedConfig;
27
- const install = Runtime.getInstallCommand(`@travetto/${pkg}`, production);
28
- return cliTpl`
29
- ${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
30
- ${{ identifier: install }}
31
- `;
32
- }
33
- }
34
-
35
- help?: string;
36
- cmd: string;
37
-
38
- constructor(cmd: string) {
39
- super(`Unknown command: ${cmd}`);
40
- this.cmd = cmd;
41
- this.help = this.#getMissingCommandHelp(cmd);
42
- }
43
-
44
- get defaultMessage(): string {
45
- return cliTpl`${{ subtitle: 'Unknown command' }}: ${{ input: this.cmd }}`;
46
- }
47
- }
48
-
49
- /**
50
- * Provides a basic error wrapper for cli validation issues
51
- */
52
- export class CliValidationResultError extends RuntimeError<{ errors: ValidationError[] }> {
53
- command: CliCommandShape;
54
-
55
- constructor(command: CliCommandShape, errors: ValidationError[]) {
56
- super('', { details: { errors } });
57
- this.command = command;
58
- }
59
- }
@@ -1,16 +0,0 @@
1
- import { RuntimeIndex, Runtime } from '@travetto/runtime';
2
- import type { ServiceDescriptor } from '../../__index__.ts';
3
-
4
- export async function getServices(services: string[]): Promise<ServiceDescriptor[]> {
5
- return (await Promise.all(
6
- RuntimeIndex.find({
7
- module: module => module.roles.includes('std'),
8
- folder: folder => folder === 'support',
9
- file: file => /support\/service[.]/.test(file.sourceFile)
10
- })
11
- .map(file => Runtime.importFrom<{ service: ServiceDescriptor }>(file.import).then(value => value.service))
12
- ))
13
- .filter(file => !!file)
14
- .filter(file => services?.length ? services.includes(file.name) : true)
15
- .toSorted((a, b) => a.name.localeCompare(b.name));
16
- }