@travetto/cli 3.0.0-rc.1 → 3.0.0-rc.4

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
@@ -1,5 +1,5 @@
1
1
  <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
- <!-- Please modify https://github.com/travetto/travetto/tree/main/module/cli/doc.ts and execute "npx trv doc" to rebuild -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/cli/DOC.ts and execute "npx trv doc" to rebuild -->
3
3
  # Command Line Interface
4
4
  ## CLI infrastructure for travetto framework
5
5
 
@@ -10,7 +10,7 @@ npm install @travetto/cli
10
10
 
11
11
  The cli is the primary structure for interacting with the external requirements of the framework. This can range from running tests, to running applications, to generating email templates. The main executable can be installed globally or locally. If installed globally and locally, it will defer to the local installation for execution.
12
12
 
13
- As is the custom, modules are able to register their own cli extensions as scripts, whose name starts with `cli-`. These scripts are then picked up at runtime and all available options are provided when viewing the help documentation. The following are all the supported cli operations and the various settings they allow.
13
+ As is the custom, modules are able to register their own cli extensions as scripts, whose name starts with `cli.`. These scripts are then picked up at runtime and all available options are provided when viewing the help documentation. The following are all the supported cli operations and the various settings they allow.
14
14
 
15
15
  ## General
16
16
 
@@ -21,17 +21,14 @@ $ trv --help
21
21
  Usage: [options] [command]
22
22
 
23
23
  Options:
24
- -V, --version output the version number
25
- -h, --help display help for command
24
+ -V, --version output the version number
25
+ -h, --help display help for command
26
26
 
27
27
  Commands:
28
- build [options]
29
- clean [options]
30
- doc [options]
31
28
  echo [options] [args...]
32
- run [options] [application] [args...]
33
- test [options] [regexes...]
34
- help [command] display help for command
29
+ help [command] display help for command
30
+
31
+ 
35
32
  ```
36
33
 
37
34
  This will show all the available options/choices that are exposed given the currently installed modules.
@@ -42,8 +39,7 @@ Extending the `cli` is fairly straightforward. It is built upon [commander](htt
42
39
 
43
40
  **Code: Echo Command**
44
41
  ```typescript
45
- import '@travetto/base';
46
- import { CliCommand } from '@travetto/cli/src/command';
42
+ import { CliCommand } from '@travetto/cli';
47
43
 
