@travetto/cli 8.0.0-alpha.1 → 8.0.0-alpha.3
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 +2 -1
- package/__index__.ts +0 -1
- package/package.json +3 -3
- package/src/execute.ts +29 -57
- package/src/help.ts +48 -7
- package/src/registry/registry-index.ts +4 -3
- package/src/schema-export.ts +4 -3
- package/src/schema.ts +17 -18
- package/src/service.ts +18 -1
- package/support/cli.service.ts +31 -12
- package/src/error.ts +0 -59
- package/support/bin/util.ts +0 -16
package/README.md
CHANGED
|
@@ -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
|
-
-
|
|
561
|
+
-q, --quiet (default: false)
|
|
562
|
+
-h, --help display help for command
|
|
562
563
|
|
|
563
564
|
Available Services
|
|
564
565
|
--------------------
|
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cli",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.3",
|
|
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.
|
|
33
|
-
"@travetto/terminal": "^8.0.0-alpha.
|
|
32
|
+
"@travetto/schema": "^8.0.0-alpha.2",
|
|
33
|
+
"@travetto/terminal": "^8.0.0-alpha.2"
|
|
34
34
|
},
|
|
35
35
|
"travetto": {
|
|
36
36
|
"displayName": "Command Line Interface",
|
package/src/execute.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { ConsoleManager,
|
|
1
|
+
import { ConsoleManager, Runtime, ShutdownManager, Util } from '@travetto/runtime';
|
|
2
2
|
|
|
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,73 +11,46 @@ import type { CliCommandShape } from './types.ts';
|
|
|
12
11
|
*/
|
|
13
12
|
export class ExecutionManager {
|
|
14
13
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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);
|
|
30
|
-
}
|
|
31
|
-
console.error!();
|
|
32
|
-
}
|
|
33
|
-
|
|
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);
|
|
14
|
+
/**
|
|
15
|
+
* Execute the command line
|
|
16
|
+
* @param args
|
|
17
|
+
*/
|
|
18
|
+
static async run(argv: string[]): Promise<void> {
|
|
19
|
+
let command: CliCommandShape | undefined;
|
|
38
20
|
|
|
39
|
-
const
|
|
40
|
-
|
|
21
|
+
const { cmd, args, help } = CliParseUtil.getArgs(argv);
|
|
22
|
+
if (!cmd) {
|
|
23
|
+
return console.info!(await HelpUtil.renderAllHelp());
|
|
24
|
+
}
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
try {
|
|
27
|
+
const [{ instance, schema, config }] = await CliCommandRegistryIndex.load([cmd]);
|
|
28
|
+
command = instance;
|
|
29
|
+
const fullArgs = await CliParseUtil.expandArgs(schema, args);
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { command, boundArgs } = await this.#bindCommand(cmd, args);
|
|
31
|
+
const state = await CliParseUtil.parse(schema, fullArgs);
|
|
32
|
+
CliParseUtil.setState(instance, state);
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
const boundArgs = CliCommandSchemaUtil.bindInput(instance, state);
|
|
35
|
+
await instance.finalize?.(help);
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await command.finalize?.();
|
|
37
|
+
if (help) {
|
|
38
|
+
return console.log!(await HelpUtil.renderCommandHelp(instance));
|
|
39
|
+
}
|
|
57
40
|
|
|
58
|
-
|
|
59
|
-
await command.main(...boundArgs);
|
|
60
|
-
}
|
|
41
|
+
await CliCommandSchemaUtil.validate(command, boundArgs);
|
|
61
42
|
|
|
62
|
-
/**
|
|
63
|
-
* Execute the command line
|
|
64
|
-
* @param args
|
|
65
|
-
*/
|
|
66
|
-
static async run(argv: string[]): Promise<void> {
|
|
67
|
-
try {
|
|
68
43
|
// Wait 50ms to allow stdout to flush on shutdown
|
|
69
44
|
ShutdownManager.signal.addEventListener('abort', () => Util.blockingTimeout(50));
|
|
70
45
|
|
|
71
|
-
const
|
|
72
|
-
|
|
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);
|
|
46
|
+
for (const preMain of config.preMain ?? []) {
|
|
47
|
+
await preMain(instance);
|
|
79
48
|
}
|
|
49
|
+
|
|
50
|
+
ConsoleManager.debug(Runtime.debug);
|
|
51
|
+
await instance.main(...boundArgs);
|
|
80
52
|
} catch (error) {
|
|
81
|
-
await
|
|
53
|
+
await HelpUtil.renderError(error, cmd, command);
|
|
82
54
|
} finally {
|
|
83
55
|
await ShutdownManager.shutdown();
|
|
84
56
|
}
|
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:
|
|
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
|
+
}
|
|
179
|
+
if (command) {
|
|
180
|
+
console.error!(await this.renderCommandHelp(command));
|
|
181
|
+
} else if (error === UNKNOWN_COMMAND) {
|
|
182
|
+
console.error!(this.renderUnknownCommandMessage(cmd));
|
|
183
|
+
} else {
|
|
184
|
+
console.error!(error);
|
|
185
|
+
}
|
|
186
|
+
console.error!();
|
|
187
|
+
}
|
|
147
188
|
}
|
|
@@ -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)) {
|
|
@@ -95,7 +96,7 @@ export class CliCommandRegistryIndex implements RegistryIndex {
|
|
|
95
96
|
this.#instanceMapping.set(name, result);
|
|
96
97
|
return result;
|
|
97
98
|
}
|
|
98
|
-
throw
|
|
99
|
+
throw UNKNOWN_COMMAND;
|
|
99
100
|
}
|
|
100
101
|
|
|
101
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
|
@@ -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
|
|
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>(
|
|
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
|
|
40
|
-
switch (
|
|
38
|
+
for (const item of state.all) {
|
|
39
|
+
switch (item.type) {
|
|
41
40
|
case 'flag': {
|
|
42
|
-
const key = castKey<T>(
|
|
43
|
-
const value =
|
|
44
|
-
if (
|
|
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 (
|
|
53
|
-
castTo<unknown[]>(bound[
|
|
51
|
+
if (item.array) {
|
|
52
|
+
castTo<unknown[]>(bound[item.index] ??= []).push(item.input);
|
|
54
53
|
} else {
|
|
55
|
-
bound[
|
|
54
|
+
bound[item.index] = item.input;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
const cls = getClass(
|
|
62
|
-
BindUtil.bindSchemaToObject(cls,
|
|
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(
|
|
70
|
-
const cls = getClass(
|
|
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,
|
|
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
|
|
79
|
+
throw new ValidationResultError(errors);
|
|
81
80
|
}
|
|
82
|
-
return
|
|
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/support/cli.service.ts
CHANGED
|
@@ -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(
|
|
10
|
-
const all = await
|
|
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
|
|
27
|
+
const all = await ServiceRunner.findServices([]);
|
|
25
28
|
return [
|
|
26
29
|
cliTpl`${{ title: 'Available Services' }}`,
|
|
27
30
|
'-'.repeat(20),
|
|
@@ -31,12 +34,14 @@ 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
|
|
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);
|
|
@@ -44,19 +49,33 @@ export class CliServiceCommand implements CliCommandShape {
|
|
|
44
49
|
for await (const [valueType, value] of new ServiceRunner(descriptor).action(action)) {
|
|
45
50
|
const details = { [valueType === 'message' ? 'subtitle' : valueType]: value };
|
|
46
51
|
queue.add({ idx: i, text: msg = cliTpl`${{ identifier }} ${{ type }} ${details}` });
|
|
52
|
+
if (valueType === 'failure') {
|
|
53
|
+
failureMessages.push(msg);
|
|
54
|
+
}
|
|
47
55
|
}
|
|
48
56
|
queue.add({ idx: i, done: true, text: msg! });
|
|
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
|
-
|
|
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
|
}
|
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
|
-
}
|
package/support/bin/util.ts
DELETED
|
@@ -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
|
-
}
|