@forgecart/cli 2.202609301225.0 → 2.202610020334.0
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/src/cli.d.ts +10 -3
- package/dist/src/cli.js +34 -7
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/init.d.ts +7 -3
- package/dist/src/commands/init.js +4 -2
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/exit-ladder.d.ts +25 -0
- package/dist/src/exit-ladder.js +32 -0
- package/dist/src/exit-ladder.js.map +1 -0
- package/dist/src/question/given-answers.d.ts +42 -0
- package/dist/src/question/given-answers.js +154 -0
- package/dist/src/question/given-answers.js.map +1 -0
- package/dist/src/question/prompt-arm.d.ts +21 -0
- package/dist/src/question/prompt-arm.js +263 -0
- package/dist/src/question/prompt-arm.js.map +1 -0
- package/dist/src/question/question-prompt.adapter.d.ts +36 -0
- package/dist/src/question/question-prompt.adapter.js +81 -0
- package/dist/src/question/question-prompt.adapter.js.map +1 -0
- package/dist/src/question/question-unanswered-refusal.d.ts +15 -0
- package/dist/src/question/question-unanswered-refusal.js +35 -0
- package/dist/src/question/question-unanswered-refusal.js.map +1 -0
- package/dist/src/question/terminal-question-session.d.ts +36 -0
- package/dist/src/question/terminal-question-session.js +126 -0
- package/dist/src/question/terminal-question-session.js.map +1 -0
- package/dist/src/question/terminal-screen.d.ts +26 -0
- package/dist/src/question/terminal-screen.js +68 -0
- package/dist/src/question/terminal-screen.js.map +1 -0
- package/dist/src/question/type/question-prompt.input.d.ts +20 -0
- package/dist/src/question/type/question-prompt.input.js +2 -0
- package/dist/src/question/type/question-prompt.input.js.map +1 -0
- package/dist/src/question/type/question-prompt.types.d.ts +104 -0
- package/dist/src/question/type/question-prompt.types.js +2 -0
- package/dist/src/question/type/question-prompt.types.js.map +1 -0
- package/dist/src/question/util/answer-flag.util.d.ts +12 -0
- package/dist/src/question/util/answer-flag.util.js +22 -0
- package/dist/src/question/util/answer-flag.util.js.map +1 -0
- package/dist/src/question/util/question-frame.util.d.ts +22 -0
- package/dist/src/question/util/question-frame.util.js +49 -0
- package/dist/src/question/util/question-frame.util.js.map +1 -0
- package/dist/src/question/util/terminal-text.util.d.ts +7 -0
- package/dist/src/question/util/terminal-text.util.js +15 -0
- package/dist/src/question/util/terminal-text.util.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { GUTTER, defaultAnswer, gutterLine, questionHeader } from './util/question-frame.util.js';
|
|
2
|
+
import { displayWidth, withoutLastGrapheme } from './util/terminal-text.util.js';
|
|
3
|
+
const IGNORE = { kind: 'ignore' };
|
|
4
|
+
const REDRAW = { kind: 'redraw' };
|
|
5
|
+
/** The row a `both` question adds after its options; taking it opens the free-text buffer. */
|
|
6
|
+
const OTHER_LABEL = 'Other…';
|
|
7
|
+
/** The keys each arm takes, as its frame prints them under the rows (record `prompt`). */
|
|
8
|
+
const SELECT_KEYS = ' ↑↓ · Enter';
|
|
9
|
+
const MULTISELECT_KEYS = ' ↑↓ move · Space toggle · Enter confirm · none selected is a valid answer';
|
|
10
|
+
const MULTILINE_KEYS = '└ Enter starts a new line · an empty line then Ctrl-D finishes · Ctrl-C aborts';
|
|
11
|
+
/** Why Esc did not answer: a question without a default has no Esc arm. */
|
|
12
|
+
const NO_DEFAULT_REASON = 'This question has no default, so Esc cannot answer it.';
|
|
13
|
+
/** Why Ctrl-D did not answer: an empty answer is refused, never sent. */
|
|
14
|
+
const EMPTY_ANSWER_REASON = 'An empty answer is refused — type it, then an empty line and Ctrl-D.';
|
|
15
|
+
/** The first code point a terminal prints rather than acts on. */
|
|
16
|
+
const FIRST_PRINTABLE_CODE_POINT = 0x20;
|
|
17
|
+
/**
|
|
18
|
+
* One way of answering a question on a terminal, as a key-event reducer: each
|
|
19
|
+
* keypress returns the step it takes, and {@link frame} draws the state it
|
|
20
|
+
* leaves. Esc is the same everywhere — the question's declared default with
|
|
21
|
+
* `defaulted: true`, or, with none declared, a redraw that says why.
|
|
22
|
+
*/
|
|
23
|
+
export class PromptArm {
|
|
24
|
+
question;
|
|
25
|
+
constructor(question) {
|
|
26
|
+
this.question = question;
|
|
27
|
+
}
|
|
28
|
+
/** The arm a question opens on: the free-text buffer, a checklist or a list. */
|
|
29
|
+
static of(question) {
|
|
30
|
+
if (question.kind === 'text')
|
|
31
|
+
return new MultilineArm(question);
|
|
32
|
+
if (question.allowMultiple)
|
|
33
|
+
return new MultiselectArm(question);
|
|
34
|
+
return new SelectArm(question);
|
|
35
|
+
}
|
|
36
|
+
escape() {
|
|
37
|
+
const answer = defaultAnswer(this.question);
|
|
38
|
+
if (answer === undefined)
|
|
39
|
+
return { kind: 'refuse', reason: NO_DEFAULT_REASON };
|
|
40
|
+
return { kind: 'answer', answer };
|
|
41
|
+
}
|
|
42
|
+
/** The key line's Esc arm, naming the default it takes — none without a default. */
|
|
43
|
+
escapeHint() {
|
|
44
|
+
const fallback = this.question.options.find((option) => option.id === this.question.defaultOptionId);
|
|
45
|
+
return fallback === undefined ? '' : ` · Esc → default (${fallback.label})`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A cursor list over a question's options, plus "Other…" on a `both` question.
|
|
50
|
+
* The `❯` is the cursor; ↑ and ↓ move it and wrap at either end.
|
|
51
|
+
*/
|
|
52
|
+
class ListArm extends PromptArm {
|
|
53
|
+
cursor = 0;
|
|
54
|
+
/** The row "Other…" stands on — past the last option. */
|
|
55
|
+
get otherRow() {
|
|
56
|
+
return this.question.options.length;
|
|
57
|
+
}
|
|
58
|
+
/** Every row's label, "Other…" last on a `both` question. */
|
|
59
|
+
rowLabels() {
|
|
60
|
+
const labels = this.question.options.map((option) => option.label);
|
|
61
|
+
return this.question.kind === 'both' ? [...labels, OTHER_LABEL] : labels;
|
|
62
|
+
}
|
|
63
|
+
move(step) {
|
|
64
|
+
const rows = this.rowLabels().length;
|
|
65
|
+
this.cursor = (this.cursor + step + rows) % rows;
|
|
66
|
+
return REDRAW;
|
|
67
|
+
}
|
|
68
|
+
marker(row) {
|
|
69
|
+
return row === this.cursor ? '❯' : ' ';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A single choice (record `prompt?case=select`): the hint under each label,
|
|
74
|
+
* Enter answers the option under the cursor, "Other…" opens the free-text
|
|
75
|
+
* buffer.
|
|
76
|
+
*/
|
|
77
|
+
class SelectArm extends ListArm {
|
|
78
|
+
press(key) {
|
|
79
|
+
switch (key.name) {
|
|
80
|
+
case 'up':
|
|
81
|
+
return this.move(-1);
|
|
82
|
+
case 'down':
|
|
83
|
+
return this.move(1);
|
|
84
|
+
case 'return':
|
|
85
|
+
case 'enter':
|
|
86
|
+
return this.choose();
|
|
87
|
+
case 'escape':
|
|
88
|
+
return this.escape();
|
|
89
|
+
default:
|
|
90
|
+
return IGNORE;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
frame(columns) {
|
|
94
|
+
const rows = this.rowLabels().flatMap((label, row) => this.rowLines(label, row));
|
|
95
|
+
return {
|
|
96
|
+
lines: [
|
|
97
|
+
...questionHeader(this.question, columns),
|
|
98
|
+
...rows,
|
|
99
|
+
'',
|
|
100
|
+
`${SELECT_KEYS}${this.escapeHint()}`,
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
rowLines(label, row) {
|
|
105
|
+
const line = `${this.marker(row)} ${label}`;
|
|
106
|
+
const hint = this.question.options.at(row)?.hint;
|
|
107
|
+
return hint === undefined ? [line] : [line, ` ${hint}`];
|
|
108
|
+
}
|
|
109
|
+
choose() {
|
|
110
|
+
const option = this.question.options.at(this.cursor);
|
|
111
|
+
if (option === undefined)
|
|
112
|
+
return { kind: 'open', arm: new MultilineArm(this.question) };
|
|
113
|
+
return { kind: 'answer', answer: { questionId: this.question.id, optionIds: [option.id] } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Several choices (record `prompt?case=multiselect`): `◉` / `◯` carry what is
|
|
118
|
+
* chosen and the `❯` where the cursor is — two things that never move
|
|
119
|
+
* together. Space toggles the row under the cursor; Enter answers the whole
|
|
120
|
+
* set, and an empty set is an answer. With "Other…" ticked, Enter opens the
|
|
121
|
+
* free-text buffer and the ticked options ride along with the text.
|
|
122
|
+
*/
|
|
123
|
+
class MultiselectArm extends ListArm {
|
|
124
|
+
ticked = new Set();
|
|
125
|
+
press(key) {
|
|
126
|
+
switch (key.name) {
|
|
127
|
+
case 'up':
|
|
128
|
+
return this.move(-1);
|
|
129
|
+
case 'down':
|
|
130
|
+
return this.move(1);
|
|
131
|
+
case 'space':
|
|
132
|
+
return this.toggle();
|
|
133
|
+
case 'return':
|
|
134
|
+
case 'enter':
|
|
135
|
+
return this.confirm();
|
|
136
|
+
case 'escape':
|
|
137
|
+
return this.escape();
|
|
138
|
+
default:
|
|
139
|
+
return IGNORE;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
frame(columns) {
|
|
143
|
+
const rows = this.rowLabels().map((label, row) => `${this.marker(row)} ${this.ticked.has(row) ? '◉' : '◯'} ${label}`);
|
|
144
|
+
return {
|
|
145
|
+
lines: [
|
|
146
|
+
...questionHeader(this.question, columns),
|
|
147
|
+
...rows,
|
|
148
|
+
'',
|
|
149
|
+
`${MULTISELECT_KEYS}${this.escapeHint()}`,
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
toggle() {
|
|
154
|
+
if (this.ticked.has(this.cursor)) {
|
|
155
|
+
this.ticked.delete(this.cursor);
|
|
156
|
+
return REDRAW;
|
|
157
|
+
}
|
|
158
|
+
this.ticked.add(this.cursor);
|
|
159
|
+
return REDRAW;
|
|
160
|
+
}
|
|
161
|
+
confirm() {
|
|
162
|
+
const optionIds = this.question.options
|
|
163
|
+
.filter((_option, row) => this.ticked.has(row))
|
|
164
|
+
.map((option) => option.id);
|
|
165
|
+
if (this.ticked.has(this.otherRow)) {
|
|
166
|
+
return { kind: 'open', arm: new MultilineArm(this.question, optionIds) };
|
|
167
|
+
}
|
|
168
|
+
return { kind: 'answer', answer: { questionId: this.question.id, optionIds } };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Free text (record `prompt?case=multiline`): a buffer behind the `│` gutter.
|
|
173
|
+
* Enter starts a new line, because a terminal has no Answer button; Ctrl-D on
|
|
174
|
+
* an empty line finishes, and an empty buffer is refused rather than sent. The
|
|
175
|
+
* placeholder is drawn once as the prompt's example line, never as text inside
|
|
176
|
+
* the buffer.
|
|
177
|
+
*/
|
|
178
|
+
class MultilineArm extends PromptArm {
|
|
179
|
+
ticked;
|
|
180
|
+
/** The lines Enter has closed. */
|
|
181
|
+
lines = [];
|
|
182
|
+
/** The line being typed. */
|
|
183
|
+
current = '';
|
|
184
|
+
/**
|
|
185
|
+
* @param ticked the options ticked beside "Other…" on a checklist: they are
|
|
186
|
+
* answered with the text; absent when the text is the whole answer.
|
|
187
|
+
*/
|
|
188
|
+
constructor(question, ticked) {
|
|
189
|
+
super(question);
|
|
190
|
+
this.ticked = ticked;
|
|
191
|
+
}
|
|
192
|
+
press(key) {
|
|
193
|
+
if (key.name === 'escape')
|
|
194
|
+
return this.escape();
|
|
195
|
+
if (key.ctrl === true && key.name === 'd')
|
|
196
|
+
return this.finish();
|
|
197
|
+
if (key.name === 'return' || key.name === 'enter')
|
|
198
|
+
return this.newline();
|
|
199
|
+
if (key.name === 'backspace')
|
|
200
|
+
return this.erase();
|
|
201
|
+
const text = this.printable(key);
|
|
202
|
+
if (text === undefined)
|
|
203
|
+
return IGNORE;
|
|
204
|
+
this.current += text;
|
|
205
|
+
return REDRAW;
|
|
206
|
+
}
|
|
207
|
+
frame(columns) {
|
|
208
|
+
const { placeholder } = this.question;
|
|
209
|
+
const example = placeholder === undefined ? [] : [` ${placeholder}`];
|
|
210
|
+
const above = [...questionHeader(this.question, columns), ...example, ''];
|
|
211
|
+
const buffer = [...this.lines, this.current].map(gutterLine);
|
|
212
|
+
return {
|
|
213
|
+
lines: [...above, ...buffer, `${MULTILINE_KEYS}${this.escapeHint()}`],
|
|
214
|
+
cursor: {
|
|
215
|
+
line: above.length + this.lines.length,
|
|
216
|
+
column: displayWidth(GUTTER) + 1 + displayWidth(this.current),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** Ctrl-D: finishes only on an empty line, and never with an empty answer. */
|
|
221
|
+
finish() {
|
|
222
|
+
if (this.current !== '')
|
|
223
|
+
return IGNORE;
|
|
224
|
+
const text = this.lines.join('\n').trimEnd();
|
|
225
|
+
if (text === '')
|
|
226
|
+
return { kind: 'refuse', reason: EMPTY_ANSWER_REASON };
|
|
227
|
+
return { kind: 'answer', answer: this.answer(text) };
|
|
228
|
+
}
|
|
229
|
+
answer(text) {
|
|
230
|
+
if (this.ticked === undefined)
|
|
231
|
+
return { questionId: this.question.id, text };
|
|
232
|
+
return { questionId: this.question.id, optionIds: [...this.ticked], text };
|
|
233
|
+
}
|
|
234
|
+
newline() {
|
|
235
|
+
this.lines.push(this.current);
|
|
236
|
+
this.current = '';
|
|
237
|
+
return REDRAW;
|
|
238
|
+
}
|
|
239
|
+
/** Backspace: the last character, or — on an empty line — back onto the line above. */
|
|
240
|
+
erase() {
|
|
241
|
+
if (this.current !== '') {
|
|
242
|
+
this.current = withoutLastGrapheme(this.current);
|
|
243
|
+
return REDRAW;
|
|
244
|
+
}
|
|
245
|
+
const previous = this.lines.pop();
|
|
246
|
+
if (previous === undefined)
|
|
247
|
+
return IGNORE;
|
|
248
|
+
this.current = previous;
|
|
249
|
+
return REDRAW;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The character a keypress types — none for a chord, a control key or an
|
|
253
|
+
* escape sequence, each of which opens below the first printable code point.
|
|
254
|
+
*/
|
|
255
|
+
printable(key) {
|
|
256
|
+
const { sequence } = key;
|
|
257
|
+
if (sequence === undefined || key.ctrl === true || key.meta === true)
|
|
258
|
+
return;
|
|
259
|
+
const lead = sequence.codePointAt(0) ?? 0;
|
|
260
|
+
return lead >= FIRST_PRINTABLE_CODE_POINT ? sequence : undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=prompt-arm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-arm.js","sourceRoot":"","sources":["../../../src/question/prompt-arm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
import type { QuestionPromptInput } from './type/question-prompt.input.js';
|
|
3
|
+
import type { CliQuestion, QuestionPrompt, QuestionPromptAnswer, QuestionPromptIo } from './type/question-prompt.types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The ONE way a `forgecart` command asks its questions (#2771; record
|
|
6
|
+
* `refs/design/cli-improvements`, screen `prompt`).
|
|
7
|
+
*
|
|
8
|
+
* A question is answered by the run first — `--answer`, then `--answers-file`,
|
|
9
|
+
* then `--yes`'s default. One they leave open is asked on the terminal when
|
|
10
|
+
* stdin AND stdout are TTYs: a cursor list, a checklist or a free-text buffer.
|
|
11
|
+
* On anything else it is refused with exit 2, naming the question — a run
|
|
12
|
+
* without a terminal never waits for one.
|
|
13
|
+
*/
|
|
14
|
+
export declare class QuestionPromptAdapter implements QuestionPrompt {
|
|
15
|
+
private readonly flags;
|
|
16
|
+
private readonly io;
|
|
17
|
+
/**
|
|
18
|
+
* @param flags the command's parsed `--answer`, `--answers-file` and `--yes`.
|
|
19
|
+
* @param io the terminal to ask on.
|
|
20
|
+
*/
|
|
21
|
+
constructor(flags: QuestionPromptInput, io?: QuestionPromptIo);
|
|
22
|
+
/**
|
|
23
|
+
* Register the three flags on a command whose questions this adapter asks.
|
|
24
|
+
* They stay out of `--help`: which questions a run asks is decided at run
|
|
25
|
+
* time, so the flags are taught where they apply — a run that leaves a
|
|
26
|
+
* question unanswered is told all three beside the question's id.
|
|
27
|
+
*/
|
|
28
|
+
static registerOptions(command: Command): Command;
|
|
29
|
+
/** Answer `questions`, in their order — from the run's flags, or on the terminal. */
|
|
30
|
+
ask(questions: readonly CliQuestion[]): Promise<QuestionPromptAnswer[]>;
|
|
31
|
+
private askOnTerminal;
|
|
32
|
+
/** A prompt renders only when both ends are a terminal: a TTY to read and a TTY to draw on. */
|
|
33
|
+
private isTerminal;
|
|
34
|
+
/** A choice needs options to choose from, and a default must be one of them. */
|
|
35
|
+
private static assertAskable;
|
|
36
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
2
|
+
import { GivenAnswers } from './given-answers.js';
|
|
3
|
+
import { QuestionUnansweredRefusal } from './question-unanswered-refusal.js';
|
|
4
|
+
import { TerminalQuestionSession } from './terminal-question-session.js';
|
|
5
|
+
import { collectAnswerFlag } from './util/answer-flag.util.js';
|
|
6
|
+
/**
|
|
7
|
+
* The ONE way a `forgecart` command asks its questions (#2771; record
|
|
8
|
+
* `refs/design/cli-improvements`, screen `prompt`).
|
|
9
|
+
*
|
|
10
|
+
* A question is answered by the run first — `--answer`, then `--answers-file`,
|
|
11
|
+
* then `--yes`'s default. One they leave open is asked on the terminal when
|
|
12
|
+
* stdin AND stdout are TTYs: a cursor list, a checklist or a free-text buffer.
|
|
13
|
+
* On anything else it is refused with exit 2, naming the question — a run
|
|
14
|
+
* without a terminal never waits for one.
|
|
15
|
+
*/
|
|
16
|
+
export class QuestionPromptAdapter {
|
|
17
|
+
flags;
|
|
18
|
+
io;
|
|
19
|
+
/**
|
|
20
|
+
* @param flags the command's parsed `--answer`, `--answers-file` and `--yes`.
|
|
21
|
+
* @param io the terminal to ask on.
|
|
22
|
+
*/
|
|
23
|
+
constructor(flags, io = { input: process.stdin, output: process.stdout }) {
|
|
24
|
+
this.flags = flags;
|
|
25
|
+
this.io = io;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register the three flags on a command whose questions this adapter asks.
|
|
29
|
+
* They stay out of `--help`: which questions a run asks is decided at run
|
|
30
|
+
* time, so the flags are taught where they apply — a run that leaves a
|
|
31
|
+
* question unanswered is told all three beside the question's id.
|
|
32
|
+
*/
|
|
33
|
+
static registerOptions(command) {
|
|
34
|
+
return command
|
|
35
|
+
.addOption(new Option('--answer <question=answer>', 'Answer a question without a terminal: its id or key, then an option id, comma-separated option ids, or the text (repeatable)')
|
|
36
|
+
.argParser(collectAnswerFlag)
|
|
37
|
+
.hideHelp())
|
|
38
|
+
.addOption(new Option('--answers-file <path>', 'Answer questions from a JSON object of question ids or keys to option ids or text').hideHelp())
|
|
39
|
+
.addOption(new Option('--yes', "Take each question's default; a question without one still needs an answer").hideHelp());
|
|
40
|
+
}
|
|
41
|
+
/** Answer `questions`, in their order — from the run's flags, or on the terminal. */
|
|
42
|
+
async ask(questions) {
|
|
43
|
+
for (const question of questions)
|
|
44
|
+
QuestionPromptAdapter.assertAskable(question);
|
|
45
|
+
const given = await GivenAnswers.from(this.flags);
|
|
46
|
+
const resolved = questions.map((question) => ({ question, answer: given.answerFor(question) }));
|
|
47
|
+
const open = resolved
|
|
48
|
+
.filter(({ answer }) => answer === undefined)
|
|
49
|
+
.map(({ question }) => question);
|
|
50
|
+
const asked = new Map();
|
|
51
|
+
if (open.length > 0) {
|
|
52
|
+
for (const answer of await this.askOnTerminal(open))
|
|
53
|
+
asked.set(answer.questionId, answer);
|
|
54
|
+
}
|
|
55
|
+
return resolved
|
|
56
|
+
.map(({ question, answer }) => answer ?? asked.get(question.id))
|
|
57
|
+
.filter((answer) => answer !== undefined);
|
|
58
|
+
}
|
|
59
|
+
async askOnTerminal(questions) {
|
|
60
|
+
if (!this.isTerminal())
|
|
61
|
+
throw new QuestionUnansweredRefusal(questions);
|
|
62
|
+
return new TerminalQuestionSession(this.io, questions).run();
|
|
63
|
+
}
|
|
64
|
+
/** A prompt renders only when both ends are a terminal: a TTY to read and a TTY to draw on. */
|
|
65
|
+
isTerminal() {
|
|
66
|
+
return this.io.input.isTTY === true && this.io.output.isTTY === true;
|
|
67
|
+
}
|
|
68
|
+
/** A choice needs options to choose from, and a default must be one of them. */
|
|
69
|
+
static assertAskable(question) {
|
|
70
|
+
if (question.kind !== 'text' && question.options.length === 0) {
|
|
71
|
+
throw new Error(`Question ${question.id} is a ${question.kind} question without options.`);
|
|
72
|
+
}
|
|
73
|
+
const { defaultOptionId } = question;
|
|
74
|
+
if (defaultOptionId === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (question.options.some((option) => option.id === defaultOptionId))
|
|
77
|
+
return;
|
|
78
|
+
throw new Error(`Question ${question.id} defaults to ${defaultOptionId}, which is none of its options.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=question-prompt.adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-prompt.adapter.js","sourceRoot":"","sources":["../../../src/question/question-prompt.adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CliRefusal } from '../exit-ladder.js';
|
|
2
|
+
import type { CliQuestion } from './type/question-prompt.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Questions a run neither answered nor could ask — exit
|
|
5
|
+
* {@link CliExitLadder.questionUnanswered}, never a hang (record
|
|
6
|
+
* `prompt?case=headless`). One line per question, naming it by the id
|
|
7
|
+
* `--answer` takes and the three flags that answer it; `--yes` is offered only
|
|
8
|
+
* where a default exists for it to take.
|
|
9
|
+
*/
|
|
10
|
+
export declare class QuestionUnansweredRefusal extends CliRefusal {
|
|
11
|
+
constructor(questions: readonly CliQuestion[]);
|
|
12
|
+
private static lineFor;
|
|
13
|
+
/** What `--answer` would carry: the declared default, or the shape an answer takes. */
|
|
14
|
+
private static exampleFor;
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CliExitLadder, CliRefusal } from '../exit-ladder.js';
|
|
2
|
+
/** The answers file a refusal points a headless run at. */
|
|
3
|
+
const ANSWERS_FILE = 'answers.json';
|
|
4
|
+
/**
|
|
5
|
+
* Questions a run neither answered nor could ask — exit
|
|
6
|
+
* {@link CliExitLadder.questionUnanswered}, never a hang (record
|
|
7
|
+
* `prompt?case=headless`). One line per question, naming it by the id
|
|
8
|
+
* `--answer` takes and the three flags that answer it; `--yes` is offered only
|
|
9
|
+
* where a default exists for it to take.
|
|
10
|
+
*/
|
|
11
|
+
export class QuestionUnansweredRefusal extends CliRefusal {
|
|
12
|
+
constructor(questions) {
|
|
13
|
+
super(CliExitLadder.questionUnanswered, questions.map((question) => QuestionUnansweredRefusal.lineFor(question)));
|
|
14
|
+
this.name = 'QuestionUnansweredRefusal';
|
|
15
|
+
}
|
|
16
|
+
static lineFor(question) {
|
|
17
|
+
const unanswered = `question ${question.id} "${question.question}" needs an answer`;
|
|
18
|
+
const answer = `--answer ${question.id}=${QuestionUnansweredRefusal.exampleFor(question)}`;
|
|
19
|
+
if (question.defaultOptionId === undefined) {
|
|
20
|
+
return `${unanswered} — pass ${answer} or --answers-file ${ANSWERS_FILE} (--yes cannot answer it: it has no default)`;
|
|
21
|
+
}
|
|
22
|
+
return `${unanswered} — pass ${answer}, --answers-file ${ANSWERS_FILE} or --yes`;
|
|
23
|
+
}
|
|
24
|
+
/** What `--answer` would carry: the declared default, or the shape an answer takes. */
|
|
25
|
+
static exampleFor(question) {
|
|
26
|
+
if (question.defaultOptionId !== undefined)
|
|
27
|
+
return question.defaultOptionId;
|
|
28
|
+
if (question.kind === 'text')
|
|
29
|
+
return '<text>';
|
|
30
|
+
if (question.kind === 'both')
|
|
31
|
+
return '<optionId|text>';
|
|
32
|
+
return question.allowMultiple ? '<optionId,…>' : '<optionId>';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=question-unanswered-refusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question-unanswered-refusal.js","sourceRoot":"","sources":["../../../src/question/question-unanswered-refusal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI9D,2DAA2D;AAC3D,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,OAAO,yBAA0B,SAAQ,UAAU;IACvD,YAAY,SAAiC;QAC3C,KAAK,CACH,aAAa,CAAC,kBAAkB,EAChC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CACzE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;IAEO,MAAM,CAAC,OAAO,CAAC,QAAqB;QAC1C,MAAM,UAAU,GAAG,YAAY,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,QAAQ,mBAAmB,CAAC;QACpF,MAAM,MAAM,GAAG,YAAY,QAAQ,CAAC,EAAE,IAAI,yBAAyB,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3F,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YAC3C,OAAO,GAAG,UAAU,WAAW,MAAM,sBAAsB,YAAY,8CAA8C,CAAC;QACxH,CAAC;QACD,OAAO,GAAG,UAAU,WAAW,MAAM,oBAAoB,YAAY,WAAW,CAAC;IACnF,CAAC;IAED,uFAAuF;IAC/E,MAAM,CAAC,UAAU,CAAC,QAAqB;QAC7C,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC,eAAe,CAAC;QAC5E,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,QAAQ,CAAC;QAC9C,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,iBAAiB,CAAC;QACvD,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC;IAChE,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CliQuestion, QuestionPromptAnswer, QuestionPromptIo } from './type/question-prompt.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Asks questions on a terminal, one prompt at a time: the keyboard in raw mode,
|
|
4
|
+
* each keypress reduced by the question's {@link PromptArm} and the frame it
|
|
5
|
+
* leaves drawn in place. An answered prompt collapses to the answer it sent and
|
|
6
|
+
* the next question is drawn below it — straight away, so keys typed ahead land
|
|
7
|
+
* on it. Ctrl-C aborts; a keyboard that ends leaves the rest unanswered.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TerminalQuestionSession {
|
|
10
|
+
private readonly io;
|
|
11
|
+
private readonly questions;
|
|
12
|
+
private readonly screen;
|
|
13
|
+
private readonly answers;
|
|
14
|
+
private arm;
|
|
15
|
+
/** Why the last keypress was refused, drawn under the frame until the next one. */
|
|
16
|
+
private note;
|
|
17
|
+
private settlement;
|
|
18
|
+
/** @param questions the questions to ask, in order — at least one. */
|
|
19
|
+
constructor(io: QuestionPromptIo, questions: readonly CliQuestion[]);
|
|
20
|
+
/** Ask every question; resolves with their answers, in order, once the last is answered. */
|
|
21
|
+
run(): Promise<QuestionPromptAnswer[]>;
|
|
22
|
+
/** The question being asked: the first one without an answer. */
|
|
23
|
+
private get current();
|
|
24
|
+
private readonly onKeypress;
|
|
25
|
+
private readonly onEnd;
|
|
26
|
+
private readonly onError;
|
|
27
|
+
private open;
|
|
28
|
+
private close;
|
|
29
|
+
private apply;
|
|
30
|
+
private redraw;
|
|
31
|
+
private draw;
|
|
32
|
+
/** Collapse the answered prompt to its answer, then ask the next question — or end. */
|
|
33
|
+
private settle;
|
|
34
|
+
private finish;
|
|
35
|
+
private fail;
|
|
36
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { PromptArm } from './prompt-arm.js';
|
|
3
|
+
import { QuestionUnansweredRefusal } from './question-unanswered-refusal.js';
|
|
4
|
+
import { TerminalScreen } from './terminal-screen.js';
|
|
5
|
+
import { answeredLines } from './util/question-frame.util.js';
|
|
6
|
+
/**
|
|
7
|
+
* Asks questions on a terminal, one prompt at a time: the keyboard in raw mode,
|
|
8
|
+
* each keypress reduced by the question's {@link PromptArm} and the frame it
|
|
9
|
+
* leaves drawn in place. An answered prompt collapses to the answer it sent and
|
|
10
|
+
* the next question is drawn below it — straight away, so keys typed ahead land
|
|
11
|
+
* on it. Ctrl-C aborts; a keyboard that ends leaves the rest unanswered.
|
|
12
|
+
*/
|
|
13
|
+
export class TerminalQuestionSession {
|
|
14
|
+
io;
|
|
15
|
+
questions;
|
|
16
|
+
screen;
|
|
17
|
+
answers = [];
|
|
18
|
+
arm;
|
|
19
|
+
/** Why the last keypress was refused, drawn under the frame until the next one. */
|
|
20
|
+
note;
|
|
21
|
+
settlement;
|
|
22
|
+
/** @param questions the questions to ask, in order — at least one. */
|
|
23
|
+
constructor(io, questions) {
|
|
24
|
+
this.io = io;
|
|
25
|
+
this.questions = questions;
|
|
26
|
+
this.screen = new TerminalScreen(io.output);
|
|
27
|
+
this.arm = PromptArm.of(this.current);
|
|
28
|
+
}
|
|
29
|
+
/** Ask every question; resolves with their answers, in order, once the last is answered. */
|
|
30
|
+
run() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
this.settlement = { resolve, reject };
|
|
33
|
+
this.open();
|
|
34
|
+
this.draw();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** The question being asked: the first one without an answer. */
|
|
38
|
+
get current() {
|
|
39
|
+
return this.questions[this.answers.length];
|
|
40
|
+
}
|
|
41
|
+
onKeypress = (_sequence, key) => {
|
|
42
|
+
if (key.ctrl === true && key.name === 'c') {
|
|
43
|
+
this.fail(new Error(`Aborted with Ctrl-C at question ${this.current.id}.`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.apply(this.arm.press(key));
|
|
47
|
+
};
|
|
48
|
+
onEnd = () => {
|
|
49
|
+
this.fail(new QuestionUnansweredRefusal(this.questions.slice(this.answers.length)));
|
|
50
|
+
};
|
|
51
|
+
onError = (error) => {
|
|
52
|
+
this.fail(error);
|
|
53
|
+
};
|
|
54
|
+
open() {
|
|
55
|
+
const { input } = this.io;
|
|
56
|
+
emitKeypressEvents(input);
|
|
57
|
+
input.setRawMode?.(true);
|
|
58
|
+
input.on('keypress', this.onKeypress);
|
|
59
|
+
input.on('end', this.onEnd);
|
|
60
|
+
input.on('error', this.onError);
|
|
61
|
+
input.resume();
|
|
62
|
+
}
|
|
63
|
+
close() {
|
|
64
|
+
const { input } = this.io;
|
|
65
|
+
input.off('keypress', this.onKeypress);
|
|
66
|
+
input.off('end', this.onEnd);
|
|
67
|
+
input.off('error', this.onError);
|
|
68
|
+
input.setRawMode?.(false);
|
|
69
|
+
input.pause();
|
|
70
|
+
this.screen.release();
|
|
71
|
+
this.screen.restoreCursor();
|
|
72
|
+
}
|
|
73
|
+
apply(step) {
|
|
74
|
+
switch (step.kind) {
|
|
75
|
+
case 'ignore':
|
|
76
|
+
return;
|
|
77
|
+
case 'redraw':
|
|
78
|
+
this.redraw();
|
|
79
|
+
return;
|
|
80
|
+
case 'refuse':
|
|
81
|
+
this.redraw(step.reason);
|
|
82
|
+
return;
|
|
83
|
+
case 'open':
|
|
84
|
+
this.arm = step.arm;
|
|
85
|
+
this.redraw();
|
|
86
|
+
return;
|
|
87
|
+
case 'answer':
|
|
88
|
+
this.settle(step.answer);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
redraw(note) {
|
|
92
|
+
this.note = note;
|
|
93
|
+
this.draw();
|
|
94
|
+
}
|
|
95
|
+
draw() {
|
|
96
|
+
const frame = this.arm.frame(this.screen.columns);
|
|
97
|
+
if (this.note === undefined) {
|
|
98
|
+
this.screen.draw(frame);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.screen.draw({ ...frame, lines: [...frame.lines, ` ${this.note}`] });
|
|
102
|
+
}
|
|
103
|
+
/** Collapse the answered prompt to its answer, then ask the next question — or end. */
|
|
104
|
+
settle(answer) {
|
|
105
|
+
this.screen.draw({ lines: answeredLines(this.current, answer, this.screen.columns) });
|
|
106
|
+
this.screen.release();
|
|
107
|
+
this.answers.push(answer);
|
|
108
|
+
if (this.answers.length === this.questions.length) {
|
|
109
|
+
this.finish();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.arm = PromptArm.of(this.current);
|
|
113
|
+
this.redraw();
|
|
114
|
+
}
|
|
115
|
+
finish() {
|
|
116
|
+
this.close();
|
|
117
|
+
this.settlement?.resolve(this.answers);
|
|
118
|
+
this.settlement = undefined;
|
|
119
|
+
}
|
|
120
|
+
fail(error) {
|
|
121
|
+
this.close();
|
|
122
|
+
this.settlement?.reject(error);
|
|
123
|
+
this.settlement = undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=terminal-question-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-question-session.js","sourceRoot":"","sources":["../../../src/question/terminal-question-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PromptFrame, TerminalDisplay } from './type/question-prompt.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The block of terminal rows a prompt owns. Each frame erases the one before it
|
|
4
|
+
* and is drawn in its place, so a prompt redraws without scrolling; rows are
|
|
5
|
+
* counted as the terminal wraps them, so a long line still erases whole.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TerminalScreen {
|
|
8
|
+
private readonly display;
|
|
9
|
+
/** Rows the drawn block covers. */
|
|
10
|
+
private blockRows;
|
|
11
|
+
/** The row of the block the cursor rests on — `blockRows` when it rests below it. */
|
|
12
|
+
private cursorRow;
|
|
13
|
+
constructor(display: TerminalDisplay);
|
|
14
|
+
/** The terminal's width in cells. */
|
|
15
|
+
get columns(): number;
|
|
16
|
+
/** Replace the drawn block with `frame`. */
|
|
17
|
+
draw(frame: PromptFrame): void;
|
|
18
|
+
/** Leave the drawn block on screen; whatever is written next starts on the row below it. */
|
|
19
|
+
release(): void;
|
|
20
|
+
/** Hand the terminal its cursor back. */
|
|
21
|
+
restoreCursor(): void;
|
|
22
|
+
/** Put the cursor where the frame asks, or hide it below the block. */
|
|
23
|
+
private rest;
|
|
24
|
+
/** The rows one line takes once the terminal wraps it — an empty line still takes one. */
|
|
25
|
+
private rowsOf;
|
|
26
|
+
}
|