@travetto/cli 2.2.2 → 3.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/package.json +2 -2
- package/src/{plugin.ts → command-manager.ts} +16 -16
- package/src/{plugin-base.ts → command.ts} +39 -18
- package/src/execute.ts +14 -14
- package/src/util.ts +1 -1
package/README.md
CHANGED
|
@@ -38,19 +38,19 @@ This will show all the available options/choices that are exposed given the curr
|
|
|
38
38
|
|
|
39
39
|
## Extending
|
|
40
40
|
|
|
41
|
-
Extending the `cli` is fairly straightforward. It is built upon [commander](https://www.npmjs.com/package/commander), with a
|
|
41
|
+
Extending the `cli` is fairly straightforward. It is built upon [commander](https://www.npmjs.com/package/commander), with a model that is extensible:
|
|
42
42
|
|
|
43
|
-
**Code: Echo
|
|
43
|
+
**Code: Echo Command**
|
|
44
44
|
```typescript
|
|
45
45
|
import '@travetto/base';
|
|
46
|
-
import {
|
|
46
|
+
import { CliCommand } from '@travetto/cli/src/command';
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* `npx trv echo`
|
|
50
50
|
*
|
|
51
51
|
* Allows for cleaning of the cache dire
|
|
52
52
|
*/
|
|
53
|
-
export class
|
|
53
|
+
export class CliEchoCommand extends CliCommand {
|
|
54
54
|
name = 'echo';
|
|
55
55
|
|
|
56
56
|
getOptions() {
|
|
@@ -72,7 +72,7 @@ export class CliEchoPlugin extends BasePlugin {
|
|
|
72
72
|
|
|
73
73
|
With the corresponding output:
|
|
74
74
|
|
|
75
|
-
**Terminal: Echo
|
|
75
|
+
**Terminal: Echo Command Help**
|
|
76
76
|
```bash
|
|
77
77
|
$ trv echo --help
|
|
78
78
|
|
|
@@ -85,9 +85,9 @@ Options:
|
|
|
85
85
|
|
|
86
86
|
And actually using it:
|
|
87
87
|
|
|
88
|
-
**Terminal: Echo
|
|
88
|
+
**Terminal: Echo Command Run**
|
|
89
89
|
```bash
|
|
90
90
|
$ trv echo -u bOb rOb DRoP
|
|
91
91
|
|
|
92
|
-
[ '
|
|
92
|
+
[ 'bOb', 'rOb', 'DRoP' ]
|
|
93
93
|
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cli",
|
|
3
3
|
"displayName": "Command Line Interface",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0-rc.0",
|
|
5
5
|
"description": "CLI infrastructure for travetto framework",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"cli",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"directory": "module/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/base": "^
|
|
30
|
+
"@travetto/base": "^3.0.0-rc.0",
|
|
31
31
|
"commander": "^9.4.0"
|
|
32
32
|
},
|
|
33
33
|
"docDependencies": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SourceIndex } from '@travetto/boot/src/internal/source';
|
|
2
2
|
|
|
3
3
|
import { color } from './color';
|
|
4
|
-
import {
|
|
4
|
+
import { CliCommand } from './command';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const COMMAND_PACKAGE = [
|
|
7
7
|
[/^run$/, 'app', true],
|
|
8
8
|
[/^compile$/, 'compiler', true],
|
|
9
9
|
[/^test$/, 'test', false],
|
|
@@ -15,14 +15,14 @@ const PLUGIN_PACKAGE = [
|
|
|
15
15
|
] as const;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Manages loading and finding all
|
|
18
|
+
* Manages loading and finding all commands
|
|
19
19
|
*/
|
|
20
|
-
export class
|
|
20
|
+
export class CliCommandManager {
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Get list of all
|
|
23
|
+
* Get list of all commands available
|
|
24
24
|
*/
|
|
25
|
-
static
|
|
25
|
+
static getCommandMapping(): Map<string, string> {
|
|
26
26
|
const all = new Map<string, string>();
|
|
27
27
|
for (const { file } of SourceIndex.find({ folder: 'bin', filter: /bin\/cli-/ })) {
|
|
28
28
|
all.set(file.replace(/^.*\/bin\/.+?-(.*?)[.][^.]*$/, (_, f) => f), file);
|
|
@@ -31,13 +31,13 @@ export class PluginManager {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Load
|
|
34
|
+
* Load command
|
|
35
35
|
*/
|
|
36
|
-
static async
|
|
36
|
+
static async loadCommand(cmd: string, op?: (p: CliCommand) => unknown): Promise<CliCommand> {
|
|
37
37
|
const command = cmd.replace(/:/g, '_');
|
|
38
|
-
const f = this.
|
|
38
|
+
const f = this.getCommandMapping().get(command)!;
|
|
39
39
|
if (!f) {
|
|
40
|
-
const cfg =
|
|
40
|
+
const cfg = COMMAND_PACKAGE.find(([re]) => re.test(cmd));
|
|
41
41
|
if (cfg) {
|
|
42
42
|
const [, pkg, prod] = cfg;
|
|
43
43
|
console.error(color`
|
|
@@ -51,7 +51,7 @@ ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}`);
|
|
|
51
51
|
for (const v of values) {
|
|
52
52
|
try {
|
|
53
53
|
const inst = new v();
|
|
54
|
-
if (inst instanceof
|
|
54
|
+
if (inst instanceof CliCommand) {
|
|
55
55
|
if (op) {
|
|
56
56
|
await op(inst);
|
|
57
57
|
}
|
|
@@ -59,17 +59,17 @@ ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}`);
|
|
|
59
59
|
}
|
|
60
60
|
} catch { }
|
|
61
61
|
}
|
|
62
|
-
throw new Error(`Not a valid
|
|
62
|
+
throw new Error(`Not a valid command: ${cmd}`);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Load all available
|
|
66
|
+
* Load all available commands
|
|
67
67
|
*/
|
|
68
|
-
static async
|
|
68
|
+
static async loadAllCommands(op?: (p: CliCommand) => unknown | Promise<unknown>): Promise<CliCommand[]> {
|
|
69
69
|
return Promise.all(
|
|
70
|
-
[...this.
|
|
70
|
+
[...this.getCommandMapping().keys()]
|
|
71
71
|
.sort((a, b) => a.localeCompare(b))
|
|
72
|
-
.map(k => this.
|
|
72
|
+
.map(k => this.loadCommand(k, op))
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendFile } from 'fs/promises';
|
|
1
2
|
import * as commander from 'commander';
|
|
2
3
|
|
|
3
4
|
import { CompletionConfig } from './types';
|
|
@@ -6,9 +7,9 @@ import { CliUtil } from './util';
|
|
|
6
7
|
|
|
7
8
|
type Completion = Record<string, string[]>;
|
|
8
9
|
|
|
9
|
-
type OptionPrimitive = string | number | boolean
|
|
10
|
+
type OptionPrimitive = string | number | boolean;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
type CoreOptionConfig<K> = {
|
|
12
13
|
type?: Function;
|
|
13
14
|
key?: string;
|
|
14
15
|
short?: string | false;
|
|
@@ -16,19 +17,26 @@ export type OptionConfig<K extends OptionPrimitive = OptionPrimitive> = {
|
|
|
16
17
|
desc: string;
|
|
17
18
|
completion?: boolean;
|
|
18
19
|
def?: K;
|
|
19
|
-
choices?: K[] | readonly K[];
|
|
20
20
|
combine?: (v: string, curr: K) => K;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
export type OptionConfig<K extends OptionPrimitive = OptionPrimitive> = CoreOptionConfig<K> & {
|
|
24
|
+
choices?: K[] | readonly K[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ListOptionConfig<K extends OptionPrimitive = OptionPrimitive> = CoreOptionConfig<K[]>;
|
|
28
|
+
|
|
29
|
+
type AllOptionConfig<K extends OptionPrimitive = OptionPrimitive> = OptionConfig<K> | ListOptionConfig<K>;
|
|
30
|
+
|
|
23
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
-
type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ?
|
|
32
|
+
type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? AllOptionConfig<T[key]> : never };
|
|
25
33
|
|
|
26
34
|
type Shape<M extends OptionMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
|
|
27
35
|
|
|
28
36
|
/**
|
|
29
|
-
* Base
|
|
37
|
+
* Base command
|
|
30
38
|
*/
|
|
31
|
-
export abstract class
|
|
39
|
+
export abstract class CliCommand<V extends OptionMap = OptionMap> {
|
|
32
40
|
/**
|
|
33
41
|
* Command object
|
|
34
42
|
*/
|
|
@@ -51,7 +59,7 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
51
59
|
abstract action(...args: unknown[]): void | Promise<void>;
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
|
-
* Setup environment before
|
|
62
|
+
* Setup environment before command runs
|
|
55
63
|
*/
|
|
56
64
|
envInit?(): Promise<void> | void;
|
|
57
65
|
/**
|
|
@@ -70,13 +78,17 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
70
78
|
* Extra help
|
|
71
79
|
*/
|
|
72
80
|
help?(): Promise<string> | string;
|
|
81
|
+
/**
|
|
82
|
+
* Supports JSON IPC?
|
|
83
|
+
*/
|
|
84
|
+
jsonIpc?(...args: unknown[]): Promise<unknown>;
|
|
73
85
|
|
|
74
86
|
/**
|
|
75
87
|
* Define option
|
|
76
88
|
*/
|
|
77
|
-
option(cfg:
|
|
78
|
-
if (cfg.combine && cfg.def) {
|
|
79
|
-
cfg.def = cfg.combine(cfg.def
|
|
89
|
+
option<K extends OptionPrimitive, T extends OptionConfig<K>>(cfg: T): T {
|
|
90
|
+
if ('combine' in cfg && cfg.combine && cfg.def !== undefined && !Array.isArray(cfg.def)) {
|
|
91
|
+
cfg.def = cfg.combine(`${cfg.def}`, cfg.def);
|
|
80
92
|
}
|
|
81
93
|
return { type: String, ...cfg };
|
|
82
94
|
}
|
|
@@ -84,11 +96,11 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
84
96
|
/**
|
|
85
97
|
* Define option
|
|
86
98
|
*/
|
|
87
|
-
choiceOption<K extends string
|
|
88
|
-
|
|
99
|
+
choiceOption<K extends string, T extends (OptionConfig<K> & { choices: K[] | readonly K[] })>({ choices, ...cfg }: T): T {
|
|
100
|
+
// @ts-expect-error
|
|
101
|
+
const config: T = {
|
|
89
102
|
type: String,
|
|
90
|
-
|
|
91
|
-
combine: (v: string, acc: K): K => choices.includes(v as K) ? v as K : acc,
|
|
103
|
+
combine: (v: K, acc) => choices.includes(v) ? v : acc,
|
|
92
104
|
choices,
|
|
93
105
|
completion: true,
|
|
94
106
|
...cfg
|
|
@@ -99,7 +111,7 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
99
111
|
/**
|
|
100
112
|
* Define list option
|
|
101
113
|
*/
|
|
102
|
-
listOption(cfg:
|
|
114
|
+
listOption<T extends ListOptionConfig<string>>(cfg: T): T {
|
|
103
115
|
return {
|
|
104
116
|
type: String,
|
|
105
117
|
def: [],
|
|
@@ -168,11 +180,11 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
168
180
|
* Process all options into final set before registering with commander
|
|
169
181
|
* @returns
|
|
170
182
|
*/
|
|
171
|
-
async finalizeOptions(): Promise<
|
|
183
|
+
async finalizeOptions(): Promise<AllOptionConfig[]> {
|
|
172
184
|
const opts = this.getOptions?.();
|
|
173
185
|
const used = new Set();
|
|
174
186
|
|
|
175
|
-
return (opts ? Object.entries(opts) : []).map(([k, cfg]) => {
|
|
187
|
+
return (opts ? Object.entries<AllOptionConfig>(opts) : []).map(([k, cfg]) => {
|
|
176
188
|
cfg.key = k;
|
|
177
189
|
cfg.name ??= k.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
|
|
178
190
|
if (cfg.short === undefined) {
|
|
@@ -205,6 +217,7 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
205
217
|
if (cfg.type !== Boolean || cfg.def) {
|
|
206
218
|
key = `${key} <${cfg.name}>`;
|
|
207
219
|
}
|
|
220
|
+
// @ts-expect-error
|
|
208
221
|
cmd = cfg.combine ? cmd.option(key, cfg.desc, cfg.combine, cfg.def) : cmd.option(key, cfg.desc, (cur, acc) => cur, cfg.def);
|
|
209
222
|
}
|
|
210
223
|
|
|
@@ -218,6 +231,14 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
218
231
|
async runAction(...args: unknown[]): Promise<void> {
|
|
219
232
|
await this.envInit?.();
|
|
220
233
|
await this.build();
|
|
234
|
+
if (process.env.TRV_CLI_JSON_IPC && this.jsonIpc) {
|
|
235
|
+
const data = await this.jsonIpc(...args);
|
|
236
|
+
if (data !== undefined) {
|
|
237
|
+
const payload = JSON.stringify({ type: this.name, data });
|
|
238
|
+
await appendFile(process.env.TRV_CLI_JSON_IPC, `${payload}\n`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
221
242
|
return await this.action(...args);
|
|
222
243
|
}
|
|
223
244
|
|
|
@@ -240,7 +261,7 @@ export abstract class BasePlugin<V extends OptionMap> {
|
|
|
240
261
|
for (const el of await this.finalizeOptions()) {
|
|
241
262
|
if (el.completion) {
|
|
242
263
|
out[''] = [...out['']!, `--${el.name} `];
|
|
243
|
-
if (el.choices) {
|
|
264
|
+
if ('choices' in el && el.choices) {
|
|
244
265
|
out[`--${el.name} `] = el.choices.map(x => `${x}`);
|
|
245
266
|
if (el.short) {
|
|
246
267
|
out[`- ${el.short} `] = el.choices.map(x => `${x}`);
|
package/src/execute.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { program as commander } from 'commander';
|
|
|
2
2
|
|
|
3
3
|
import { CliUtil } from './util';
|
|
4
4
|
import { CompletionConfig } from './types';
|
|
5
|
-
import {
|
|
5
|
+
import { CliCommandManager } from './command-manager';
|
|
6
6
|
import { HelpUtil } from './help';
|
|
7
7
|
import { version } from '../package.json';
|
|
8
8
|
|
|
@@ -16,31 +16,31 @@ export class ExecutionManager {
|
|
|
16
16
|
*/
|
|
17
17
|
static async runCompletion(args: string[]): Promise<void> {
|
|
18
18
|
const cfg: CompletionConfig = { all: [], task: {} };
|
|
19
|
-
await
|
|
19
|
+
await CliCommandManager.loadAllCommands(x => x.setupCompletion(cfg));
|
|
20
20
|
const res = await CliUtil.getCompletion(cfg, args.slice(3));
|
|
21
21
|
console.log(res.join(' '));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Run
|
|
26
|
+
* Run command
|
|
27
27
|
*/
|
|
28
|
-
static async
|
|
28
|
+
static async runCommand(args: string[]): Promise<void> {
|
|
29
29
|
const cmd = args[2];
|
|
30
30
|
|
|
31
|
-
let
|
|
31
|
+
let command;
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
|
-
// Load a single
|
|
35
|
-
|
|
36
|
-
await
|
|
34
|
+
// Load a single command
|
|
35
|
+
command = await CliCommandManager.loadCommand(cmd);
|
|
36
|
+
await command.setup(commander);
|
|
37
37
|
} catch (err) {
|
|
38
38
|
return HelpUtil.showHelp(commander, `Unknown command ${cmd}`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
42
|
if (args.includes('-h') || args.includes('--help')) {
|
|
43
|
-
return
|
|
43
|
+
return command.showHelp();
|
|
44
44
|
} else {
|
|
45
45
|
commander.parse(args);
|
|
46
46
|
}
|
|
@@ -48,13 +48,13 @@ export class ExecutionManager {
|
|
|
48
48
|
if (!(err instanceof Error)) {
|
|
49
49
|
throw err;
|
|
50
50
|
}
|
|
51
|
-
return
|
|
51
|
+
return command.showHelp(err);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Execute the command line
|
|
57
|
-
* @param args
|
|
57
|
+
* @param args
|
|
58
58
|
*/
|
|
59
59
|
static async run(args: string[]): Promise<void> {
|
|
60
60
|
const width = +(process.env.TRV_CONSOLE_WIDTH ?? process.stdout.columns ?? 120);
|
|
@@ -68,10 +68,10 @@ export class ExecutionManager {
|
|
|
68
68
|
await this.runCompletion(args);
|
|
69
69
|
} else {
|
|
70
70
|
if (cmd && !cmd.startsWith('-')) {
|
|
71
|
-
await this.
|
|
71
|
+
await this.runCommand(args);
|
|
72
72
|
} else {
|
|
73
|
-
// Load all
|
|
74
|
-
await
|
|
73
|
+
// Load all commands
|
|
74
|
+
await CliCommandManager.loadAllCommands(x => x.setup(commander));
|
|
75
75
|
HelpUtil.showHelp(commander);
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/util.ts
CHANGED
|
@@ -18,7 +18,7 @@ export class CliUtil {
|
|
|
18
18
|
static toBool(x: string | boolean, def: boolean): boolean;
|
|
19
19
|
static toBool(x?: string | boolean, def?: boolean): boolean | undefined;
|
|
20
20
|
static toBool(x?: string | boolean, def?: boolean): boolean | undefined {
|
|
21
|
-
return x === undefined ?
|
|
21
|
+
return x === undefined ? def :
|
|
22
22
|
(typeof x === 'boolean' ? x :
|
|
23
23
|
(this.isBoolean(x) ? /^(1|yes|on|true)$/i.test(x) :
|
|
24
24
|
def));
|