@travetto/cli 2.2.0 → 2.2.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 +6 -6
- package/package.json +2 -2
- package/src/{plugin.ts → command-manager.ts} +16 -16
- package/src/{plugin-base.ts → command.ts} +44 -24
- package/src/execute.ts +14 -14
- package/src/util.ts +3 -6
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,7 +85,7 @@ 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
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cli",
|
|
3
3
|
"displayName": "Command Line Interface",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.3",
|
|
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": "^2.2.
|
|
30
|
+
"@travetto/base": "^2.2.3",
|
|
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
|
|
10
|
+
type OptionPrimitive = string | number | boolean;
|
|
10
11
|
|
|
11
|
-
type
|
|
12
|
+
type CoreOptionConfig<K> = {
|
|
12
13
|
type?: Function;
|
|
13
14
|
key?: string;
|
|
14
15
|
short?: string | false;
|
|
@@ -16,19 +17,26 @@ type ParamConfig<K extends ParamPrimitive = ParamPrimitive> = {
|
|
|
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
|
|
32
|
+
type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? AllOptionConfig<T[key]> : never };
|
|
25
33
|
|
|
26
|
-
type Shape<M extends
|
|
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 ParamMap = ParamMap> {
|
|
|
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 ParamMap = ParamMap> {
|
|
|
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 && !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 ParamMap = ParamMap> {
|
|
|
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 ParamMap = ParamMap> {
|
|
|
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: [],
|
|
@@ -112,7 +124,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
112
124
|
/**
|
|
113
125
|
* Define bool option
|
|
114
126
|
*/
|
|
115
|
-
boolOption(cfg:
|
|
127
|
+
boolOption(cfg: OptionConfig<boolean>): OptionConfig<boolean> {
|
|
116
128
|
return {
|
|
117
129
|
type: Boolean,
|
|
118
130
|
combine: CliUtil.toBool.bind(CliUtil),
|
|
@@ -124,7 +136,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
124
136
|
/**
|
|
125
137
|
* Define int option
|
|
126
138
|
*/
|
|
127
|
-
intOption({ lower, upper, ...cfg }:
|
|
139
|
+
intOption({ lower, upper, ...cfg }: OptionConfig<number> & { lower?: number, upper?: number }): OptionConfig<number> {
|
|
128
140
|
return {
|
|
129
141
|
type: Number,
|
|
130
142
|
combine: CliUtil.toInt.bind(CliUtil, lower, upper),
|
|
@@ -143,9 +155,8 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
143
155
|
/**
|
|
144
156
|
* Expose configuration as constrained typed object
|
|
145
157
|
*/
|
|
146
|
-
get cmd(): Shape<
|
|
147
|
-
|
|
148
|
-
return this.#cmd.opts() as Shape<ReturnType<Exclude<this['getOptions'], undefined>>>;
|
|
158
|
+
get cmd(): Shape<V> {
|
|
159
|
+
return this.#cmd.opts();
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
/**
|
|
@@ -169,11 +180,11 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
169
180
|
* Process all options into final set before registering with commander
|
|
170
181
|
* @returns
|
|
171
182
|
*/
|
|
172
|
-
async finalizeOptions(): Promise<
|
|
183
|
+
async finalizeOptions(): Promise<AllOptionConfig[]> {
|
|
173
184
|
const opts = this.getOptions?.();
|
|
174
185
|
const used = new Set();
|
|
175
186
|
|
|
176
|
-
return (opts ? Object.entries(opts) : []).map(([k, cfg]) => {
|
|
187
|
+
return (opts ? Object.entries<AllOptionConfig>(opts) : []).map(([k, cfg]) => {
|
|
177
188
|
cfg.key = k;
|
|
178
189
|
cfg.name ??= k.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
|
|
179
190
|
if (cfg.short === undefined) {
|
|
@@ -206,6 +217,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
206
217
|
if (cfg.type !== Boolean || cfg.def) {
|
|
207
218
|
key = `${key} <${cfg.name}>`;
|
|
208
219
|
}
|
|
220
|
+
// @ts-expect-error
|
|
209
221
|
cmd = cfg.combine ? cmd.option(key, cfg.desc, cfg.combine, cfg.def) : cmd.option(key, cfg.desc, (cur, acc) => cur, cfg.def);
|
|
210
222
|
}
|
|
211
223
|
|
|
@@ -219,6 +231,14 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
219
231
|
async runAction(...args: unknown[]): Promise<void> {
|
|
220
232
|
await this.envInit?.();
|
|
221
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
|
+
}
|
|
222
242
|
return await this.action(...args);
|
|
223
243
|
}
|
|
224
244
|
|
|
@@ -241,7 +261,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
|
|
|
241
261
|
for (const el of await this.finalizeOptions()) {
|
|
242
262
|
if (el.completion) {
|
|
243
263
|
out[''] = [...out['']!, `--${el.name} `];
|
|
244
|
-
if (el.choices) {
|
|
264
|
+
if ('choices' in el && el.choices) {
|
|
245
265
|
out[`--${el.name} `] = el.choices.map(x => `${x}`);
|
|
246
266
|
if (el.short) {
|
|
247
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as timers from 'timers/promises';
|
|
1
2
|
import * as readline from 'readline';
|
|
2
3
|
import { Writable } from 'stream';
|
|
3
4
|
|
|
@@ -94,10 +95,6 @@ export class CliUtil {
|
|
|
94
95
|
}));
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
static sleep(ms: number): Promise<void> {
|
|
98
|
-
return new Promise(r => setTimeout(r, ms));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
98
|
/**
|
|
102
99
|
* Waiting message with a callback to end
|
|
103
100
|
*
|
|
@@ -129,12 +126,12 @@ export class CliUtil {
|
|
|
129
126
|
.finally(() => done = true);
|
|
130
127
|
|
|
131
128
|
if (delay) {
|
|
132
|
-
await Promise.race([
|
|
129
|
+
await Promise.race([timers.setTimeout(delay), final]);
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
while (!done) {
|
|
136
133
|
await writeLine(`${this.#waitState[i = (i + 1) % this.#waitState.length]} ${message}`);
|
|
137
|
-
await
|
|
134
|
+
await timers.setTimeout(50);
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
if (i >= 0) {
|