@travetto/cli 2.1.2 → 2.2.1
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 +1 -1
- package/package.json +3 -3
- package/src/color.ts +15 -10
- package/src/execute.ts +10 -7
- package/src/help.ts +5 -5
- package/src/plugin-base.ts +31 -29
- package/src/plugin.ts +5 -4
- package/src/util.ts +22 -23
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
|
|
4
|
+
"version": "2.2.1",
|
|
5
5
|
"description": "CLI infrastructure for travetto framework",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"cli",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"directory": "module/cli"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/base": "^2.1
|
|
31
|
-
"commander": "^9.
|
|
30
|
+
"@travetto/base": "^2.2.1",
|
|
31
|
+
"commander": "^9.4.0"
|
|
32
32
|
},
|
|
33
33
|
"docDependencies": {
|
|
34
34
|
"@travetto/app": true
|
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
|
|
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
|
|
19
|
-
await PluginManager.loadAllPlugins(x => x.setupCompletion(
|
|
20
|
-
const res = await CliUtil.getCompletion(
|
|
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:');
|
package/src/plugin-base.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { CliUtil } from './util';
|
|
|
6
6
|
|
|
7
7
|
type Completion = Record<string, string[]>;
|
|
8
8
|
|
|
9
|
-
type
|
|
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
|
|
24
|
+
type OptionMap<T = any> = { [key in keyof T]: T[key] extends OptionPrimitive ? OptionConfig<T[key]> : never };
|
|
23
25
|
|
|
24
|
-
type Shape<M extends
|
|
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
|
|
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:
|
|
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 }:
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
95
|
+
};
|
|
96
|
+
return config;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
/**
|
|
96
100
|
* Define list option
|
|
97
101
|
*/
|
|
98
|
-
listOption(cfg:
|
|
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:
|
|
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 }:
|
|
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()
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 (!
|
|
51
|
-
opts =
|
|
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
|
|
61
|
+
if (last in cfg.task[cmd]) {
|
|
59
62
|
flag = last;
|
|
60
63
|
last = '';
|
|
61
|
-
} else if (second in
|
|
64
|
+
} else if (second in cfg.task[cmd]) {
|
|
62
65
|
// Look for available flags
|
|
63
|
-
if (
|
|
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 =
|
|
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:
|
|
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?:
|
|
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
|
|
122
|
+
let capturedError: Error | undefined;
|
|
124
123
|
const final = work
|
|
125
124
|
.then(res => value = res)
|
|
126
|
-
.catch(
|
|
125
|
+
.catch(err => capturedError = err)
|
|
127
126
|
.finally(() => done = true);
|
|
128
127
|
|
|
129
128
|
if (delay) {
|
|
130
|
-
await Promise.race([
|
|
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
|
|
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 (
|
|
142
|
-
throw
|
|
140
|
+
if (capturedError) {
|
|
141
|
+
throw capturedError;
|
|
143
142
|
} else {
|
|
144
143
|
return value!;
|
|
145
144
|
}
|