-
Notifications
You must be signed in to change notification settings - Fork 6
/
prompter.ts
135 lines (118 loc) · 4.03 KB
/
prompter.ts
1
2
3
4
5
6
7
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
import Enquirer from "enquirer";
import { Argument } from "./argument.js";
import { Option } from "./option.js";
/**
* Workaround for "The requested module 'enquirer' is a CommonJS module, which
* may not support all module.exports as named exports."
*/
const prompt = Enquirer.prompt;
/**
* Extract PromptOptions from exported Enquirer types, since types are not
* exported natively.
* @todo Wait for upstream change to import types from enquirer
* @link https://github.com/enquirer/enquirer/pull/258
*/
type EnquirerQuestion = Extract<
Parameters<typeof prompt>[0],
{ initial?: any }
>;
// Another workaround: we need to add the `limit` option to ArrayPromptOptions.
type ArrayPromptOptions = Extract<EnquirerQuestion, { maxChoices?: number }> & {
limit?: number;
};
type Question = EnquirerQuestion | ArrayPromptOptions;
/**
* Creates a new prompter instance
*/
export function prompter<T = {}>(baseArgs: Array<Argument | Option>, args: T) {
return new Prompter(baseArgs, args);
}
export class Prompter<T = {}> {
constructor(
private baseArgs: Array<Argument | Option>,
private args: T,
) {}
public async prompt() {
const questions = this.getQuestions(this.args);
// Short circuit if there are no questions to ask.
if (!questions.length) {
return this.args;
}
// Ask questions and merge with passed in args.
const answers = await prompt(questions);
return {
...this.args,
...answers,
};
}
private getSelectLimit() {
// Never more than what fits on the screen (+ some padding) or 20
return Math.min(process.stdout.rows - 3, 20);
}
/**
* Returns an array of arguments and options which should be prompted, because
* they are promptable (`isPromptable()` returned true) and they are not
* provided in the args passed in to this function.
*/
private getQuestions(args: T) {
// If we need to prompt for things, fill questions array
return this.baseArgs.reduce((questions, arg) => {
const name = arg.getName();
const defaultValue = arg.getDefault();
const isPromptable = arg.isPromptable();
const presentInArgs = Object.constructor.hasOwnProperty.call(args, name);
const isDefault =
presentInArgs &&
typeof defaultValue !== "undefined" &&
defaultValue == (args as any)[name];
// We're going to assume that if an argument/option still has its default
// value and it is promptable, it should get a prompt.
if (isPromptable && (!presentInArgs || isDefault)) {
// Detect the type of question we need to ask
switch (true) {
case typeof arg.getChoices() !== "undefined" &&
(arg.getType() === "array" || Array.isArray(defaultValue)):
// Use checkbox question type
questions.push({
name,
type: "multiselect",
message: arg.getPrompt(),
initial: defaultValue,
choices: arg.getChoices() as string[],
limit: this.getSelectLimit(),
});
break;
case typeof arg.getChoices() !== "undefined":
// Use list question type
questions.push({
name,
type: "autocomplete",
message: arg.getPrompt(),
initial: defaultValue,
choices: arg.getChoices() as string[],
limit: this.getSelectLimit(),
});
break;
case arg.getType() === "boolean" || typeof defaultValue === "boolean":
// Use confirm question type
questions.push({
name,
type: "confirm",
message: arg.getPrompt(),
initial: defaultValue,
});
break;
default:
// Use input question type as default
questions.push({
name,
type: "input",
message: arg.getPrompt(),
initial: defaultValue,
});
}
}
return questions;
}, [] as Question[]);
}
}