@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 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,9 +85,9 @@ 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
 
92
- [ 'BOB', 'ROB', 'DROP' ]
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": "2.2.2",
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": "^2.2.2",
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 { 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 OptionPrimitive = string | number | boolean | string[] | number[];
10
+ type OptionPrimitive = string | number | boolean;
10
11
 
11
- export type OptionConfig<K extends OptionPrimitive = OptionPrimitive> = {
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 ? OptionConfig<T[key]> : never };
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 plugin
37
+ * Base command
30
38
  */
31
- export abstract class BasePlugin<V extends OptionMap> {
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 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 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: OptionConfig<string>): OptionConfig<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 !== 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 | number>({ choices, ...cfg }: OptionConfig<K> & { choices: K[] | readonly K[] }): OptionConfig<K> {
88
- const config: OptionConfig<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 OptionMap> {
99
111
  /**
100
112
  * Define list option
101
113
  */
102
- listOption(cfg: OptionConfig<string[]>): OptionConfig<string[]> {
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<OptionConfig[]> {
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 { 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
@@ -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 ? true :
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));