@orkestrel/terminal 0.0.14 → 0.0.16
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/README.md +10 -11
- package/dist/src/core/index.cjs +118 -83
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +265 -150
- package/dist/src/core/index.d.ts +265 -150
- package/dist/src/core/index.js +119 -84
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +111 -86
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +122 -92
- package/dist/src/server/index.d.ts +122 -92
- package/dist/src/server/index.js +111 -86
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -19
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_console = require("@orkestrel/console");
|
|
3
3
|
let _src_core = require("../core/index.cjs");
|
|
4
|
+
let _orkestrel_contract = require("@orkestrel/contract");
|
|
4
5
|
let _orkestrel_form = require("@orkestrel/form");
|
|
5
6
|
let _orkestrel_console_server = require("@orkestrel/console/server");
|
|
6
7
|
let node_readline = require("node:readline");
|
|
7
8
|
let node_process = require("node:process");
|
|
8
9
|
//#region src/server/constants.ts
|
|
9
10
|
/**
|
|
10
|
-
* Holds the cursor-
|
|
11
|
-
* interpolates the `{count}` placeholder with the
|
|
12
|
-
* the count stays out of the constant.
|
|
11
|
+
* Holds the cursor-up sequence template (`ESC[{count}A`) —
|
|
12
|
+
* {@link import('./helpers.js').renderCursorUp} interpolates the `{count}` placeholder with the
|
|
13
|
+
* number of lines to climb. Kept as a template so the count stays out of the constant.
|
|
13
14
|
*/
|
|
14
15
|
var CSI_UP = `${_orkestrel_console.CSI}{count}A`;
|
|
15
16
|
/**
|
|
@@ -20,9 +21,9 @@ var CURSOR_HIDE = `${_orkestrel_console.CSI}?25l`;
|
|
|
20
21
|
/** Shows the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */
|
|
21
22
|
var CURSOR_SHOW = `${_orkestrel_console.CSI}?25h`;
|
|
22
23
|
/**
|
|
23
|
-
* Erases from the cursor down to the end of the screen (`ESC[J`) — wipes the
|
|
24
|
-
*
|
|
25
|
-
* never leaves orphaned rows
|
|
24
|
+
* Erases from the cursor down to the end of the screen (`ESC[J`) — wipes the whole previous view,
|
|
25
|
+
* which a `select` or `checkbox` can spread over several lines, in one write before the new view is
|
|
26
|
+
* rendered, so a redraw never leaves orphaned rows behind.
|
|
26
27
|
*/
|
|
27
28
|
var CLEAR_DOWN = `${_orkestrel_console.CSI}J`;
|
|
28
29
|
/**
|
|
@@ -54,9 +55,15 @@ var CONTROL_HINTS = Object.freeze({
|
|
|
54
55
|
});
|
|
55
56
|
/** Holds the instruction a `file` field with `multiple` shows before its entries — one path per line, and a blank line ends the list. */
|
|
56
57
|
var FILE_HINT = "One path per line, blank to finish";
|
|
57
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Holds the lead on the line listing an open `select`'s offered values, which a typed answer can
|
|
60
|
+
* ignore.
|
|
61
|
+
*/
|
|
58
62
|
var SUGGESTION_LEAD = "Suggestions";
|
|
59
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Holds the lead on the line listing the choices a `select` or `checkbox` shows but refuses, so a
|
|
65
|
+
* reader sees why one is missing from the list it heads.
|
|
66
|
+
*/
|
|
60
67
|
var UNAVAILABLE_LEAD = "Unavailable";
|
|
61
68
|
/** Holds the mark on a locked field's line — the walk renders its value and moves on, because the form refuses an edit there. */
|
|
62
69
|
var LOCKED_MARK = "(locked)";
|
|
@@ -70,55 +77,55 @@ var REFUSAL_MESSAGE = "Enter a value this field accepts";
|
|
|
70
77
|
//#endregion
|
|
71
78
|
//#region src/server/helpers.ts
|
|
72
79
|
/**
|
|
73
|
-
* Checks whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` /
|
|
74
|
-
* `'data'` subscription methods. A total type guard: it
|
|
75
|
-
*
|
|
76
|
-
* or a fake TTY a test injects) to the exact slice the driver reads
|
|
80
|
+
* Checks whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` /
|
|
81
|
+
* `off` `'data'` subscription methods. A total type guard: it never throws and returns `false` for
|
|
82
|
+
* anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`,
|
|
83
|
+
* or a fake TTY a test injects) to the exact slice the driver reads, never an assertion.
|
|
77
84
|
*
|
|
78
85
|
* @remarks
|
|
79
86
|
* Only `on` / `off` are required (the irreducible event seam); `setRawMode` / `resume` / `pause` /
|
|
80
87
|
* `isTTY` are optional on {@link InputStreamInterface}, so their absence does not disqualify a stream
|
|
81
|
-
* — a piped, non-TTY stream is still a valid input,
|
|
88
|
+
* — a piped, non-TTY stream is still a valid input, one the driver reads through the readline
|
|
82
89
|
* fallback rather than raw mode.
|
|
83
90
|
*
|
|
84
91
|
* @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
|
|
85
92
|
* @returns True if `value` has callable `on` and `off`; false otherwise
|
|
86
93
|
*/
|
|
87
94
|
function isInputStream(value) {
|
|
88
|
-
return
|
|
95
|
+
return (0, _orkestrel_contract.isObject)(value) && "on" in value && (0, _orkestrel_contract.isFunction)(value.on) && "off" in value && (0, _orkestrel_contract.isFunction)(value.off);
|
|
89
96
|
}
|
|
90
97
|
/**
|
|
91
98
|
* Checks whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard
|
|
92
|
-
* checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface`
|
|
93
|
-
* as its `input`. The non-TTY fallback narrows the resolved input stream through this
|
|
94
|
-
* it to readline
|
|
95
|
-
* crosses into the readline boundary honestly. Never throws; returns
|
|
96
|
-
* isn't a full readable.
|
|
99
|
+
* checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface`
|
|
100
|
+
* requires as its `input`. The non-TTY fallback narrows the resolved input stream through this
|
|
101
|
+
* before handing it to readline, never through an assertion, so a real piped `process.stdin` (or a
|
|
102
|
+
* `PassThrough` a test injects) crosses into the readline boundary honestly. Never throws; returns
|
|
103
|
+
* `false` for a minimal fake that isn't a full readable.
|
|
97
104
|
*
|
|
98
105
|
* @param value - The resolved input stream (or any value crossing the boundary)
|
|
99
106
|
* @returns True if `value` has the readable methods readline needs; false otherwise
|
|
100
107
|
*/
|
|
101
108
|
function isReadable(value) {
|
|
102
|
-
return
|
|
109
|
+
return (0, _orkestrel_contract.isObject)(value) && "read" in value && (0, _orkestrel_contract.isFunction)(value.read) && "pipe" in value && (0, _orkestrel_contract.isFunction)(value.pipe);
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
105
|
-
* Checks whether an input stream can be driven in
|
|
106
|
-
* exposes a callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to
|
|
107
|
-
*
|
|
108
|
-
* `node:readline` line-input fallback
|
|
109
|
-
* never throws.
|
|
112
|
+
* Checks whether an input stream can be driven in raw mode — it reports `isTTY === true` and
|
|
113
|
+
* exposes a callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to
|
|
114
|
+
* choose its path: true selects the interactive raw-mode fields, with arrow-key navigation and a
|
|
115
|
+
* live re-render; false selects the `node:readline` line-input fallback, because a piped or
|
|
116
|
+
* non-terminal stream cannot enter raw mode. Total — never throws.
|
|
110
117
|
*
|
|
111
118
|
* @param input - The resolved {@link InputStreamInterface}
|
|
112
119
|
* @returns True if the stream is a TTY with `setRawMode`; false otherwise
|
|
113
120
|
*/
|
|
114
121
|
function supportsRawMode(input) {
|
|
115
|
-
return input.isTTY === true &&
|
|
122
|
+
return input.isTTY === true && (0, _orkestrel_contract.isFunction)(input.setRawMode);
|
|
116
123
|
}
|
|
117
124
|
/**
|
|
118
|
-
* Counts the terminal
|
|
119
|
-
*
|
|
120
|
-
* re-render: the driver records the line count of the view it
|
|
121
|
-
* far up to move the cursor before overwriting. Total; an empty string is one
|
|
125
|
+
* Counts the terminal lines a rendered prompt `view` occupies — one more than its newline count, so
|
|
126
|
+
* a view with no newline is a single line and a view with N newlines spans N+1 lines. The basis of
|
|
127
|
+
* the in-place re-render: the driver records the line count of the view it wrote so the next redraw
|
|
128
|
+
* knows how far up to move the cursor before overwriting. Total; an empty string is one empty line.
|
|
122
129
|
*
|
|
123
130
|
* @param view - The rendered (possibly multi-line, possibly ANSI-styled) view string
|
|
124
131
|
* @returns The number of lines the view spans (always at least 1)
|
|
@@ -129,10 +136,10 @@ function lineCount(view) {
|
|
|
129
136
|
return lines;
|
|
130
137
|
}
|
|
131
138
|
/**
|
|
132
|
-
* Returns the cursor-
|
|
133
|
-
* empty string when `count` is zero or negative
|
|
134
|
-
* The pure step the in-place re-render uses to climb back over the previous view
|
|
135
|
-
* Total.
|
|
139
|
+
* Returns the cursor-up control sequence that moves the cursor up `count` lines (`ESC[{count}A`),
|
|
140
|
+
* or the empty string when `count` is zero or negative, because no movement is needed and `ESC[0A`
|
|
141
|
+
* is a wasted write. The pure step the in-place re-render uses to climb back over the previous view
|
|
142
|
+
* before clearing it. Total.
|
|
136
143
|
*
|
|
137
144
|
* @param count - How many lines to move the cursor up
|
|
138
145
|
* @returns The `ESC[{count}A` sequence, or `''` when `count <= 0`
|
|
@@ -142,31 +149,31 @@ function renderCursorUp(count) {
|
|
|
142
149
|
return `${CSI_UP.replace("{count}", String(count))}`;
|
|
143
150
|
}
|
|
144
151
|
/**
|
|
145
|
-
* Returns the full reposition-and-clear prefix to write
|
|
146
|
-
* the line count of the
|
|
147
|
-
* 0, and erases everything from there to the end of the screen, so the next view is drawn
|
|
148
|
-
*
|
|
149
|
-
* followed by the new view.
|
|
152
|
+
* Returns the full reposition-and-clear prefix to write before re-rendering a prompt view in place
|
|
153
|
+
* — given the line count of the previous view, it moves the cursor up over those lines, returns it
|
|
154
|
+
* to column 0, and erases everything from there to the end of the screen, so the next view is drawn
|
|
155
|
+
* on a clean region and a taller previous view leaves no orphaned rows. Pure; the driver writes
|
|
156
|
+
* this immediately followed by the new view.
|
|
150
157
|
*
|
|
151
158
|
* @remarks
|
|
152
|
-
* For the
|
|
153
|
-
* the prefix is
|
|
154
|
-
* subsequent render it climbs `previousLines - 1` lines (the cursor is on the
|
|
159
|
+
* For the first render `previousLines` is `1` (the cursor sits on the line the prompt opened on) so
|
|
160
|
+
* the prefix is a carriage return + clear-down — the prompt draws from the current line. For a
|
|
161
|
+
* subsequent render it climbs `previousLines - 1` lines (the cursor is on the last line of the prior
|
|
155
162
|
* view) before clearing. Keeping the math here (not in the driver) makes the re-render unit-testable
|
|
156
163
|
* without a real terminal.
|
|
157
164
|
*
|
|
158
|
-
* @param previousLines - The line count of the view
|
|
165
|
+
* @param previousLines - The line count of the view on screen (from {@link lineCount})
|
|
159
166
|
* @returns The control-sequence prefix to write before the new view
|
|
160
167
|
*/
|
|
161
168
|
function redrawPrefix(previousLines) {
|
|
162
169
|
return `${renderCursorUp(previousLines - 1)}${_src_core.RETURN}${CLEAR_DOWN}`;
|
|
163
170
|
}
|
|
164
171
|
/**
|
|
165
|
-
* Projects any field the walk reads as a
|
|
166
|
-
* takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`,
|
|
167
|
-
* `
|
|
168
|
-
*
|
|
169
|
-
* projection carries no rule, because the
|
|
172
|
+
* Projects any field the walk reads as a line of text into the {@link TextField} the text reducer
|
|
173
|
+
* takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`, `time`,
|
|
174
|
+
* `datetime`, `color`, and one `file` entry. The label carries that control's format cue from
|
|
175
|
+
* {@link CONTROL_HINTS}, and a declared `default` becomes the line a bare return submits. The
|
|
176
|
+
* projection carries no rule, because the authoritative form still evaluates the answer this line
|
|
170
177
|
* binds; it exists only so one reducer covers every one of them.
|
|
171
178
|
*
|
|
172
179
|
* @param field - The field being read
|
|
@@ -182,12 +189,12 @@ function fieldToText(field) {
|
|
|
182
189
|
const label = field.label ?? field.name;
|
|
183
190
|
const hint = CONTROL_HINTS[field.control];
|
|
184
191
|
const seed = "default" in field ? field.default : void 0;
|
|
185
|
-
const text =
|
|
192
|
+
const text = (0, _orkestrel_contract.isNumber)(seed) ? String(seed) : seed;
|
|
186
193
|
return {
|
|
187
194
|
control: "text",
|
|
188
195
|
name: field.name,
|
|
189
196
|
label: hint === void 0 ? label : `${label} ${hint}`,
|
|
190
|
-
...
|
|
197
|
+
...(0, _orkestrel_contract.isString)(text) ? { default: text } : {}
|
|
191
198
|
};
|
|
192
199
|
}
|
|
193
200
|
/**
|
|
@@ -200,13 +207,13 @@ function fieldToText(field) {
|
|
|
200
207
|
*/
|
|
201
208
|
function valueToText(value) {
|
|
202
209
|
if (value === void 0) return "";
|
|
203
|
-
if (
|
|
204
|
-
if (
|
|
205
|
-
if (
|
|
210
|
+
if ((0, _orkestrel_contract.isString)(value)) return value;
|
|
211
|
+
if ((0, _orkestrel_contract.isNumber)(value)) return String(value);
|
|
212
|
+
if ((0, _orkestrel_contract.isBoolean)(value)) return value ? "yes" : "no";
|
|
206
213
|
return value.join(", ");
|
|
207
214
|
}
|
|
208
215
|
/**
|
|
209
|
-
* Returns the choices a `select` or `checkbox` field actually
|
|
216
|
+
* Returns the choices a `select` or `checkbox` field actually offers — the form refuses a disabled
|
|
210
217
|
* choice's value at every door, including a fill, so the walk never puts one in front of the
|
|
211
218
|
* cursor. Pair with {@link filterDisabled} to tell the reader what was withheld.
|
|
212
219
|
*
|
|
@@ -217,7 +224,7 @@ function filterEnabled(choices) {
|
|
|
217
224
|
return choices.filter((choice) => choice.disabled !== true);
|
|
218
225
|
}
|
|
219
226
|
/**
|
|
220
|
-
* Returns the choices a `select` or `checkbox` field
|
|
227
|
+
* Returns the choices a `select` or `checkbox` field shows but refuses — the complement of
|
|
221
228
|
* {@link filterEnabled}, rendered by {@link renderUnavailableLine} above the list so a reader sees
|
|
222
229
|
* why a declared choice is missing from it.
|
|
223
230
|
*
|
|
@@ -240,9 +247,9 @@ function renderGroupHeader(styler, theme, label) {
|
|
|
240
247
|
return styler.render(theme.roles.message, label);
|
|
241
248
|
}
|
|
242
249
|
/**
|
|
243
|
-
* Renders the read-only line a
|
|
244
|
-
* answer the form already holds. The walk writes this instead of a prompt, because the field is
|
|
245
|
-
* validated and still submitted but must not be edited here.
|
|
250
|
+
* Renders the read-only line a locked field shows — its label, the {@link LOCKED_MARK}, and the
|
|
251
|
+
* answer the form already holds. The walk writes this instead of a prompt, because the field is
|
|
252
|
+
* still validated and still submitted but must not be edited here.
|
|
246
253
|
*
|
|
247
254
|
* @param styler - The console styler that renders each role
|
|
248
255
|
* @param theme - The resolved prompt theme
|
|
@@ -255,7 +262,7 @@ function renderLockedLine(styler, theme, label, value) {
|
|
|
255
262
|
return value.length === 0 ? head : `${head} ${styler.render(theme.roles.content, value)}`;
|
|
256
263
|
}
|
|
257
264
|
/**
|
|
258
|
-
* Renders the line listing an
|
|
265
|
+
* Renders the line listing an open select's offered values above its text prompt — a suggestion
|
|
259
266
|
* list, because an open select admits an answer the list does not offer.
|
|
260
267
|
*
|
|
261
268
|
* @param styler - The console styler that renders each role
|
|
@@ -296,12 +303,13 @@ function renderNumberedList(styler, theme, choices) {
|
|
|
296
303
|
//#endregion
|
|
297
304
|
//#region src/server/Terminal.ts
|
|
298
305
|
/**
|
|
299
|
-
* Implements {@link TerminalInterface} for a human at this machine's keyboard — the interactive
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* the form it is given, and this class owns only raw mode, the cursor, and
|
|
306
|
+
* Implements {@link TerminalInterface} for a human at this machine's keyboard — the interactive
|
|
307
|
+
* form driver, and the only impure part of the terminal stack. {@link ask} walks one form's fields
|
|
308
|
+
* in schema order, feeds raw-mode stdin bytes through `parseKey` into the matching pure reducer,
|
|
309
|
+
* renders each returned view in place, binds every answer through the form's own `fill`, and
|
|
310
|
+
* re-asks what the form refused. It owns no form logic: the schema, the rules, the values, and the
|
|
311
|
+
* settlement all belong to the form it is given, and this class owns only raw mode, the cursor, and
|
|
312
|
+
* the re-render.
|
|
305
313
|
*
|
|
306
314
|
* @remarks
|
|
307
315
|
* See {@link TerminalInterface} for the driving contract. The walk itself:
|
|
@@ -313,12 +321,11 @@ function renderNumberedList(styler, theme, choices) {
|
|
|
313
321
|
* `open` means the answer need not come from the list.
|
|
314
322
|
* - **The binding projects through `matchesAnswer`.** Every answer is filled as
|
|
315
323
|
* `fill(name, matchesAnswer(value) ? value : undefined)` after `parseValue` has coerced it to the
|
|
316
|
-
* control's own shape, so a bare return on a field with no default binds as
|
|
324
|
+
* control's own shape, so a bare return on a field with no default binds as absence and the
|
|
317
325
|
* form's `required` rule refuses it. A typed answer the control cannot hold binds as absence and
|
|
318
326
|
* invalidates the field, so the walk asks again with the reason on screen.
|
|
319
|
-
* - **Visibility is honored.** A `hidden` field and a field
|
|
320
|
-
*
|
|
321
|
-
* header.
|
|
327
|
+
* - **Visibility is honored.** A `hidden` field and a field in `form.disabled` are skipped; a
|
|
328
|
+
* `locked` field renders read-only; entering a new group writes its label as a section header.
|
|
322
329
|
* - **Refusal re-asks.** After the walk the form is submitted. A refusal re-walks only the erroring
|
|
323
330
|
* fields the walk can edit and submits again. When every erroring field is one the walk cannot
|
|
324
331
|
* edit — hidden, locked, or disabled — the form is abandoned instead, because asking again could
|
|
@@ -746,12 +753,12 @@ var Terminal = class {
|
|
|
746
753
|
//#endregion
|
|
747
754
|
//#region src/server/factories.ts
|
|
748
755
|
/**
|
|
749
|
-
* Creates the interactive terminal form driver — the local-keyboard arm of the terminal trio,
|
|
750
|
-
* the core headless `createPrompt` broker and the SSE `createPromptClient` bridge. Where the
|
|
751
|
-
*
|
|
752
|
-
* form's fields in schema order, drives each control's pure reducer over raw-mode stdin,
|
|
753
|
-
* answer through the form's own `fill`, and submits. It is the only impure part of the
|
|
754
|
-
* stack.
|
|
756
|
+
* Creates the interactive terminal form driver — the local-keyboard arm of the terminal trio,
|
|
757
|
+
* beside the core headless `createPrompt` broker and the SSE `createPromptClient` bridge. Where the
|
|
758
|
+
* broker parks a live form until somebody elsewhere answers it, a `Terminal` answers one here: it
|
|
759
|
+
* walks the form's fields in schema order, drives each control's pure reducer over raw-mode stdin,
|
|
760
|
+
* binds every answer through the form's own `fill`, and submits. It is the only impure part of the
|
|
761
|
+
* terminal stack.
|
|
755
762
|
*
|
|
756
763
|
* @param options - See {@link TerminalOptions}
|
|
757
764
|
* @returns A {@link TerminalInterface} whose `ask` drives one whole form over the resolved streams
|
|
@@ -771,21 +778,39 @@ var Terminal = class {
|
|
|
771
778
|
* - **Non-TTY fallback.** A piped stream cannot enter raw mode, so the same walk runs over
|
|
772
779
|
* `node:readline` line input.
|
|
773
780
|
*
|
|
774
|
-
* @example
|
|
781
|
+
* @example Ask one form at this keyboard
|
|
775
782
|
* ```ts
|
|
776
783
|
* import { createForm } from '@orkestrel/form'
|
|
784
|
+
* import { isTerminalError } from '@orkestrel/terminal'
|
|
777
785
|
* import { createTerminal } from '@orkestrel/terminal/server'
|
|
778
786
|
*
|
|
779
|
-
* const terminal = createTerminal()
|
|
780
|
-
* const
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
787
|
+
* const terminal = createTerminal() // process.stdin / process.stdout by default
|
|
788
|
+
* const form = createForm({
|
|
789
|
+
* label: 'Sign up',
|
|
790
|
+
* fields: [
|
|
791
|
+
* { control: 'text', name: 'name', label: 'Your name', rule: { required: true, minimum: 2 } },
|
|
792
|
+
* { control: 'text', name: 'email', label: 'Email', rule: { required: true, email: true } },
|
|
793
|
+
* { control: 'password', name: 'token', label: 'Token' },
|
|
794
|
+
* { control: 'confirm', name: 'terms', label: 'Accept the terms', rule: { required: true } },
|
|
795
|
+
* {
|
|
796
|
+
* control: 'select',
|
|
797
|
+
* name: 'role',
|
|
798
|
+
* label: 'Role',
|
|
799
|
+
* choices: [
|
|
800
|
+
* { value: 'admin', label: 'Admin' },
|
|
801
|
+
* { value: 'viewer', label: 'Viewer', help: 'read-only' },
|
|
802
|
+
* ],
|
|
803
|
+
* },
|
|
804
|
+
* ],
|
|
805
|
+
* })
|
|
806
|
+
*
|
|
807
|
+
* try {
|
|
808
|
+
* const values = await terminal.ask(form)
|
|
809
|
+
* deploy(values)
|
|
810
|
+
* } catch (error) {
|
|
811
|
+
* // Ctrl-c: the walk ended, and the form is still `editing` for whoever owns it.
|
|
812
|
+
* if (isTerminalError(error) && error.code === 'CANCEL') form.destroy()
|
|
813
|
+
* }
|
|
789
814
|
* ```
|
|
790
815
|
*/
|
|
791
816
|
function createTerminal(options) {
|