@travetto/cli 8.0.0-alpha.0 → 8.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -50
- package/__index__.ts +0 -1
- package/package.json +3 -3
- package/src/execute.ts +28 -54
- package/src/help.ts +48 -7
- package/src/parse.ts +32 -0
- package/src/registry/decorator.ts +126 -128
- package/src/registry/registry-adapter.ts +13 -6
- package/src/registry/registry-index.ts +4 -6
- package/src/schema-export.ts +4 -3
- package/src/schema.ts +35 -40
- package/src/service.ts +18 -1
- package/src/types.ts +5 -68
- package/src/util.ts +11 -17
- package/support/cli.cli_schema.ts +21 -18
- package/support/cli.main.ts +13 -14
- package/support/cli.service.ts +41 -33
- package/src/error.ts +0 -58
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#
|
|
62
|
+
* The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) 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#
|
|
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.
|
|
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#
|
|
134
|
+
The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/registry/decorator.ts#L27) 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#
|
|
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.
|
|
394
394
|
|
|
395
395
|
**Code: Simple Run Target**
|
|
396
396
|
```typescript
|
|
@@ -412,47 +412,19 @@ export class RunCommand {
|
|
|
412
412
|
|
|
413
413
|
**Code: Anatomy of a Command**
|
|
414
414
|
```typescript
|
|
415
|
-
export interface CliCommandShape
|
|
416
|
-
/**
|
|
417
|
-
* Parsed state
|
|
418
|
-
*/
|
|
419
|
-
_parsed?: ParsedState;
|
|
420
|
-
/**
|
|
421
|
-
* Config
|
|
422
|
-
*/
|
|
423
|
-
_cfg?: CliCommandConfig;
|
|
415
|
+
export interface CliCommandShape {
|
|
424
416
|
/**
|
|
425
417
|
* Action target of the command
|
|
426
418
|
*/
|
|
427
|
-
main(...args:
|
|
419
|
+
main(...args: unknown[]): OrProm<undefined | void>;
|
|
428
420
|
/**
|
|
429
421
|
* Run before main runs
|
|
430
422
|
*/
|
|
431
|
-
|
|
423
|
+
finalize?(help?: boolean): OrProm<void>;
|
|
432
424
|
/**
|
|
433
425
|
* Extra help
|
|
434
426
|
*/
|
|
435
427
|
help?(): OrProm<string[]>;
|
|
436
|
-
/**
|
|
437
|
-
* Run before help is displayed
|
|
438
|
-
*/
|
|
439
|
-
preHelp?(): OrProm<void>;
|
|
440
|
-
/**
|
|
441
|
-
* Is the command active/eligible for usage
|
|
442
|
-
*/
|
|
443
|
-
isActive?(): boolean;
|
|
444
|
-
/**
|
|
445
|
-
* Run before binding occurs
|
|
446
|
-
*/
|
|
447
|
-
preBind?(): OrProm<void>;
|
|
448
|
-
/**
|
|
449
|
-
* Run before validation occurs
|
|
450
|
-
*/
|
|
451
|
-
preValidate?(): OrProm<void>;
|
|
452
|
-
/**
|
|
453
|
-
* Validation method
|
|
454
|
-
*/
|
|
455
|
-
validate?(...args: T): OrProm<CliValidationError | CliValidationError[] | undefined>;
|
|
456
428
|
}
|
|
457
429
|
```
|
|
458
430
|
|
|
@@ -463,7 +435,7 @@ If the goal is to run a more complex application, which may include depending on
|
|
|
463
435
|
```typescript
|
|
464
436
|
import { Runtime, toConcrete } from '@travetto/runtime';
|
|
465
437
|
import { DependencyRegistryIndex } from '@travetto/di';
|
|
466
|
-
import { CliCommand, type CliCommandShape } from '@travetto/cli';
|
|
438
|
+
import { CliCommand, CliDebugIpcFlag, CliModuleFlag, CliProfilesFlag, CliRestartOnChangeFlag, type CliCommandShape } from '@travetto/cli';
|
|
467
439
|
import { NetUtil } from '@travetto/web';
|
|
468
440
|
import { Registry } from '@travetto/registry';
|
|
469
441
|
|
|
@@ -472,7 +444,7 @@ import type { WebHttpServer } from '../src/types.ts';
|
|
|
472
444
|
/**
|
|
473
445
|
* Run a web server
|
|
474
446
|
*/
|
|
475
|
-
@CliCommand(
|
|
447
|
+
@CliCommand()
|
|
476
448
|
export class WebHttpCommand implements CliCommandShape {
|
|
477
449
|
|
|
478
450
|
/** Port to run on */
|
|
@@ -481,7 +453,19 @@ export class WebHttpCommand implements CliCommandShape {
|
|
|
481
453
|
/** Kill conflicting port owner */
|
|
482
454
|
killConflict?: boolean = Runtime.localDevelopment;
|
|
483
455
|
|
|
484
|
-
|
|
456
|
+
@CliModuleFlag({ short: 'm' })
|
|
457
|
+
module: string;
|
|
458
|
+
|
|
459
|
+
@CliProfilesFlag()
|
|
460
|
+
profile: string[];
|
|
461
|
+
|
|
462
|
+
@CliRestartOnChangeFlag()
|
|
463
|
+
restartOnChange: boolean = true;
|
|
464
|
+
|
|
465
|
+
@CliDebugIpcFlag()
|
|
466
|
+
debugIpc?: boolean;
|
|
467
|
+
|
|
468
|
+
finalize(): void {
|
|
485
469
|
if (this.port) {
|
|
486
470
|
process.env.WEB_HTTP_PORT = `${this.port}`;
|
|
487
471
|
}
|
|
@@ -512,32 +496,56 @@ As noted in the example above, `fields` is specified in this execution, with sup
|
|
|
512
496
|
The `module` field is slightly more complex, but is geared towards supporting commands within a monorepo context. This flag ensures that a module is specified if running from the root of the monorepo, and that the module provided is real, and can run the desired command. When running from an explicit module folder in the monorepo, the module flag is ignored.
|
|
513
497
|
|
|
514
498
|
### Custom Validation
|
|
515
|
-
In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any [
|
|
499
|
+
In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any [ValidationError](https://github.com/travetto/travetto/tree/main/module/schema/src/validate/types.ts#L10) errors that are returned will be shared with the user, and fail to invoke the `main` method.
|
|
516
500
|
|
|
517
|
-
**Code:
|
|
501
|
+
**Code: ValidationError**
|
|
518
502
|
```typescript
|
|
519
|
-
export interface
|
|
503
|
+
export interface ValidationError {
|
|
520
504
|
/**
|
|
521
505
|
* The error message
|
|
522
506
|
*/
|
|
523
507
|
message: string;
|
|
524
508
|
/**
|
|
525
|
-
*
|
|
509
|
+
* The object path of the error
|
|
526
510
|
*/
|
|
527
|
-
|
|
528
|
-
|
|
511
|
+
path: string;
|
|
512
|
+
/**
|
|
513
|
+
* The kind of validation
|
|
514
|
+
*/
|
|
515
|
+
kind: ValidationKind;
|
|
516
|
+
/**
|
|
517
|
+
* The value provided
|
|
518
|
+
*/
|
|
519
|
+
value?: unknown;
|
|
520
|
+
/**
|
|
521
|
+
* Regular expression to match
|
|
522
|
+
*/
|
|
523
|
+
regex?: string;
|
|
524
|
+
/**
|
|
525
|
+
* Number to compare against
|
|
526
|
+
*/
|
|
527
|
+
limit?: NumericLikeIntrinsic;
|
|
528
|
+
/**
|
|
529
|
+
* The type of the field
|
|
530
|
+
*/
|
|
531
|
+
type?: string;
|
|
532
|
+
/**
|
|
533
|
+
* Source of the error
|
|
534
|
+
*/
|
|
535
|
+
source?: string;
|
|
536
|
+
}
|
|
529
537
|
```
|
|
530
538
|
|
|
531
539
|
A simple example of the validation can be found in the `doc` command:
|
|
532
540
|
|
|
533
541
|
**Code: Simple Validation Example**
|
|
534
542
|
```typescript
|
|
535
|
-
async
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
543
|
+
@Validator(async (cmd) => {
|
|
544
|
+
const docFile = path.resolve(cmd.input);
|
|
545
|
+
if (!(await fs.stat(docFile).catch(() => false))) {
|
|
546
|
+
return { message: `input: ${cmd.input} does not exist`, path: 'input', source: 'flag', kind: 'invalid' };
|
|
540
547
|
}
|
|
548
|
+
})
|
|
541
549
|
```
|
|
542
550
|
|
|
543
551
|
## CLI - service
|
|
@@ -550,7 +558,8 @@ $ trv service --help
|
|
|
550
558
|
Usage: service [options] <action:restart|start|status|stop> [services...:string]
|
|
551
559
|
|
|
552
560
|
Options:
|
|
553
|
-
-
|
|
561
|
+
-q, --quiet (default: false)
|
|
562
|
+
-h, --help display help for command
|
|
554
563
|
|
|
555
564
|
Available Services
|
|
556
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.2",
|
|
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.1",
|
|
33
|
+
"@travetto/terminal": "^8.0.0-alpha.1"
|
|
34
34
|
},
|
|
35
35
|
"travetto": {
|
|
36
36
|
"displayName": "Command Line Interface",
|
package/src/execute.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { ConsoleManager, Runtime, ShutdownManager, Util } from '@travetto/runtim
|
|
|
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,71 +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
|
|
21
|
+
const { cmd, args, help } = CliParseUtil.getArgs(argv);
|
|
22
|
+
if (!cmd) {
|
|
23
|
+
return console.info!(await HelpUtil.renderAllHelp());
|
|
24
|
+
}
|
|
40
25
|
|
|
41
|
-
|
|
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
|
-
|
|
37
|
+
if (help) {
|
|
38
|
+
return console.log!(await HelpUtil.renderCommandHelp(instance));
|
|
39
|
+
}
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
await command.main(...boundArgs);
|
|
58
|
-
}
|
|
41
|
+
await CliCommandSchemaUtil.validate(command, boundArgs);
|
|
59
42
|
|
|
60
|
-
/**
|
|
61
|
-
* Execute the command line
|
|
62
|
-
* @param args
|
|
63
|
-
*/
|
|
64
|
-
static async run(argv: string[]): Promise<void> {
|
|
65
|
-
try {
|
|
66
43
|
// Wait 50ms to allow stdout to flush on shutdown
|
|
67
44
|
ShutdownManager.signal.addEventListener('abort', () => Util.blockingTimeout(50));
|
|
68
45
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
console.info!(await HelpUtil.renderAllHelp());
|
|
72
|
-
} else if (help) {
|
|
73
|
-
const { command } = await this.#bindCommand(cmd, args);
|
|
74
|
-
console.log!(await HelpUtil.renderCommandHelp(command));
|
|
75
|
-
} else {
|
|
76
|
-
await this.#runCommand(cmd, args);
|
|
46
|
+
for (const preMain of config.preMain ?? []) {
|
|
47
|
+
await preMain(instance);
|
|
77
48
|
}
|
|
49
|
+
|
|
50
|
+
ConsoleManager.debug(Runtime.debug);
|
|
51
|
+
await instance.main(...boundArgs);
|
|
78
52
|
} catch (error) {
|
|
79
|
-
await
|
|
53
|
+
await HelpUtil.renderError(error, cmd, command);
|
|
80
54
|
} finally {
|
|
81
55
|
await ShutdownManager.shutdown();
|
|
82
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.preHelp?.();
|
|
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
|
}
|
package/src/parse.ts
CHANGED
|
@@ -20,6 +20,8 @@ export const isBoolFlag = (value?: SchemaInputConfig): boolean => value?.type ==
|
|
|
20
20
|
|
|
21
21
|
export type AliasesParseResult = Record<'long' | 'short' | 'raw' | 'env', string[]>;
|
|
22
22
|
|
|
23
|
+
const STATE_SYMBOL = Symbol();
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* Parsing support for the cli
|
|
25
27
|
*/
|
|
@@ -238,4 +240,34 @@ export class CliParseUtil {
|
|
|
238
240
|
return result;
|
|
239
241
|
}, { long: [], short: [], raw: [], env: [] });
|
|
240
242
|
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Build aliases for a schema config
|
|
246
|
+
*/
|
|
247
|
+
static buildAliases(config: { full?: string, short?: string, envVars?: string[] }, ...extraEnvVars: string[]): Partial<SchemaFieldConfig> {
|
|
248
|
+
const envVars = [...config.envVars ?? [], ...extraEnvVars];
|
|
249
|
+
return {
|
|
250
|
+
aliases: [
|
|
251
|
+
...(config.full ? [config.full.startsWith('-') ? config.full : `--${config.full}`] : []),
|
|
252
|
+
...(config.short ? [config.short.startsWith('-') ? config.short : `-${config.short}`] : []),
|
|
253
|
+
...(envVars.length ? envVars.map(CliParseUtil.toEnvField) : [])
|
|
254
|
+
]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get the state from an object
|
|
260
|
+
*/
|
|
261
|
+
static getState<T extends object>(item: T): ParsedState | undefined {
|
|
262
|
+
const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
|
|
263
|
+
return local[STATE_SYMBOL];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Set the state
|
|
268
|
+
*/
|
|
269
|
+
static setState<T extends object>(item: T, state: ParsedState): void {
|
|
270
|
+
const local: T & { [STATE_SYMBOL]?: ParsedState } = item;
|
|
271
|
+
local[STATE_SYMBOL] = state;
|
|
272
|
+
}
|
|
241
273
|
}
|