mixcli 3.2.0 → 3.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/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +67 -94
- package/dist/index.d.ts +67 -94
- package/dist/index.js +3 -1132
- package/dist/index.js.map +1 -1
- package/package.json +24 -11
- package/readme.md +8 -12
- package/src/cli.ts +8 -15
- package/src/command.ts +452 -452
- package/src/finder.ts +142 -142
- package/src/index.ts +5 -4
- package/src/option.ts +39 -81
- package/src/prompt.ts +183 -110
- package/src/utils.ts +144 -158
- package/dist/index.mjs +0 -1088
- package/dist/index.mjs.map +0 -1
- package/src/oslocate.js +0 -148
package/src/command.ts
CHANGED
@@ -1,452 +1,452 @@
|
|
1
|
-
import { Command, Option } from "commander";
|
2
|
-
import prompts, { PromptObject } from "prompts";
|
3
|
-
import { MixOption, type MixedOptionParams } from "./option";
|
4
|
-
import { addBuiltInOptions,
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
//
|
54
|
-
export type
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
private
|
75
|
-
private
|
76
|
-
private
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
get
|
93
|
-
|
94
|
-
}
|
95
|
-
get
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
}
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
}
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
actionArgs,
|
226
|
-
actionOpts,
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
}
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
return
|
264
|
-
}
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
this.
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
);
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
}
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
*
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
.
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
*
|
403
|
-
*
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
*
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
const
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
}
|
441
|
-
/**
|
442
|
-
*
|
443
|
-
*/
|
444
|
-
|
445
|
-
this._enable_prompts =
|
446
|
-
return this;
|
447
|
-
}
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
1
|
+
import { Command, Option } from "commander";
|
2
|
+
import prompts, { PromptObject } from "prompts";
|
3
|
+
import { MixOption, type MixedOptionParams } from "./option";
|
4
|
+
import { addBuiltInOptions, hyphenToCamelCase, isDisablePrompts, outputDebug } from "./utils";
|
5
|
+
import type { AsyncFunction } from "flex-tools/types";
|
6
|
+
import path from "node:path";
|
7
|
+
import fs from "node:fs";
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
export type IMixCommandHookListener = ({
|
12
|
+
args,
|
13
|
+
options,
|
14
|
+
command,
|
15
|
+
}: {
|
16
|
+
args: any[];
|
17
|
+
options: Record<string, any>;
|
18
|
+
command: MixCommand;
|
19
|
+
}) => void | Promise<void>;
|
20
|
+
|
21
|
+
export type BeforeMixCommandHookListener = ({
|
22
|
+
args,
|
23
|
+
options,
|
24
|
+
command,
|
25
|
+
}: {
|
26
|
+
args: any[];
|
27
|
+
options: Record<string, any>;
|
28
|
+
command: MixCommand;
|
29
|
+
}) => void | Promise<void>;
|
30
|
+
|
31
|
+
export type AfterMixCommandHookListener = ({
|
32
|
+
value,
|
33
|
+
args,
|
34
|
+
options,
|
35
|
+
command,
|
36
|
+
}: {
|
37
|
+
value: any;
|
38
|
+
args: any[];
|
39
|
+
options: Record<string, any>;
|
40
|
+
command: MixCommand;
|
41
|
+
}) => void | Promise<void>;
|
42
|
+
|
43
|
+
export interface MixActionOptions {
|
44
|
+
id: string;
|
45
|
+
at: "replace" | "before" | "after" | "preappend" | "append" | number;
|
46
|
+
enhance: boolean; // 函数签名类型,即采用原始的commander的action函数签名,还是mixcli的action函数签名
|
47
|
+
}
|
48
|
+
|
49
|
+
export interface MixActionRegistry extends Omit<MixActionOptions, "at"> {
|
50
|
+
fn: Function;
|
51
|
+
}
|
52
|
+
|
53
|
+
// 原始的Action动作函数
|
54
|
+
export type MixOriginalAction = (...args: any[]) => any | Promise<void>;
|
55
|
+
|
56
|
+
// 增强的Action函数签名
|
57
|
+
export type MixEnhanceAction = ({
|
58
|
+
args,
|
59
|
+
options,
|
60
|
+
value,
|
61
|
+
command,
|
62
|
+
}: {
|
63
|
+
args: any[];
|
64
|
+
options: Record<string, any>;
|
65
|
+
value: any;
|
66
|
+
command: MixCommand;
|
67
|
+
}) => any | Promise<any>;
|
68
|
+
|
69
|
+
// 执行action的返回结果
|
70
|
+
export const BREAK = Symbol("BREAK_ACTION"); // 中止后续的action执行
|
71
|
+
|
72
|
+
export class MixCommand extends Command {
|
73
|
+
__MIX_COMMAND__ = true;
|
74
|
+
private _beforeHooks : [ BeforeMixCommandHookListener, boolean ][] = [];
|
75
|
+
private _afterHooks : [ AfterMixCommandHookListener, boolean ][] = [];
|
76
|
+
private _customPrompts : PromptObject[] = [];
|
77
|
+
private _optionValues : Record<string, any> = {}; // 命令行输入的选项值
|
78
|
+
private _actions : MixActionRegistry[] = []; // 允许多个action
|
79
|
+
private _enable_prompts: boolean = true; // 是否启用交互提示
|
80
|
+
constructor(name?: string) {
|
81
|
+
super(name);
|
82
|
+
// eslint-disable-next-line no-this-alias
|
83
|
+
const self = this
|
84
|
+
if (!this.isRoot) addBuiltInOptions(this);
|
85
|
+
this.hook("preAction", async function (this: any) {
|
86
|
+
self._optionValues = self.getOptionValues(this.hookedCommand);
|
87
|
+
// @ts-ignore
|
88
|
+
await self.preActionHook.apply(self, arguments);
|
89
|
+
});
|
90
|
+
}
|
91
|
+
get isRoot() { return this.parent==undefined; }
|
92
|
+
get optionValues(){ return this._optionValues }
|
93
|
+
get actions() { return this._actions; }
|
94
|
+
get beforeHooks() { return this._beforeHooks; }
|
95
|
+
get afterHooks() {return this._afterHooks;}
|
96
|
+
get fullname() {
|
97
|
+
let names = [this.name()];
|
98
|
+
let parent = this.parent;
|
99
|
+
while (parent) {
|
100
|
+
if (parent.name() !== "root") {
|
101
|
+
names.unshift(parent.name());
|
102
|
+
}
|
103
|
+
parent = parent.parent;
|
104
|
+
}
|
105
|
+
return names.join(".");
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* 返回根命令
|
110
|
+
*/
|
111
|
+
root() {
|
112
|
+
// eslint-disable-next-line no-this-alias
|
113
|
+
let root:any = this;
|
114
|
+
while (root && root.parent != null) {
|
115
|
+
root = root.parent as unknown as MixCommand;
|
116
|
+
}
|
117
|
+
return root;
|
118
|
+
}
|
119
|
+
|
120
|
+
|
121
|
+
action(fn: MixEnhanceAction, options: MixActionOptions): this;
|
122
|
+
action(fn: MixOriginalAction): this;
|
123
|
+
action(): this {
|
124
|
+
const actionFunc = arguments[0];
|
125
|
+
if (arguments.length == 1 && typeof actionFunc == "function") {
|
126
|
+
// 原始方式
|
127
|
+
this._actions.push({
|
128
|
+
id: Math.random().toString(36).substring(2),
|
129
|
+
enhance: false,
|
130
|
+
fn: actionFunc,
|
131
|
+
});
|
132
|
+
} else if (arguments.length == 2 && typeof actionFunc == "function" && typeof arguments[1] == "object" ) {
|
133
|
+
// 增强模式
|
134
|
+
const actionFn = arguments[0];
|
135
|
+
const actionOpts: MixActionOptions = Object.assign({ at: "append" }, arguments[1]);
|
136
|
+
if (actionOpts.at == "replace") this._actions = [];
|
137
|
+
const actionItem = {
|
138
|
+
id: actionOpts.id || Math.random().toString(36).substring(2),
|
139
|
+
enhance: actionOpts.enhance == undefined ? true : actionOpts.enhance,
|
140
|
+
fn: actionFn,
|
141
|
+
} as const;
|
142
|
+
if (typeof actionOpts.at == "number") {
|
143
|
+
this._actions.splice(Number(actionOpts.at), 0, actionItem);
|
144
|
+
} else if (["append", "before"].includes(actionOpts.at)) {
|
145
|
+
this._actions.push(actionItem);
|
146
|
+
} else if (["preappend", "after"].includes(actionOpts.at)) {
|
147
|
+
this._actions.splice(0, 0, actionItem);
|
148
|
+
} else {
|
149
|
+
this._actions.push(actionItem);
|
150
|
+
}
|
151
|
+
} else {
|
152
|
+
console.log("[mixcli] action params error");
|
153
|
+
}
|
154
|
+
return super.action(this.getWrapperedAction());
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* 读取命令配置值,包括父命令提供的配置选项
|
159
|
+
* @param command
|
160
|
+
*/
|
161
|
+
private getOptionValues(command: MixCommand) {
|
162
|
+
const opts = {};
|
163
|
+
// eslint-disable-next-line no-this-alias
|
164
|
+
let parent: any = command;
|
165
|
+
while (parent) {
|
166
|
+
Object.assign(opts, (parent as MixCommand).optionValues);
|
167
|
+
parent = parent.parent;
|
168
|
+
}
|
169
|
+
return opts;
|
170
|
+
}
|
171
|
+
/**
|
172
|
+
* 本函数在运行时子类进行action生成该命令的action
|
173
|
+
*/
|
174
|
+
private getWrapperedAction() {
|
175
|
+
return this.wrapperWorkDirsAction(this.wrapperChainActions());
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* 向上查找所有祖先命令
|
180
|
+
*/
|
181
|
+
private getAncestorCommands(): MixCommand[] {
|
182
|
+
let cmds: MixCommand[] = [];
|
183
|
+
// eslint-disable-next-line no-this-alias
|
184
|
+
let cmd: MixCommand | null = this;
|
185
|
+
while (cmd) {
|
186
|
+
cmd = cmd.parent as unknown as MixCommand;
|
187
|
+
if (cmd) {
|
188
|
+
cmds.push(cmd);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
return cmds;
|
192
|
+
}
|
193
|
+
/***
|
194
|
+
* 将所有actions包装成一个链式调用的函数
|
195
|
+
*/
|
196
|
+
private wrapperChainActions() {
|
197
|
+
// eslint-disable-next-line no-this-alias
|
198
|
+
const self = this;
|
199
|
+
return async function (this: any) {
|
200
|
+
const args = Array.from(arguments); // 原始输入的参数
|
201
|
+
let preValue: any; // 保存上一个action的返回值
|
202
|
+
//解析参数, 0-1个参数为options,最后一个参数为command
|
203
|
+
let actionOpts: Record<string, any> = {},
|
204
|
+
actionArgs: any[] = [],
|
205
|
+
cmd: any;
|
206
|
+
if (args.length >= 2) {
|
207
|
+
cmd = args[args.length - 1]; // 最后一个command
|
208
|
+
actionOpts = args[args.length - 2];
|
209
|
+
actionArgs = args.slice(0, args.length - 2);
|
210
|
+
}
|
211
|
+
await self.executeBeforeHooks({ args: actionArgs, options: actionOpts, command: cmd });
|
212
|
+
try {
|
213
|
+
for (let action of self._actions) {
|
214
|
+
try {
|
215
|
+
if (action.enhance) {
|
216
|
+
// 增强模式
|
217
|
+
outputDebug("执行<{}>: args={}, options={}", () => [
|
218
|
+
self.name(),
|
219
|
+
actionArgs,
|
220
|
+
actionOpts,
|
221
|
+
]);
|
222
|
+
preValue = await action.fn.call(this, {
|
223
|
+
command: cmd,
|
224
|
+
value : preValue,
|
225
|
+
args : actionArgs,
|
226
|
+
options: actionOpts,
|
227
|
+
});
|
228
|
+
} else {
|
229
|
+
// 原始模式
|
230
|
+
preValue = await action.fn.apply(this, args);
|
231
|
+
}
|
232
|
+
if (preValue === BREAK) break;
|
233
|
+
} catch (e) {
|
234
|
+
outputDebug("命令{}的Action({})执行出错:{}", [self.name, action.id, e]);
|
235
|
+
throw e;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
} finally {
|
239
|
+
await self.executeAfterHooks({
|
240
|
+
value: preValue,
|
241
|
+
args: actionArgs,
|
242
|
+
options: actionOpts,
|
243
|
+
command: cmd,
|
244
|
+
});
|
245
|
+
}
|
246
|
+
};
|
247
|
+
}
|
248
|
+
/**
|
249
|
+
* 当传入--work-dirs时用来处理工作目录
|
250
|
+
*/
|
251
|
+
private wrapperWorkDirsAction(fn: AsyncFunction) {
|
252
|
+
// eslint-disable-next-line no-this-alias
|
253
|
+
const self = this;
|
254
|
+
return async function (this: any) {
|
255
|
+
let workDirs = self._optionValues.workDirs;
|
256
|
+
// 未指定工作目录参数
|
257
|
+
if (!workDirs) {
|
258
|
+
return await fn.apply(this, Array.from(arguments));
|
259
|
+
}
|
260
|
+
if (!Array.isArray(workDirs)) workDirs = workDirs.split(",");
|
261
|
+
workDirs = workDirs.reduce((dirs: any[], dir: string) => {
|
262
|
+
if (typeof dir == "string") dirs.push(...dir.split(","));
|
263
|
+
return dirs;
|
264
|
+
}, []);
|
265
|
+
for (let workDir of workDirs) {
|
266
|
+
const cwd = process.cwd();
|
267
|
+
try {
|
268
|
+
if (!path.isAbsolute(workDir)) workDir = path.join(cwd, workDir);
|
269
|
+
if (fs.existsSync(workDir) && fs.statSync(workDir).isDirectory()) {
|
270
|
+
outputDebug("切换到工作目录:{}", workDir);
|
271
|
+
process.chdir(workDir); // 切换
|
272
|
+
await fn.apply(this, Array.from(arguments));
|
273
|
+
} else {
|
274
|
+
outputDebug("无效的工作目录:{}", workDir);
|
275
|
+
}
|
276
|
+
} finally {
|
277
|
+
process.chdir(cwd);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
};
|
281
|
+
}
|
282
|
+
getOption(name: string): MixOption {
|
283
|
+
return this.options.find((option) => option.name() == name) as unknown as MixOption;
|
284
|
+
}
|
285
|
+
/**
|
286
|
+
* 添加一个Before钩子
|
287
|
+
* @param listener
|
288
|
+
* @param scope =false时代表只在本命令执行,=true时代表在本命令及其子命令执行
|
289
|
+
* @returns
|
290
|
+
*/
|
291
|
+
before(listener: BeforeMixCommandHookListener, scope: boolean = true) {
|
292
|
+
this._beforeHooks.push([listener, scope]);
|
293
|
+
return this;
|
294
|
+
}
|
295
|
+
private async executeBeforeHooks(args: any) {
|
296
|
+
const hooks: [BeforeMixCommandHookListener, boolean, MixCommand][] = this.beforeHooks.map(
|
297
|
+
([hook, scope]) => [hook, scope, this]
|
298
|
+
);
|
299
|
+
this.getAncestorCommands().forEach((cmd: MixCommand) => {
|
300
|
+
hooks.unshift(
|
301
|
+
...cmd.beforeHooks.map(([hook, scope]) => {
|
302
|
+
return [hook, scope, cmd] as [BeforeMixCommandHookListener, boolean, MixCommand];
|
303
|
+
})
|
304
|
+
);
|
305
|
+
});
|
306
|
+
for (let [hook, scope, cmd] of hooks) {
|
307
|
+
if (!scope) continue;
|
308
|
+
await hook.call(cmd, args);
|
309
|
+
}
|
310
|
+
}
|
311
|
+
/**
|
312
|
+
* 添加一个After钩子
|
313
|
+
* @param listener
|
314
|
+
* @param scope =false时代表只在本命令执行,=true时代表在本命令及其子命令执行
|
315
|
+
* @returns
|
316
|
+
*/
|
317
|
+
after(listener: AfterMixCommandHookListener, scope: boolean = true) {
|
318
|
+
this._afterHooks.push([listener, scope]);
|
319
|
+
return this;
|
320
|
+
}
|
321
|
+
|
322
|
+
private async executeAfterHooks(args: any) {
|
323
|
+
const hooks: [AfterMixCommandHookListener, boolean, MixCommand][] = this.afterHooks.map(
|
324
|
+
([hook, scope]) => [hook, scope, this]
|
325
|
+
);
|
326
|
+
this.getAncestorCommands().forEach((cmd: MixCommand) => {
|
327
|
+
hooks.push(
|
328
|
+
...cmd.afterHooks.map(([hook, scope]) => {
|
329
|
+
return [hook, scope, cmd] as [BeforeMixCommandHookListener, boolean, MixCommand];
|
330
|
+
})
|
331
|
+
);
|
332
|
+
});
|
333
|
+
for (let [hook, scope, cmd] of hooks) {
|
334
|
+
if (!scope) continue; //=false时不执行
|
335
|
+
await hook.call(cmd, args);
|
336
|
+
}
|
337
|
+
}
|
338
|
+
private async preActionHook(thisCommand: Command) {
|
339
|
+
if (this.isEnablePrompts()) {
|
340
|
+
// 自动生成提示
|
341
|
+
const questions: PromptObject[] = [
|
342
|
+
...this.generateAutoPrompts(),
|
343
|
+
...this._customPrompts,
|
344
|
+
];
|
345
|
+
// 用户提示
|
346
|
+
if (questions.length > 0) {
|
347
|
+
const results = await prompts(questions);
|
348
|
+
Object.entries(results).forEach(([key, value]) => {
|
349
|
+
thisCommand.setOptionValue(key, value);
|
350
|
+
});
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
private isEnablePrompts() {
|
356
|
+
if (isDisablePrompts()) {
|
357
|
+
return false;// 命令行参数禁用了提示,优先级最高
|
358
|
+
} else {
|
359
|
+
return this._enable_prompts;
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
/**
|
364
|
+
* 生成选项自动提示
|
365
|
+
*
|
366
|
+
* @remarks
|
367
|
+
* 要求所有未提供默认值的Option自动生成提示
|
368
|
+
*
|
369
|
+
* - 未提供默认值,并且是必选的参数Option
|
370
|
+
* - 指定了choices但未提供有效值的Option
|
371
|
+
*
|
372
|
+
*/
|
373
|
+
private generateAutoPrompts(): PromptObject[] {
|
374
|
+
const options = this.options as unknown as MixOption[];
|
375
|
+
const optionPromports = options
|
376
|
+
.filter((option) => !option.hidden && option.__MIX_OPTION__)
|
377
|
+
.map((option) => option.getPrompt(this._optionValues[option.attributeName()]))
|
378
|
+
.filter((prompt) => prompt) as PromptObject[];
|
379
|
+
|
380
|
+
outputDebug("命令<{}>自动生成{}个选项提示:{}", [
|
381
|
+
this.name(),
|
382
|
+
optionPromports.length,
|
383
|
+
optionPromports.map((prompt) => `${prompt.name}(${prompt.type})`).join(","),
|
384
|
+
]);
|
385
|
+
return optionPromports;
|
386
|
+
}
|
387
|
+
|
388
|
+
// @ts-ignore
|
389
|
+
option( flags: string, description: string, options?: MixedOptionParams ):this{
|
390
|
+
const option = new MixOption(flags, description, options);
|
391
|
+
if (option.required && !this.isEnablePrompts()) option.mandatory = true;
|
392
|
+
return this.addOption(option as unknown as Option)
|
393
|
+
}
|
394
|
+
|
395
|
+
/**
|
396
|
+
* 添加提示
|
397
|
+
*
|
398
|
+
* @remarks
|
399
|
+
*
|
400
|
+
* 添加一些自定义提示
|
401
|
+
*
|
402
|
+
* @param questions
|
403
|
+
* @returns
|
404
|
+
*/
|
405
|
+
prompt(questions: PromptObject | PromptObject[]) {
|
406
|
+
this._customPrompts.push(...(Array.isArray(questions) ? questions : [questions]));
|
407
|
+
return this;
|
408
|
+
}
|
409
|
+
|
410
|
+
/**
|
411
|
+
*
|
412
|
+
* 选择命令并执行
|
413
|
+
*
|
414
|
+
* @remorks
|
415
|
+
*
|
416
|
+
* 当命令具有多个子命令时,并且没有提供默认子命令时,提示用户选择一个子命令
|
417
|
+
*
|
418
|
+
*/
|
419
|
+
async selectCommands() {
|
420
|
+
const choices = this.commands.map((command) => ({
|
421
|
+
title: `${command.description()}(${command.name()})`,
|
422
|
+
value: command.name(),
|
423
|
+
}));
|
424
|
+
const result = await prompts({
|
425
|
+
type : "select",
|
426
|
+
name : "command",
|
427
|
+
message: "请选择命令:",
|
428
|
+
choices,
|
429
|
+
});
|
430
|
+
// 重新解析命令行参数标志,
|
431
|
+
const command = this.commands.find((command) => command.name() === result.command);
|
432
|
+
await command?.parseAsync([result.command], { from: "user" });
|
433
|
+
}
|
434
|
+
/**
|
435
|
+
* 禁用/启用所有提示
|
436
|
+
*/
|
437
|
+
disablePrompts() {
|
438
|
+
this._enable_prompts = false;
|
439
|
+
return this;
|
440
|
+
}
|
441
|
+
/**
|
442
|
+
* 启用所有提示
|
443
|
+
*/
|
444
|
+
enablePrompts() {
|
445
|
+
this._enable_prompts = true;
|
446
|
+
return this;
|
447
|
+
}
|
448
|
+
}
|
449
|
+
|
450
|
+
|
451
|
+
|
452
|
+
// 编写一个类型号用来表示Commander的Option flags字面量类型
|