48
44
  /**
49
45
  * `npx trv echo`
@@ -65,7 +61,7 @@ export class CliEchoCommand extends CliCommand {
65
61
  if (this.cmd.uppercase) {
66
62
  args = args.map(x => x.toUpperCase());
67
63
  }
68
- console.log(args);
64
+ console.log!(args);
69
65
  }
70
66
  }
71
67
  ```
@@ -81,6 +77,8 @@ Usage: echo [options] [args...]
81
77
  Options:
82
78
  -u, --uppercase Upper case (default: false)
83
79
  -h, --help display help for command
80
+
81
+ 
84
82
  ```
85
83
 
86
84
  And actually using it:
@@ -89,5 +87,6 @@ And actually using it:
89
87
  ```bash
90
88
  $ trv echo -u bOb rOb DRoP
91
89
 
92
- [ 'bOb', 'rOb', 'DRoP' ]
90
+ [ 'BOB', 'ROB', 'DROP' ]
91
+ 
93
92
  ```
package/__index__.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './src/command';
2
+ export * from './src/command-manager';
3
+ export * from './src/execute';
4
+ export * from './src/help';
5
+ export * from './src/color';
6
+ export * from './src/module';
7
+ export * from './src/scm';
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "displayName": "Command Line Interface",
4
- "version": "3.0.0-rc.1",
3
+ "version": "3.0.0-rc.4",
5
4
  "description": "CLI infrastructure for travetto framework",
6
5
  "keywords": [
7
6
  "cli",
@@ -15,23 +14,25 @@
15
14
  "name": "Travetto Framework"
16
15
  },
17
16
  "files": [
17
+ "__index__.ts",
18
18
  "src",
19
+ "support",
19
20
  "bin"
20
21
  ],
21
- "main": "bin/trv.js",
22
- "bin": {
23
- "trv": "bin/trv.js"
24
- },
22
+ "bin": "trv.js",
23
+ "main": "__index__.ts",
25
24
  "repository": {
26
25
  "url": "https://github.com/travetto/travetto.git",
27
26
  "directory": "module/cli"
28
27
  },
29
28
  "dependencies": {
30
- "@travetto/base": "^3.0.0-rc.1",
31
- "commander": "^9.4.0"
29
+ "@travetto/base": "^3.0.0-rc.4",
30
+ "@travetto/terminal": "^3.0.0-rc.4",
31
+ "@travetto/worker": "^3.0.0-rc.4",
32
+ "commander": "^9.5.0"
32
33
  },
33
- "docDependencies": {
34
- "@travetto/app": true
34
+ "travetto": {
35
+ "displayName": "Command Line Interface"
35
36
  },
36
37
  "publishConfig": {
37
38
  "access": "public"
package/src/color.ts CHANGED
@@ -1,29 +1,19 @@
1
- import { ColorUtil } from '@travetto/boot/src/color';
1
+ import { Util } from '@travetto/base';
2
+ import { GlobalTerminal } from '@travetto/terminal';
2
3
 
3
- const colorSet = {
4
- input: ColorUtil.makeColorer('yellow'),
5
- output: ColorUtil.makeColorer('magenta'),
6
- path: ColorUtil.makeColorer('cyan'),
7
- success: ColorUtil.makeColorer('green', 'bold'),
8
- failure: ColorUtil.makeColorer('red', 'bold'),
9
- param: ColorUtil.makeColorer('green'),
10
- type: ColorUtil.makeColorer('cyan'),
11
- description: ColorUtil.makeColorer('white', 'faint', 'bold'),
12
- title: ColorUtil.makeColorer('white', 'bold'),
13
- identifier: ColorUtil.makeColorer('blue', 'bold'),
14
- subtitle: ColorUtil.makeColorer('white'),
15
- subsubtitle: ColorUtil.makeColorer('white', 'faint')
16
- } as const;
4
+ const tplFn = GlobalTerminal.templateFunction({
5
+ input: 'oliveDrab',
6
+ output: 'pink',
7
+ path: 'teal',
8
+ success: 'green',
9
+ failure: 'red',
10
+ param: 'yellow',
11
+ type: 'cyan',
12
+ description: 'white',
13
+ title: 'brightWhite',
14
+ identifier: 'dodgerBlue',
15
+ subtitle: 'lightGray',
16
+ subsubtitle: 'darkGray'
17
+ });
17
18
 
18
- /**
19
- * Colorize a string, as a string interpolation
20
- *
21
- * @example
22
- * ```
23
- * color`${{title: 'Main Title'}} is ${{subtitle: 'Sub Title}}`
24
- * ```
25
- */
26
- export const color = ColorUtil.makeTemplate(colorSet);
27
-
28
-
29
- export type ColoredElement = keyof typeof colorSet;
19
+ export const cliTpl = Util.makeTemplate(tplFn);
@@ -1,13 +1,13 @@
1
- import { SourceIndex } from '@travetto/boot/src/internal/source';
1
+ import { ShutdownManager } from '@travetto/base';
2
+ import { RootIndex } from '@travetto/manifest';
2
3
 
3
- import { color } from './color';
4
+ import { cliTpl } from './color';
4
5
  import { CliCommand } from './command';
5
6
 
