@travetto/cli 2.1.3 → 2.2.2

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/bin/cli.ts CHANGED
@@ -6,7 +6,7 @@ import { EnvUtil } from '@travetto/boot';
6
6
  /**
7
7
  * Entry point
8
8
  */
9
- export async function main() {
9
+ export async function main(): Promise<void> {
10
10
  if (!EnvUtil.isFalse('TRV_CLI_LOCAL') && !PathUtil.toUnix(__filename).includes(PathUtil.cwd)) { // If the current file is not under the working directory
11
11
  console.error(`
12
12
  The @travetto/cli is not intended to be installed globally. Please install it within your local project
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
3
  "displayName": "Command Line Interface",
4
- "version": "2.1.3",
4
+ "version": "2.2.2",
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.1.3",
30
+ "@travetto/base": "^2.2.2",
31
31
  "commander": "^9.4.0"
32
32
  },
33
33
  "docDependencies": {
package/src/color.ts CHANGED
@@ -1,14 +1,6 @@
1
1
  import { ColorUtil } from '@travetto/boot/src/color';
2
2
 
3
- /**
4
- * Colorize a string, as a string interpolation
5
- *
6
- * @example
7
- * ```
8
- * color`${{title: 'Main Title'}} is ${{subtitle: 'Sub Title}}`
9
- * ```
10
- */
11
- export const color = ColorUtil.makeTemplate({
3
+ const colorSet = {
12
4
  input: ColorUtil.makeColorer('yellow'),
13
5
  output: ColorUtil.makeColorer('magenta'),
14
6
  path: ColorUtil.makeColorer('cyan'),
@@ -21,4 +13,17 @@ export const color = ColorUtil.makeTemplate({
21
13
  identifier: ColorUtil.makeColorer('blue', 'bold'),
22
14
  subtitle: ColorUtil.makeColorer('white'),
23
15
  subsubtitle: ColorUtil.makeColorer('white', 'faint')
24
- });
16
+ } as const;
17
+
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;
package/src/execute.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as commander from 'commander';
1
+ import { program as commander } from 'commander';
2
2
 
3
3
  import { CliUtil } from './util';
4
4
  import { CompletionConfig } from './types';
@@ -14,10 +14,10 @@ export class ExecutionManager {
14
14
  /**
15
15
  * Run tab completion given the full args list
16
16
  */
17
- static async runCompletion(args: string[]) {
18
- const compl: CompletionConfig = { all: [], task: {} };
19
- await PluginManager.loadAllPlugins(x => x.setupCompletion(compl));
20
- const res = await CliUtil.getCompletion(compl, args.slice(3));
17
+ static async runCompletion(args: string[]): Promise<void> {
18
+ const cfg: CompletionConfig = { all: [], task: {} };
19
+ await PluginManager.loadAllPlugins(x => x.setupCompletion(cfg));
20
+ const res = await CliUtil.getCompletion(cfg, args.slice(3));
21
21
  console.log(res.join(' '));
22
22
  return;
23
23
  }
@@ -25,7 +25,7 @@ export class ExecutionManager {
25
25
  /**
26
26
  * Run plugin
27
27
  */
28
- static async runPlugin(args: string[]) {
28
+ static async runPlugin(args: string[]): Promise<void> {
29
29
  const cmd = args[2];
30
30
 
31
31
  let plugin;
@@ -45,6 +45,9 @@ export class ExecutionManager {
45
45
  commander.parse(args);
46
46
  }
47
47
  } catch (err) {
48
+ if (!(err instanceof Error)) {
49
+ throw err;
50
+ }
48
51
  return plugin.showHelp(err);
49
52
  }
50
53
  }
@@ -53,7 +56,7 @@ export class ExecutionManager {
53
56
  * Execute the command line
54
57
  * @param args argv
55
58
  */
56
- static async run(args: string[]) {
59
+ static async run(args: string[]): Promise<void> {
57
60
  const width = +(process.env.TRV_CONSOLE_WIDTH ?? process.stdout.columns ?? 120);
58
61
  commander
59
62
  .version(version)
package/src/help.ts CHANGED
@@ -11,7 +11,7 @@ export class HelpUtil {
11
11
  * @param text Source text
12
12
  * @param key
13
13
  */
14
- static extractValue(text: string, key: string) {
14
+ static extractValue(text: string, key: string): readonly [string, string] {
15
15
  let sub = '';
16
16
  if (text.includes(key)) {
17
17
  const start = text.indexOf(key);
@@ -28,7 +28,7 @@ export class HelpUtil {
28
28
  /**
29
29
  * Colorize Usage
30
30
  */
31
- static colorizeOptions(option: string) {
31
+ static colorizeOptions(option: string): string {
32
32
  return option.replace(/(\s*)(-[^, ]+)(,?\s*)(--\S+)?((\s+)?((?:\[[^\]]+\])|(?:\<[^>]+>)))?((\s+)(.*))?/g, (
33
33
  p: string, spacing: string,
34
34
  simpleParam: string, pSep: string,
@@ -59,7 +59,7 @@ export class HelpUtil {
59
59
  /**
60
60
  * Colorize command section
61
61
  */
62
- static colorizeCommands(commands: string) {
62
+ static colorizeCommands(commands: string): string {
63
63
  return commands
64
64
  .replace(/\s([^\[\]]\S+)/g, param => color`${{ param }}`)
65
65
  .replace(/(\s*[^\x1b]\[[^\]]+\])/g, input => color`${{ input }}`) // eslint-disable-line no-control-regex
@@ -69,14 +69,14 @@ export class HelpUtil {
69
69
  /**
70
70
  * Colorize usage
71
71
  */
72
- static colorizeUsage(usage: string) {
72
+ static colorizeUsage(usage: string): string {
73
73
  return usage.replace(/Usage:/, title => color`${{ title }}`);
74
74
  }
75
75
 
76
76
  /**
77
77
  * Get full help text
78
78
  */
79
- static getHelpText(text: string, extraText?: string) {
79
+ static getHelpText(text: string, extraText?: string): string {
80
80
  const [usage, text2] = this.extractValue(text, 'Usage:');
81
81
  const [options, text3] = this.extractValue(text2, 'Options:');
82
82
  const [commands, textFinal] = this.extractValue(text3, 'Commands:');
@@ -6,7 +6,9 @@ import { CliUtil } from './util';
6
6
 
7
7
  type Completion = Record<string, string[]>;
8
8
 
9
- type ParamConfig<K> = {
9
+ type OptionPrimitive = string | number | boolean | string[] | number[];
10
+
11
+ export type OptionConfig<K extends OptionPrimitive = OptionPrimitive> = {
10
12
  type?: Function;
11
13
  key?: string;
12
14
  short?: string | false;
@@ -14,19 +16,19 @@ type ParamConfig<K> = {
14
16
  desc: string;
15
17
  completion?: boolean;
16
18
  def?: K;
17
- choices?: K[];
19
+ choices?: K[] | readonly K[];
18
20
  combine?: (v: string, curr: K) => K;
19
21
  };
20
22
 
21
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- type ParamMap<T = any> = { [key in keyof T]: ParamConfig<T[key]> };
24
+ type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? OptionConfig<T[key]> : never };
23
25
 
24
- type Shape<M extends ParamMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
26
+ type Shape<M extends OptionMap> = { [k in keyof M]: Exclude<M[k]['def'], undefined> };
25
27
 
26
28
  /**
27
29
  * Base plugin
28
30
  */
29
- export abstract class BasePlugin<V extends ParamMap = ParamMap> {
31
+ export abstract class BasePlugin<V extends OptionMap> {
30
32
  /**
31
33
  * Command object
32
34
  */
@@ -72,7 +74,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
72
74
  /**
73
75
  * Define option
74
76
  */
75
- option(cfg: ParamConfig<string>): ParamConfig<string> {
77
+ option(cfg: OptionConfig<string>): OptionConfig<string> {
76
78
  if (cfg.combine && cfg.def) {
77
79
  cfg.def = cfg.combine(cfg.def, cfg.def);
78
80
  }
@@ -82,20 +84,22 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
82
84
  /**
83
85
  * Define option
84
86
  */
85
- choiceOption<K>({ choices, ...cfg }: ParamConfig<string> & { choices: K[] | readonly K[] }): ParamConfig<K> {
86
- return {
87
+ choiceOption<K extends string | number>({ choices, ...cfg }: OptionConfig<K> & { choices: K[] | readonly K[] }): OptionConfig<K> {
88
+ const config: OptionConfig<K> = {
87
89
  type: String,
88
- combine: (v, acc) => choices.includes(v as unknown as K) ? v as unknown as K : acc,
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,
89
92
  choices,
90
93
  completion: true,
91
94
  ...cfg
92
- } as ParamConfig<K>;
95
+ };
96
+ return config;
93
97
  }
94
98
 
95
99
  /**
96
100
  * Define list option
97
101
  */
98
- listOption(cfg: ParamConfig<string[]>): ParamConfig<string[]> {
102
+ listOption(cfg: OptionConfig<string[]>): OptionConfig<string[]> {
99
103
  return {
100
104
  type: String,
101
105
  def: [],
@@ -108,7 +112,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
108
112
  /**
109
113
  * Define bool option
110
114
  */
111
- boolOption(cfg: ParamConfig<boolean>): ParamConfig<boolean> {
115
+ boolOption(cfg: OptionConfig<boolean>): OptionConfig<boolean> {
112
116
  return {
113
117
  type: Boolean,
114
118
  combine: CliUtil.toBool.bind(CliUtil),
@@ -120,7 +124,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
120
124
  /**
121
125
  * Define int option
122
126
  */
123
- intOption({ lower, upper, ...cfg }: ParamConfig<number> & { lower?: number, upper?: number }): ParamConfig<number> {
127
+ intOption({ lower, upper, ...cfg }: OptionConfig<number> & { lower?: number, upper?: number }): OptionConfig<number> {
124
128
  return {
125
129
  type: Number,
126
130
  combine: CliUtil.toInt.bind(CliUtil, lower, upper),
@@ -131,7 +135,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
131
135
  /**
132
136
  * Pre-compile on every cli execution
133
137
  */
134
- async build() {
138
+ async build(): Promise<void> {
135
139
  await (await import('@travetto/base/bin/lib/'))
136
140
  .BuildUtil.build();
137
141
  }
@@ -139,14 +143,14 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
139
143
  /**
140
144
  * Expose configuration as constrained typed object
141
145
  */
142
- get cmd() {
143
- return this.#cmd.opts() as Shape<ReturnType<Exclude<this['getOptions'], undefined>>>;
146
+ get cmd(): Shape<V> {
147
+ return this.#cmd.opts();
144
148
  }
145
149
 
146
150
  /**
147
151
  * Expose command line arguments
148
152
  */
149
- get args() {
153
+ get args(): string[] {
150
154
  return this.#cmd.args;
151
155
  }
152
156
 
@@ -164,11 +168,11 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
164
168
  * Process all options into final set before registering with commander
165
169
  * @returns
166
170
  */
167
- async finalizeOptions() {
168
- const opts = this.getOptions?.() ?? {};
171
+ async finalizeOptions(): Promise<OptionConfig[]> {
172
+ const opts = this.getOptions?.();
169
173
  const used = new Set();
170
174
 
171
- return Object.entries(opts as ParamMap).map(([k, cfg]) => {
175
+ return (opts ? Object.entries(opts) : []).map(([k, cfg]) => {
172
176
  cfg.key = k;
173
177
  cfg.name ??= k.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
174
178
  if (cfg.short === undefined) {
@@ -185,7 +189,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
185
189
  /**
186
190
  * Receive the commander object, and process
187
191
  */
188
- async setup(cmd: commander.Command) {
192
+ async setup(cmd: commander.Command): Promise<commander.Command> {
189
193
  cmd = cmd.command(this.name);
190
194
  if (this.allowUnknownOptions) {
191
195
  cmd = cmd.allowUnknownOption(true);
@@ -201,7 +205,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
201
205
  if (cfg.type !== Boolean || cfg.def) {
202
206
  key = `${key} <${cfg.name}>`;
203
207
  }
204
- cmd = cfg.combine ? cmd.option(key, cfg.desc, cfg.combine, cfg.def) : cmd.option(key, cfg.desc, cfg.def);
208
+ cmd = cfg.combine ? cmd.option(key, cfg.desc, cfg.combine, cfg.def) : cmd.option(key, cfg.desc, (cur, acc) => cur, cfg.def);
205
209
  }
206
210
 
207
211
  cmd = cmd.action(this.runAction.bind(this));
@@ -211,7 +215,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
211
215
  /**
212
216
  * Runs the action at execution time
213
217
  */
214
- async runAction(...args: unknown[]) {
218
+ async runAction(...args: unknown[]): Promise<void> {
215
219
  await this.envInit?.();
216
220
  await this.build();
217
221
  return await this.action(...args);
@@ -220,7 +224,7 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
220
224
  /**
221
225
  * Collection tab completion information
222
226
  */
223
- async setupCompletion(config: CompletionConfig) {
227
+ async setupCompletion(config: CompletionConfig): Promise<void> {
224
228
  const task = await this.complete();
225
229
  config.all = [...config.all, this.name];
226
230
  if (task) {
@@ -232,16 +236,14 @@ export abstract class BasePlugin<V extends ParamMap = ParamMap> {
232
236
  * Return tab completion information
233
237
  */
234
238
  async complete(): Promise<Completion | void> {
235
- const out: Completion = {
236
- '': [] as string[]
237
- };
239
+ const out: Completion = { '': [] };
238
240
  for (const el of await this.finalizeOptions()) {
239
241
  if (el.completion) {
240
242
  out[''] = [...out['']!, `--${el.name} `];
241
243
  if (el.choices) {
242
- out[`--${el.name} `] = el.choices;
244
+ out[`--${el.name} `] = el.choices.map(x => `${x}`);
243
245
  if (el.short) {
244
- out[`- ${el.short} `] = el.choices;
246
+ out[`- ${el.short} `] = el.choices.map(x => `${x}`);
245
247
  }
246
248
  }
247
249
  }
package/src/plugin.ts CHANGED
@@ -12,7 +12,7 @@ const PLUGIN_PACKAGE = [
12
12
  [/^openapi:(spec|client)$/, 'openapi', true],
13
13
  [/^email:(compile|dev)$/, 'email-template', false],
14
14
  [/^pack(:assemble|:zip|:docker)?$/, 'pack', false],
15
- ] as [patt: RegExp, pkg: string, prod: boolean][];
15
+ ] as const;
16
16
 
17
17
  /**
18
18
  * Manages loading and finding all plugins
@@ -22,7 +22,7 @@ export class PluginManager {
22
22
  /**
23
23
  * Get list of all plugins available
24
24
  */
25
- static getPluginMapping() {
25
+ static getPluginMapping(): 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);
@@ -47,7 +47,8 @@ ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}`);
47
47
  }
48
48
  throw new Error(`Unknown command: ${cmd}`);
49
49
  }
50
- for (const v of Object.values(await import(f)) as { new(...args: unknown[]): unknown }[]) {
50
+ const values = Object.values<{ new(...args: unknown[]): unknown }>(await import(f));
51
+ for (const v of values) {
51
52
  try {
52
53
  const inst = new v();
53
54
  if (inst instanceof BasePlugin) {
@@ -64,7 +65,7 @@ ${{ identifier: `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}` }}`);
64
65
  /**
65
66
  * Load all available plugins
66
67
  */
67
- static async loadAllPlugins(op?: (p: BasePlugin) => unknown | Promise<unknown>) {
68
+ static async loadAllPlugins(op?: (p: BasePlugin) => unknown | Promise<unknown>): Promise<BasePlugin[]> {
68
69
  return Promise.all(
69
70
  [...this.getPluginMapping().keys()]
70
71
  .sort((a, b) => a.localeCompare(b))
package/src/util.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import * as timers from 'timers/promises';
1
2
  import * as readline from 'readline';
3
+ import { Writable } from 'stream';
4
+
2
5
  import { CompletionConfig } from './types';
3
6
 
4
7
  /**
@@ -8,20 +11,20 @@ export class CliUtil {
8
11
 
9
12
  static #waitState = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'.split('');
10
13
 
11
- static isBoolean(x: string) {
14
+ static isBoolean(x: string): boolean {
12
15
  return /^(1|0|yes|no|on|off|auto|true|false)$/i.test(x);
13
16
  }
14
17
 
15
18
  static toBool(x: string | boolean, def: boolean): boolean;
16
19
  static toBool(x?: string | boolean, def?: boolean): boolean | undefined;
17
- static toBool(x?: string | boolean, def?: boolean) {
20
+ static toBool(x?: string | boolean, def?: boolean): boolean | undefined {
18
21
  return x === undefined ? true :
19
22
  (typeof x === 'boolean' ? x :
20
23
  (this.isBoolean(x) ? /^(1|yes|on|true)$/i.test(x) :
21
24
  def));
22
25
  }
23
26
 
24
- static toInt(l: number | undefined, u: number | undefined, v: string, d: number) {
27
+ static toInt(l: number | undefined, u: number | undefined, v: string, d: number): number {
25
28
  let n = +v;
26
29
  if (l === undefined && u === undefined) {
27
30
  return n;
@@ -38,7 +41,7 @@ export class CliUtil {
38
41
  /**
39
42
  * Get code completion values
40
43
  */
41
- static async getCompletion(compl: CompletionConfig, args: string[]) {
44
+ static async getCompletion(cfg: CompletionConfig, args: string[]): Promise<string[]> {
42
45
  args = args.slice(0); // Copy as we mutate
43
46
 
44
47
  const cmd = args.shift()!;
@@ -47,27 +50,27 @@ export class CliUtil {
47
50
  let opts: string[] = [];
48
51
 
49
52
  // List all commands
50
- if (!compl.task[cmd]) {
51
- opts = compl.all;
53
+ if (!cfg.task[cmd]) {
54
+ opts = cfg.all;
52
55
  } else {
53
56
  // Look available sub commands
54
57
  last = args.pop() ?? '';
55
58
  const second = args.pop() ?? '';
56
59
  let flag = '';
57
60
 
58
- if (last in compl.task[cmd]) {
61
+ if (last in cfg.task[cmd]) {
59
62
  flag = last;
60
63
  last = '';
61
- } else if (second in compl.task[cmd]) {
64
+ } else if (second in cfg.task[cmd]) {
62
65
  // Look for available flags
63
- if (compl.task[cmd][second].includes(last)) {
66
+ if (cfg.task[cmd][second].includes(last)) {
64
67
  flag = '';
65
68
  last = '';
66
69
  } else {
67
70
  flag = second;
68
71
  }
69
72
  }
70
- opts = compl.task[cmd][flag];
73
+ opts = cfg.task[cmd][flag];
71
74
  }
72
75
 
73
76
  return last ? opts.filter(x => x.startsWith(last)) : opts.filter(x => !x.startsWith('-'));
@@ -79,7 +82,7 @@ export class CliUtil {
79
82
  * @param text Text, if desired
80
83
  * @param clear Should the entire line be cleared?
81
84
  */
82
- static async rewriteLine(stream: NodeJS.WritableStream, text?: string, clear = false) {
85
+ static async rewriteLine(stream: Writable, text?: string, clear = false): Promise<void> {
83
86
  await new Promise<void>(r => readline.cursorTo(stream, 0, undefined, () => {
84
87
  if (clear) {
85
88
  readline.clearLine(stream, 0);
@@ -92,10 +95,6 @@ export class CliUtil {
92
95
  }));
93
96
  }
94
97
 
95
- static sleep(ms: number) {
96
- return new Promise(r => setTimeout(r, ms));
97
- }
98
-
99
98
  /**
100
99
  * Waiting message with a callback to end
101
100
  *
@@ -103,8 +102,8 @@ export class CliUtil {
103
102
  * @param delay Delay duration
104
103
  */
105
104
  static async waiting<T>(message: string, work: Promise<T> | (() => Promise<T>),
106
- config: { completion?: string, delay?: number, stream?: NodeJS.WritableStream } = {}
107
- ) {
105
+ config: { completion?: string, delay?: number, stream?: Writable } = {}
106
+ ): Promise<T> {
108
107
  const { stream, delay, completion } = { delay: 1000, stream: process.stderr, ...config };
109
108
 
110
109
  const writeLine = this.rewriteLine.bind(this, stream);
@@ -120,26 +119,26 @@ export class CliUtil {
120
119
  let i = -1;
121
120
  let done = false;
122
121
  let value: T | undefined;
123
- let err: Error | undefined;
122
+ let capturedError: Error | undefined;
124
123
  const final = work
125
124
  .then(res => value = res)
126
- .catch(e => err = e)
125
+ .catch(err => capturedError = err)
127
126
  .finally(() => done = true);
128
127
 
129
128
  if (delay) {
130
- await Promise.race([this.sleep(delay), final]);
129
+ await Promise.race([timers.setTimeout(delay), final]);
131
130
  }
132
131
 
133
132
  while (!done) {
134
133
  await writeLine(`${this.#waitState[i = (i + 1) % this.#waitState.length]} ${message}`);
135
- await this.sleep(50);
134
+ await timers.setTimeout(50);
136
135
  }
137
136
 
138
137
  if (i >= 0) {
139
138
  await writeLine(completion ? `${message} ${completion}\n` : '', true);
140
139
  }
141
- if (err) {
142
- throw err;
140
+ if (capturedError) {
141
+ throw capturedError;
143
142
  } else {
144
143
  return value!;
145
144
  }