@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 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 plugin model that is extensible:
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 Plugin**
43
+ **Code: Echo Command**
44
44
  ```typescript
45
45
  import '@travetto/base';
46
- import { BasePlugin } from '@travetto/cli/src/plugin-base';
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 CliEchoPlugin extends BasePlugin {
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 Plugin Help**
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 Plugin Run**
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.0",
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.0",
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 { BasePlugin } from './plugin-base';
4
+ import { CliCommand } from './command';
5
5
 
6
- const PLUGIN_PACKAGE = [
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 plugins
18
+ * Manages loading and finding all commands
19
19
  */
20
- export class PluginManager {
20
+ export class CliCommandManager {
21
21
 
22
22
  /**
23
- * Get list of all plugins available
23
+ * Get list of all commands available
24
24
  */
25
- static getPluginMapping(): Map<string, string> {
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 plugin module
34
+ * Load command
35
35
  */
36
- static async loadPlugin(cmd: string, op?: (p: BasePlugin) => unknown): Promise<BasePlugin> {
36
+ static async loadCommand(cmd: string, op?: (p: CliCommand) => unknown): Promise<CliCommand> {
37
37
  const command = cmd.replace(/:/g, '_');
38
- const f = this.getPluginMapping().get(command)!;
38
+ const f = this.getCommandMapping().get(command)!;
39
39
  if (!f) {
40
- const cfg = PLUGIN_PACKAGE.find(([re]) => re.test(cmd));
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 BasePlugin) {
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 plugin: ${cmd}`);
62
+ throw new Error(`Not a valid command: ${cmd}`);
63
63
  }
64
64
 
65
65
  /**
66
- * Load all available plugins
66
+ * Load all available commands
67
67
  */
68
- static async loadAllPlugins(op?: (p: BasePlugin) => unknown | Promise<unknown>): Promise<BasePlugin[]> {
68
+ static async loadAllCommands(op?: (p: CliCommand) => unknown | Promise<unknown>): Promise<CliCommand[]> {
69
69
  return Promise.all(
70
- [...this.getPluginMapping().keys()]
70
+ [...this.getCommandMapping().keys()]
71
71
  .sort((a, b) => a.localeCompare(b))
72
- .map(k => this.loadPlugin(k, op))
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 ParamPrimitive = string | number | boolean | string[] | number[];
10
+ type OptionPrimitive = string | number | boolean;
10
11
 
11
- type ParamConfig<K extends ParamPrimitive = ParamPrimitive> = {
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 ParamMap<T = any> = { [key in keyof T]: T[key] extends ParamPrimitive ? ParamConfig<T[key]> : never };
32
+ type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? AllOptionConfig<T[key]> : never };
25
33
 
26
- type Shape<M extends ParamMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
34
+ type Shape<M extends OptionMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
27
35
 
28
36
  /**
29
- * Base plugin
37
+ * Base command
30
38
  */
31
- export abstract class BasePlugin<V extends ParamMap = ParamMap> {
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 plugin runs
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: ParamConfig<string>): ParamConfig<string> {
78
- if (cfg.combine && cfg.def) {
79
- cfg.def = cfg.combine(cfg.def, 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 | number>({ choices, ...cfg }: ParamConfig<K> & { choices: K[] | readonly K[] }): ParamConfig<K> {
88
- const config: ParamConfig<K> = {
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
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
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: ParamConfig<string[]>): ParamConfig<string[]> {
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: ParamConfig<boolean>): ParamConfig<boolean> {
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 }: ParamConfig<number> & { lower?: number, upper?: number }): ParamConfig<number> {
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<ReturnType<Exclude<this['getOptions'], undefined>>> {
147
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
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<ParamConfig[]> {
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 { PluginManager } from './plugin';
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 PluginManager.loadAllPlugins(x => x.setupCompletion(cfg));
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 plugin
26
+ * Run command
27
27
  */
28
- static async runPlugin(args: string[]): Promise<void> {
28
+ static async runCommand(args: string[]): Promise<void> {
29
29
  const cmd = args[2];
30
30
 
31
- let plugin;
31
+ let command;
32
32
 
33
33
  try {
34
- // Load a single plugin
35
- plugin = await PluginManager.loadPlugin(cmd);
36
- await plugin.setup(commander);
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 plugin.showHelp();
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 plugin.showHelp(err);
51
+ return command.showHelp(err);
52
52
  }
53
53
  }
54
54
 
55
55
  /**
56
56
  * Execute the command line
57
- * @param args argv
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.runPlugin(args);
71
+ await this.runCommand(args);
72
72
  } else {
73
- // Load all plugins
74
- await PluginManager.loadAllPlugins(x => x.setup(commander));
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([this.sleep(delay), final]);
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 this.sleep(50);
134
+ await timers.setTimeout(50);
138
135
  }
139
136
 
140
137
  if (i >= 0) {