6
7
  const COMMAND_PACKAGE = [
7
8
  [/^run$/, 'app', true],
8
- [/^compile$/, 'compiler', true],
9
9
  [/^test$/, 'test', false],
10
- [/^command:service$/, 'command', true],
10
+ [/^service$/, 'command', true],
11
11
  [/^model:(install|export)$/, 'model', true],
12
12
  [/^openapi:(spec|client)$/, 'openapi', true],
13
13
  [/^email:(compile|dev)$/, 'email-template', false],
@@ -24,8 +24,8 @@ export class CliCommandManager {
24
24
  */
25
25
  static getCommandMapping(): Map<string, string> {
26
26
  const all = new Map<string, string>();
27
- for (const { file } of SourceIndex.find({ folder: 'bin', filter: /bin\/cli-/ })) {
28
- all.set(file.replace(/^.*\/bin\/.+?-(.*?)[.][^.]*$/, (_, f) => f), file);
27
+ for (const { output } of RootIndex.findSupport({ filter: /\/cli[.]/, checkProfile: false })) {
28
+ all.set(output.replace(/^.*\/cli[.](.*?)[.][^.]+$/, (_, f) => f), output);
29
29
  }
30
30
  return all;
31
31
  }
@@ -33,43 +33,64 @@ export class CliCommandManager {
33
33
  /**
34
34
  * Load command
35
35
  */
36
- static async loadCommand(cmd: string, op?: (p: CliCommand) => unknown): Promise<CliCommand> {
36
+ static async loadCommand(
37
+ cmd: string,
38
+ cfg: {
39
+ filter?: (p: CliCommand) => boolean;
40
+ failOnMissing?: boolean;
41
+ } = {}
42
+ ): Promise<CliCommand | undefined> {
37
43
  const command = cmd.replace(/:/g, '_');
38
- const f = this.getCommandMapping().get(command)!;
39
- if (!f) {
40
- const cfg = COMMAND_PACKAGE.find(([re]) => re.test(cmd));
41
- if (cfg) {
42
- const [, pkg, prod] = cfg;
43
- console.error(color`
44
+ const found = this.getCommandMapping().get(command)!;
45
+ if (!found) {
46
+ const matchedCfg = COMMAND_PACKAGE.find(([re]) => re.test(cmd));
47
+ if (matchedCfg) {
48
+ const [, pkg, prod] = matchedCfg;
49
+ console.error!(cliTpl`
44
50
  ${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
45
- ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}`);
46
- process.exit(1);
51
+ ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}
52
+ `);
53
+ await ShutdownManager.exit(1);
47
54
  }
48
55
  throw new Error(`Unknown command: ${cmd}`);
49
56
  }
50
- const values = Object.values<{ new(...args: unknown[]): unknown }>(await import(f));
51
- for (const v of values) {
52
- try {
53
- const inst = new v();
54
- if (inst instanceof CliCommand) {
55
- if (op) {
56
- await op(inst);
57
+ try {
58
+ const values = Object.values<{ new(...args: unknown[]): unknown }>(await import(found));
59
+ for (const v of values) {
60
+ try {
61
+ const inst = new v();
62
+ if (inst instanceof CliCommand && (!cfg.filter || cfg.filter(inst))) {
63
+ return inst;
57
64
  }
58
- return inst;
59
- }
60
- } catch { }
65
+ } catch { }
66
+ }
67
+ } catch (importErr) {
68
+ console.error(`Import error: ${cmd}`, importErr);
69
+ }
70
+ if (cfg.failOnMissing ?? false) {
71
+ throw new Error(`Not a valid command: ${cmd}`);
61
72
  }
62
- throw new Error(`Not a valid command: ${cmd}`);
63
73
  }
64
74
 
65
75
  /**
66
76
  * Load all available commands
67
77
  */
68
78
  static async loadAllCommands(op?: (p: CliCommand) => unknown | Promise<unknown>): Promise<CliCommand[]> {
69
- return Promise.all(
79
+ const commands = await Promise.all(
70
80
  [...this.getCommandMapping().keys()]
71
- .sort((a, b) => a.localeCompare(b))
72
- .map(k => this.loadCommand(k, op))
81
+ .map(k => this.loadCommand(k, {
82
+ filter(cmd: CliCommand) {
83
+ return RootIndex.getFunctionMetadata(cmd.constructor)?.abstract !== true && cmd.isActive?.() !== false;
84
+ }
85
+ }))
73
86
  );
87
+
88
+ return await Promise.all(commands
89
+ .filter((x): x is Exclude<typeof x, undefined> => !!x)
90
+ .sort((a, b) => a.name.localeCompare(b.name))
91
+ .map(async x => {
92
+ await op?.(x);
93
+ return x;
94
+ }));
74
95
  }
75
96
  }
package/src/command.ts CHANGED
@@ -1,11 +1,10 @@
1
- import { appendFile } from 'fs/promises';
2
- import * as commander from 'commander';
1
+ import { appendFile, mkdir } from 'fs/promises';
2
+ import type * as commander from 'commander';
3
3
 
4
- import { CompletionConfig } from './types';
5
- import { HelpUtil } from './help';
6
- import { CliUtil } from './util';
4
+ import { path } from '@travetto/manifest';
5
+ import { ConsoleManager, DataUtil, defineGlobalEnv, GlobalEnvConfig, ShutdownManager } from '@travetto/base';
7
6
 
8
- type Completion = Record<string, string[]>;
7
+ import { HelpUtil } from './help';
9
8
 
10
9
  type OptionPrimitive = string | number | boolean;
11
10
 
@@ -29,10 +28,16 @@ export type ListOptionConfig<K extends OptionPrimitive = OptionPrimitive> = Core
29
28
  type AllOptionConfig<K extends OptionPrimitive = OptionPrimitive> = OptionConfig<K> | ListOptionConfig<K>;
30
29
 
31
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? AllOptionConfig<T[key]> : never };
31
+ export type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? AllOptionConfig<T[key]> : never };
33
32
 
34
33
  type Shape<M extends OptionMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
35
34
 
35
+ function clamp(v: number, l?: number, u?: number): number | undefined {
36
+ if (l !== undefined && v < l) { return; }
37
+ if (u !== undefined && v > u) { return; }
38
+ return v;
39
+ }
40
+
36
41
  /**
37
42
  * Base command
38
43
  */
@@ -61,7 +66,7 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
61
66
  /**
62
67
  * Setup environment before command runs
63
68
  */
64
- envInit?(): Promise<void> | void;
69
+ envInit?(): Promise<GlobalEnvConfig> | GlobalEnvConfig;
65
70
  /**
66
71
  * Get Options for commander
67
72
  */
@@ -82,7 +87,10 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
82
87
  * Supports JSON IPC?
83
88
  */
84
89
  jsonIpc?(...args: unknown[]): Promise<unknown>;
85
-
90
+ /**
91
+ * Is the command active/eligible for usage
92
+ */
93
+ isActive?(): boolean;
86
94
  /**
87
95
  * Define option
88
96
  */
@@ -127,7 +135,8 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
127
135
  boolOption(cfg: OptionConfig<boolean>): OptionConfig<boolean> {
128
136
  return {
129
137
  type: Boolean,
130
- combine: CliUtil.toBool.bind(CliUtil),
138
+ // TODO: This needs to be resolved?
139
+ combine: (val, curr): boolean => DataUtil.coerceType(val, Boolean, false) ?? true,
131
140
  completion: true,
132
141
  ...cfg
133
142
  };
@@ -139,19 +148,11 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
139
148
  intOption({ lower, upper, ...cfg }: OptionConfig<number> & { lower?: number, upper?: number }): OptionConfig<number> {
140
149
  return {
141
150
  type: Number,
142
- combine: CliUtil.toInt.bind(CliUtil, lower, upper),
151
+ combine: (val, curr): number => clamp(DataUtil.coerceType(val, Number, false), lower, upper) ?? curr,
143
152
  ...cfg
144
153
  };
145
154
  }
146
155
 
147
- /**
148
- * Pre-compile on every cli execution
149
- */
150
- async build(): Promise<void> {
151
- await (await import('@travetto/base/bin/lib/'))
152
- .BuildUtil.build();
153
- }
154
-
155
156
  /**
156
157
  * Expose configuration as constrained typed object
157
158
  */
@@ -166,14 +167,21 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
166
167
  return this.#cmd.args;
167
168
  }
168
169
 
170
+ exit(code = 0): Promise<void> {
171
+ return ShutdownManager.exit(code);
172
+ }
173
+
169
174
  /**
170
175
  * Render help with additional message or extra text
171
176
  */
172
- async showHelp(err?: string | Error, extra?: string): Promise<never> {
177
+ async showHelp(err?: string | Error, extra?: string, exitOnError = true): Promise<void> {
173
178
  if (err && typeof err !== 'string') {
174
179
  err = err.message;
175
180
  }
176
181
  HelpUtil.showHelp(this.#cmd, err, extra || (await this.help?.()) || '');
182
+ if (exitOnError) {
183
+ return this.exit(err ? 1 : 0);
184
+ }
177
185
  }
178
186
 
179
187
  /**
@@ -217,11 +225,14 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
217
225
  if (cfg.type !== Boolean || cfg.def) {
218
226
  key = `${key} <${cfg.name}>`;
219
227
  }
220
- // @ts-expect-error
221
- cmd = cfg.combine ? cmd.option(key, cfg.desc, cfg.combine, cfg.def) : cmd.option(key, cfg.desc, (cur, acc) => cur, cfg.def);
228
+ cmd = cfg.combine ?
229
+ // @ts-expect-error
230
+ cmd.option(key, cfg.desc, cfg.combine, cfg.def) :
231
+ cmd.option(key, cfg.desc, (cur, acc) => cur, cfg.def);
222
232
  }
223
233
 
224
234
  cmd = cmd.action(this.runAction.bind(this));
235
+
225
236
  return this.#cmd = cmd;
226
237
  }
227
238
 
@@ -229,46 +240,20 @@ export abstract class CliCommand<V extends OptionMap = OptionMap> {
229
240
  * Runs the action at execution time
230
241
  */
231
242
  async runAction(...args: unknown[]): Promise<void> {
232
- await this.envInit?.();
233
- await this.build();
234
- if (process.env.TRV_CLI_JSON_IPC && this.jsonIpc) {
243
+ if (this.envInit) {
244
+ defineGlobalEnv(await this.envInit());
245
+ ConsoleManager.setDebugFromEnv();
246
+ }
247
+
248
+ if (process.env.TRV_CLI_IPC && this.jsonIpc) {
235
249
  const data = await this.jsonIpc(...args);
236
250
  if (data !== undefined) {
237
251
  const payload = JSON.stringify({ type: this.name, data });
238
- await appendFile(process.env.TRV_CLI_JSON_IPC, `${payload}\n`);
252
+ await mkdir(path.dirname(process.env.TRV_CLI_IPC), { recursive: true });
253
+ await appendFile(process.env.TRV_CLI_IPC, `${payload}\n`);
239
254
  return;
240
255
  }
241
256
  }
242
257
  return await this.action(...args);
243
258
  }
244
-
245
- /**
246
- * Collection tab completion information
247
- */
248
- async setupCompletion(config: CompletionConfig): Promise<void> {
249
- const task = await this.complete();
250
- config.all = [...config.all, this.name];
251
- if (task) {
252
- config.task[this.name] = task;
253
- }
254
- }
255
-
256
- /**
257
- * Return tab completion information
258
- */
259
- async complete(): Promise<Completion | void> {
260
- const out: Completion = { '': [] };
261
- for (const el of await this.finalizeOptions()) {
262
- if (el.completion) {
263
- out[''] = [...out['']!, `--${el.name} `];
264
- if ('choices' in el && el.choices) {
265
- out[`--${el.name} `] = el.choices.map(x => `${x}`);
266
- if (el.short) {
267
- out[`- ${el.short} `] = el.choices.map(x => `${x}`);
268
- }
269
- }
270
- }
271
- }
272
- return out;
273
- }
274
259
  }
package/src/execute.ts CHANGED
@@ -1,27 +1,17 @@
1
1
  import { program as commander } from 'commander';
2
2
 
3
- import { CliUtil } from './util';
4
- import { CompletionConfig } from './types';
3
+ import { RootIndex, PackageUtil, path } from '@travetto/manifest';
4
+ import { GlobalTerminal } from '@travetto/terminal';
5
+ import { runMain } from '@travetto/base/support/init.main';
6
+
5
7
  import { CliCommandManager } from './command-manager';
6
8
  import { HelpUtil } from './help';
7
- import { version } from '../package.json';
8
9
 
9
10
  /**
10
11
  * Execution manager
11
12
  */
12
13
  export class ExecutionManager {
13
14
 
14
- /**
15
- * Run tab completion given the full args list
16
- */
17
- static async runCompletion(args: string[]): Promise<void> {
18
- const cfg: CompletionConfig = { all: [], task: {} };
19
- await CliCommandManager.loadAllCommands(x => x.setupCompletion(cfg));
20
- const res = await CliUtil.getCompletion(cfg, args.slice(3));
21
- console.log(res.join(' '));
22
- return;
23
- }
24
-
25
15
  /**
26
16
  * Run command
27
17
  */
@@ -32,7 +22,7 @@ export class ExecutionManager {
32
22
 
33
23
  try {
34
24
  // Load a single command
35
- command = await CliCommandManager.loadCommand(cmd);
25
+ command = (await CliCommandManager.loadCommand(cmd, { failOnMissing: true }))!;
36
26
  await command.setup(commander);
37
27
  } catch (err) {
38
28
  return HelpUtil.showHelp(commander, `Unknown command ${cmd}`);
@@ -42,11 +32,13 @@ export class ExecutionManager {
42
32
  if (args.includes('-h') || args.includes('--help')) {
43
33
  return command.showHelp();
44
34
  } else {
45
- commander.parse(args);
35
+ await commander.parseAsync(args);
46
36
  }
47
37
  } catch (err) {
48
38
  if (!(err instanceof Error)) {
49
39
  throw err;
40
+ } else {
41
+ console.error(err);
50
42
  }
51
43
  return command.showHelp(err);
52
44
  }
@@ -57,23 +49,26 @@ export class ExecutionManager {
57
49
  * @param args
58
50
  */
59
51
  static async run(args: string[]): Promise<void> {
60
- const width = +(process.env.TRV_CONSOLE_WIDTH ?? process.stdout.columns ?? 120);
52
+ const width = GlobalTerminal.width;
61
53
  commander
62
- .version(version)
54
+ .version(PackageUtil.getFrameworkVersion())
63
55
  .configureOutput({ getOutHelpWidth: () => width, getErrHelpWidth: () => width });
64
56
 
65
57
  const cmd = args[2];
66
58
 
67
- if (cmd === 'complete') {
68
- await this.runCompletion(args);
69
- } else {
70
- if (cmd && !cmd.startsWith('-')) {
71
- await this.runCommand(args);
72
- } else {
73
- // Load all commands
74
- await CliCommandManager.loadAllCommands(x => x.setup(commander));
75
- HelpUtil.showHelp(commander);
59
+ if (cmd === 'main') {
60
+ let mainFile = RootIndex.resolveFileImport(process.argv[3])!;
61
+ if (!mainFile.startsWith('/')) {
62
+ mainFile = path.join(RootIndex.manifest.mainModule, mainFile);
63
+ mainFile = RootIndex.resolveFileImport(mainFile);
76
64
  }
65
+ await runMain((await import(mainFile)).main, process.argv.slice(4));
66
+ } else if (cmd && !cmd.startsWith('-')) {
67
+ await this.runCommand(args);
68
+ } else {
69
+ // Load all commands
70
+ await CliCommandManager.loadAllCommands(x => x.setup(commander));
71
+ HelpUtil.showHelp(commander);
77
72
  }
78
73
  }
79
74
  }