@scelar/nodepod 1.0.4 → 1.0.6

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.
Files changed (42) hide show
  1. package/dist/{child_process-53fMkug_.js → child_process-4ZrgCVFu.js} +8234 -8233
  2. package/dist/{child_process-53fMkug_.js.map → child_process-4ZrgCVFu.js.map} +1 -1
  3. package/dist/{child_process-lxSKECHq.cjs → child_process-Cao4lyrb.cjs} +7435 -7434
  4. package/dist/{child_process-lxSKECHq.cjs.map → child_process-Cao4lyrb.cjs.map} +1 -1
  5. package/dist/{index-C-TQIrdG.cjs → index-DuYo2yDs.cjs} +38842 -38005
  6. package/dist/index-DuYo2yDs.cjs.map +1 -0
  7. package/dist/{index-B8lyh_ti.js → index-HkVqijtm.js} +36923 -36065
  8. package/dist/index-HkVqijtm.js.map +1 -0
  9. package/dist/index.cjs +67 -65
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.mjs +61 -59
  13. package/dist/memory-handler.d.ts +57 -0
  14. package/dist/memory-volume.d.ts +12 -2
  15. package/dist/packages/installer.d.ts +3 -0
  16. package/dist/persistence/idb-cache.d.ts +7 -0
  17. package/dist/polyfills/readline.d.ts +108 -87
  18. package/dist/script-engine.d.ts +3 -0
  19. package/dist/sdk/nodepod-process.d.ts +2 -1
  20. package/dist/sdk/nodepod.d.ts +20 -1
  21. package/dist/sdk/types.d.ts +5 -0
  22. package/package.json +1 -1
  23. package/src/index.ts +2 -0
  24. package/src/memory-handler.ts +168 -0
  25. package/src/memory-volume.ts +72 -8
  26. package/src/packages/installer.ts +49 -1
  27. package/src/packages/version-resolver.ts +421 -421
  28. package/src/persistence/idb-cache.ts +107 -0
  29. package/src/polyfills/child_process.ts +3 -0
  30. package/src/polyfills/events.ts +22 -4
  31. package/src/polyfills/readline.ts +593 -71
  32. package/src/polyfills/stream.ts +46 -0
  33. package/src/polyfills/wasi.ts +1306 -1306
  34. package/src/polyfills/zlib.ts +881 -881
  35. package/src/script-engine.ts +3722 -3694
  36. package/src/sdk/nodepod-process.ts +94 -86
  37. package/src/sdk/nodepod.ts +52 -6
  38. package/src/sdk/types.ts +82 -77
  39. package/src/threading/process-manager.ts +11 -0
  40. package/src/threading/worker-protocol.ts +358 -358
  41. package/dist/index-B8lyh_ti.js.map +0 -1
  42. package/dist/index-C-TQIrdG.cjs.map +0 -1
@@ -1,10 +1,10 @@
1
- // readline polyfill -- createInterface, emitKeypressEvents, terminal helpers.
2
- // tracks line/cursor on every keystroke because @clack reads rl.line directly.
1
+ // readline polyfill createInterface, emitKeypressEvents, terminal helpers
2
+ // we track line/cursor on every keystroke because @clack reads rl.line directly
3
3
 
4
4
  import { EventEmitter } from "./events";
5
5
  import { ref as _elRef, unref as _elUnref } from "../helpers/event-loop";
6
6
 
7
- // child_process wait loop checks this to avoid exiting while waiting for user input
7
+ // the wait loop checks this so it doesn't bail while the user is typing
8
8
  let _activeInterfaceCount = 0;
9
9
 
10
10
  export function getActiveInterfaceCount(): number {
@@ -26,17 +26,20 @@ export interface InterfaceConfig {
26
26
  terminal?: boolean;
27
27
  prompt?: string;
28
28
  historySize?: number;
29
+ history?: string[];
30
+ removeHistoryDuplicates?: boolean;
29
31
  completer?: (line: string) => [string[], string] | void;
30
32
  crlfDelay?: number;
31
33
  escapeCodeTimeout?: number;
32
34
  tabSize?: number;
35
+ signal?: AbortSignal;
33
36
  }
34
37
 
35
- // emitKeypressEvents: parse data events into keypress events
38
+ // turns raw data events into proper keypress events
36
39
 
37
40
  const KEYPRESS_DECODER = Symbol("keypressDecoder");
38
41
 
39
- // mirrors Node.js internal emitKeys generator
42
+ // parses individual chars (including escape sequences) and fires keypress
40
43
  function parseAndEmitKeypress(
41
44
  stream: any,
42
45
  char: string,
@@ -47,10 +50,11 @@ function parseAndEmitKeypress(
47
50
  const seq = escapeBuf.join("");
48
51
 
49
52
  if (seq.length >= 3 && seq[1] === "[") {
50
- // CSI sequence
53
+ // CSI escape sequence (arrows, home, end, etc.)
51
54
  const lastChar = seq[seq.length - 1];
52
55
  if (/[A-Za-z~]/.test(lastChar)) {
53
- let name = "";
56
+ let name = "";
57
+ let ctrl = false;
54
58
  if (lastChar === "A") name = "up";
55
59
  else if (lastChar === "B") name = "down";
56
60
  else if (lastChar === "C") name = "right";
@@ -61,12 +65,14 @@ function parseAndEmitKeypress(
61
65
  else if (seq === "\x1b[2~") name = "insert";
62
66
  else if (seq === "\x1b[5~") name = "pageup";
63
67
  else if (seq === "\x1b[6~") name = "pagedown";
68
+ else if (seq === "\x1b[1;5C") { name = "right"; ctrl = true; } // Ctrl+Right
69
+ else if (seq === "\x1b[1;5D") { name = "left"; ctrl = true; } // Ctrl+Left
64
70
  else name = lastChar;
65
71
 
66
72
  stream.emit("keypress", seq, {
67
73
  sequence: seq,
68
74
  name,
69
- ctrl: false,
75
+ ctrl,
70
76
  meta: false,
71
77
  shift: false,
72
78
  });
@@ -107,9 +113,15 @@ function parseAndEmitKeypress(
107
113
  }
108
114
 
109
115
  if (seq.length >= 2 && seq[1] !== "[" && seq[1] !== "O") {
116
+ // Alt+letter combo
117
+ const metaChar = seq[1];
118
+ let name = metaChar;
119
+ if (metaChar.charCodeAt(0) >= 32) {
120
+ name = metaChar.toLowerCase();
121
+ }
110
122
  stream.emit("keypress", seq, {
111
123
  sequence: seq,
112
- name: "escape",
124
+ name,
113
125
  ctrl: false,
114
126
  meta: true,
115
127
  shift: false,
@@ -131,7 +143,6 @@ function parseAndEmitKeypress(
131
143
  else if (char === "\t") name = "tab";
132
144
  else if (char === " ") name = "space";
133
145
  else if (char.charCodeAt(0) < 32) {
134
- // Ctrl+letter
135
146
  ctrl = true;
136
147
  name = String.fromCharCode(char.charCodeAt(0) + 96);
137
148
  }
@@ -166,7 +177,7 @@ export function emitKeypressEvents(stream: unknown, _iface?: Interface): void {
166
177
  }
167
178
  escapeBuf = parseAndEmitKeypress(stream as any, char, escapeBuf);
168
179
 
169
- // flush pending escape after timeout
180
+ // if we're mid-escape, flush it after a short timeout
170
181
  if (escapeBuf.length > 0) {
171
182
  escapeTimeout = setTimeout(() => {
172
183
  if (escapeBuf.length > 0) {
@@ -186,23 +197,48 @@ export function emitKeypressEvents(stream: unknown, _iface?: Interface): void {
186
197
  });
187
198
  }
188
199
 
200
+ // word boundary helpers for Ctrl+Left/Right, Ctrl+W, etc.
201
+
202
+ function wordLeft(line: string, cursor: number): number {
203
+ let i = cursor - 1;
204
+ while (i >= 0 && /\s/.test(line[i])) i--;
205
+ while (i >= 0 && !/\s/.test(line[i])) i--;
206
+ return i + 1;
207
+ }
208
+
209
+ function wordRight(line: string, cursor: number): number {
210
+ let i = cursor;
211
+ while (i < line.length && !/\s/.test(line[i])) i++;
212
+ while (i < line.length && /\s/.test(line[i])) i++;
213
+ return i;
214
+ }
215
+
189
216
 
190
217
  export interface Interface extends EventEmitter {
191
218
  _promptStr: string;
192
- _input: unknown;
193
- _output: unknown;
194
- _closed: boolean;
219
+ input: unknown;
220
+ output: unknown;
221
+ closed: boolean;
195
222
  _lineBuffer: string;
196
223
  _pendingQuestions: Array<{
197
224
  query: string;
198
225
  handler: (answer: string) => void;
226
+ signal?: AbortSignal;
227
+ abortListener?: () => void;
199
228
  }>;
200
229
  terminal: boolean;
201
230
  line: string;
202
231
  cursor: number;
232
+ history: string[];
233
+ _historyIndex: number;
234
+ _historySize: number;
235
+ _removeHistoryDuplicates: boolean;
236
+ _savedLine: string;
237
+ _killRing: string[];
203
238
  _refreshLine(): void;
204
239
  _onKeypress(char: string | undefined, key: any): void;
205
240
  _onData(text: string): void;
241
+ _addToHistory(line: string): void;
206
242
  prompt(preserveCursor?: boolean): void;
207
243
  setPrompt(text: string): void;
208
244
  getPrompt(): string;
@@ -211,6 +247,7 @@ export interface Interface extends EventEmitter {
211
247
  resume(): this;
212
248
  close(): void;
213
249
  write(data: string | null, _key?: { ctrl?: boolean; name?: string; meta?: boolean; shift?: boolean; sequence?: string }): void;
250
+ clearLine(dir?: number): void;
214
251
  getCursorPos(): { rows: number; cols: number };
215
252
  [Symbol.asyncIterator](): AsyncGenerator<string, void, undefined>;
216
253
  }
@@ -225,38 +262,60 @@ export const Interface = function Interface(this: any, cfg?: InterfaceConfig) {
225
262
  if (!this) return;
226
263
  EventEmitter.call(this);
227
264
  this._promptStr = cfg?.prompt ?? "> ";
228
- this._input = cfg?.input;
229
- this._output = cfg?.output;
230
- this._closed = false;
265
+ this.input = cfg?.input;
266
+ this.output = cfg?.output;
267
+ this.closed = false;
231
268
  this._lineBuffer = "";
232
269
  this._pendingQuestions = [];
233
270
  this.terminal = cfg?.terminal ?? false;
234
271
  this.line = "";
235
272
  this.cursor = 0;
236
273
 
237
- if (this._input && typeof (this._input as any).on === "function") {
274
+ this._historySize = cfg?.historySize ?? 30;
275
+ this._removeHistoryDuplicates = cfg?.removeHistoryDuplicates ?? false;
276
+ this.history = Array.isArray(cfg?.history) ? cfg!.history.slice(0, this._historySize) : [];
277
+ this._historyIndex = -1;
278
+ this._savedLine = "";
279
+ this._killRing = [];
280
+
281
+ if (cfg?.signal) {
282
+ const signal = cfg.signal;
283
+ if (signal.aborted) {
284
+ queueMicrotask(() => this.close());
285
+ } else {
286
+ const onAbort = () => this.close();
287
+ signal.addEventListener("abort", onAbort, { once: true });
288
+ }
289
+ }
290
+
291
+ if (this.input && typeof (this.input as any).on === "function") {
238
292
  _activeInterfaceCount++;
239
293
  _elRef();
240
- const inputStream = this._input as EventEmitter;
294
+ const inputStream = this.input as EventEmitter;
241
295
  const self = this;
242
296
 
243
297
  if (this.terminal) {
244
- emitKeypressEvents(this._input, this);
298
+ // raw mode so we get individual keystrokes instead of line-buffered input
299
+ if (typeof (this.input as any).setRawMode === "function") {
300
+ (this.input as any).setRawMode(true);
301
+ }
302
+
303
+ emitKeypressEvents(this.input, this);
245
304
 
246
305
  inputStream.on("keypress", (char: string | undefined, key: any) => {
247
- if (self._closed) return;
306
+ if (self.closed) return;
248
307
  self._onKeypress(char, key);
249
308
  });
250
309
  } else {
251
310
  inputStream.on("data", (data: unknown) => {
252
- if (self._closed) return;
311
+ if (self.closed) return;
253
312
  const text = typeof data === "string" ? data : String(data);
254
313
  self._onData(text);
255
314
  });
256
315
  }
257
316
 
258
317
  inputStream.on("end", () => {
259
- if (!self._closed) self.close();
318
+ if (!self.closed) self.close();
260
319
  });
261
320
  }
262
321
  } as unknown as InterfaceConstructor;
@@ -264,26 +323,62 @@ export const Interface = function Interface(this: any, cfg?: InterfaceConfig) {
264
323
  Object.setPrototypeOf(Interface.prototype, EventEmitter.prototype);
265
324
 
266
325
  Interface.prototype._refreshLine = function _refreshLine(this: any): void {
267
- if (this._output && typeof (this._output as any).write === "function") {
268
- (this._output as any).write(this.line);
326
+ if (this.output && typeof (this.output as any).write === "function") {
327
+ (this.output as any).write(this.line);
269
328
  }
270
329
  };
271
330
 
331
+ Interface.prototype._addToHistory = function _addToHistory(this: any, line: string): void {
332
+ if (this._historySize === 0 || !line) return;
333
+
334
+ if (this._removeHistoryDuplicates) {
335
+ const idx = this.history.indexOf(line);
336
+ if (idx !== -1) this.history.splice(idx, 1);
337
+ }
338
+
339
+ this.history.unshift(line);
340
+ if (this.history.length > this._historySize) {
341
+ this.history.length = this._historySize;
342
+ }
343
+
344
+ this._historyIndex = -1;
345
+ this.emit("history", this.history);
346
+ };
347
+
272
348
  Interface.prototype._onKeypress = function _onKeypress(this: any, char: string | undefined, key: any): void {
273
349
  if (!key) return;
274
350
 
351
+ // Ctrl+C
275
352
  if (key.ctrl && key.name === "c") {
276
- this.close();
353
+ if (this.listenerCount("SIGINT") > 0) {
354
+ this.emit("SIGINT");
355
+ } else {
356
+ this.close();
357
+ }
358
+ return;
359
+ }
360
+
361
+ // Ctrl+Z
362
+ if (key.ctrl && key.name === "z") {
363
+ if (this.listenerCount("SIGTSTP") > 0) {
364
+ this.emit("SIGTSTP");
365
+ }
277
366
  return;
278
367
  }
279
368
 
369
+ // Enter — submit the line
280
370
  if (key.name === "return") {
281
371
  const line = this._lineBuffer;
282
372
  this._lineBuffer = "";
283
373
  this.line = line;
284
374
 
375
+ this._addToHistory(line);
376
+
285
377
  if (this._pendingQuestions.length > 0) {
286
378
  const q = this._pendingQuestions.shift()!;
379
+ if (q.signal && q.abortListener) {
380
+ q.signal.removeEventListener("abort", q.abortListener);
381
+ }
287
382
  q.handler(line);
288
383
  }
289
384
 
@@ -293,8 +388,10 @@ Interface.prototype._onKeypress = function _onKeypress(this: any, char: string |
293
388
  return;
294
389
  }
295
390
 
391
+ // Backspace
296
392
  if (key.name === "backspace") {
297
393
  if (this.cursor > 0) {
394
+ const deleted = this._lineBuffer[this.cursor - 1];
298
395
  this._lineBuffer =
299
396
  this._lineBuffer.slice(0, this.cursor - 1) +
300
397
  this._lineBuffer.slice(this.cursor);
@@ -305,6 +402,7 @@ Interface.prototype._onKeypress = function _onKeypress(this: any, char: string |
305
402
  return;
306
403
  }
307
404
 
405
+ // Delete
308
406
  if (key.name === "delete") {
309
407
  if (this.cursor < this._lineBuffer.length) {
310
408
  this._lineBuffer =
@@ -316,28 +414,111 @@ Interface.prototype._onKeypress = function _onKeypress(this: any, char: string |
316
414
  return;
317
415
  }
318
416
 
319
- if (key.name === "left") {
417
+ // Ctrl+D delete char or close if empty
418
+ if (key.ctrl && key.name === "d") {
419
+ if (this._lineBuffer.length === 0) {
420
+ this.close();
421
+ } else if (this.cursor < this._lineBuffer.length) {
422
+ this._lineBuffer =
423
+ this._lineBuffer.slice(0, this.cursor) +
424
+ this._lineBuffer.slice(this.cursor + 1);
425
+ this.line = this._lineBuffer;
426
+ this._refreshLine();
427
+ }
428
+ return;
429
+ }
430
+
431
+ // arrow keys
432
+ if (key.name === "left" && !key.ctrl && !key.meta) {
320
433
  this.cursor = Math.max(0, this.cursor - 1);
321
434
  this._refreshLine();
322
435
  return;
323
436
  }
324
- if (key.name === "right") {
437
+ if (key.name === "right" && !key.ctrl && !key.meta) {
325
438
  this.cursor = Math.min(this._lineBuffer.length, this.cursor + 1);
326
439
  this._refreshLine();
327
440
  return;
328
441
  }
329
- if (key.name === "home") {
442
+
443
+ // Ctrl+Left / Alt+B — jump word left
444
+ if ((key.ctrl && key.name === "left") || (key.meta && key.name === "b")) {
445
+ this.cursor = wordLeft(this._lineBuffer, this.cursor);
446
+ this._refreshLine();
447
+ return;
448
+ }
449
+
450
+ // Ctrl+Right / Alt+F — jump word right
451
+ if ((key.ctrl && key.name === "right") || (key.meta && key.name === "f")) {
452
+ this.cursor = wordRight(this._lineBuffer, this.cursor);
453
+ this._refreshLine();
454
+ return;
455
+ }
456
+
457
+ // Home / Ctrl+A
458
+ if (key.name === "home" || (key.ctrl && key.name === "a")) {
330
459
  this.cursor = 0;
331
460
  this._refreshLine();
332
461
  return;
333
462
  }
334
- if (key.name === "end") {
463
+
464
+ // End / Ctrl+E
465
+ if (key.name === "end" || (key.ctrl && key.name === "e")) {
335
466
  this.cursor = this._lineBuffer.length;
336
467
  this._refreshLine();
337
468
  return;
338
469
  }
339
470
 
471
+ // Ctrl+B — back one char
472
+ if (key.ctrl && key.name === "b") {
473
+ this.cursor = Math.max(0, this.cursor - 1);
474
+ this._refreshLine();
475
+ return;
476
+ }
477
+
478
+ // Ctrl+F — forward one char
479
+ if (key.ctrl && key.name === "f") {
480
+ this.cursor = Math.min(this._lineBuffer.length, this.cursor + 1);
481
+ this._refreshLine();
482
+ return;
483
+ }
484
+
485
+ // Up / Ctrl+P — previous history entry
486
+ if (key.name === "up" || (key.ctrl && key.name === "p")) {
487
+ if (this.history.length > 0 && this._historyIndex < this.history.length - 1) {
488
+ if (this._historyIndex === -1) {
489
+ this._savedLine = this._lineBuffer;
490
+ }
491
+ this._historyIndex++;
492
+ this._lineBuffer = this.history[this._historyIndex];
493
+ this.cursor = this._lineBuffer.length;
494
+ this.line = this._lineBuffer;
495
+ this._refreshLine();
496
+ }
497
+ return;
498
+ }
499
+
500
+ // Down / Ctrl+N — next history entry
501
+ if (key.name === "down" || (key.ctrl && key.name === "n")) {
502
+ if (this._historyIndex > 0) {
503
+ this._historyIndex--;
504
+ this._lineBuffer = this.history[this._historyIndex];
505
+ this.cursor = this._lineBuffer.length;
506
+ this.line = this._lineBuffer;
507
+ this._refreshLine();
508
+ } else if (this._historyIndex === 0) {
509
+ this._historyIndex = -1;
510
+ this._lineBuffer = this._savedLine;
511
+ this.cursor = this._lineBuffer.length;
512
+ this.line = this._lineBuffer;
513
+ this._refreshLine();
514
+ }
515
+ return;
516
+ }
517
+
518
+ // Ctrl+U — kill everything before cursor
340
519
  if (key.ctrl && key.name === "u") {
520
+ const killed = this._lineBuffer.slice(0, this.cursor);
521
+ if (killed) this._killRing.push(killed);
341
522
  this._lineBuffer = this._lineBuffer.slice(this.cursor);
342
523
  this.cursor = 0;
343
524
  this.line = this._lineBuffer;
@@ -345,13 +526,17 @@ Interface.prototype._onKeypress = function _onKeypress(this: any, char: string |
345
526
  return;
346
527
  }
347
528
 
529
+ // Ctrl+K — kill everything after cursor
348
530
  if (key.ctrl && key.name === "k") {
531
+ const killed = this._lineBuffer.slice(this.cursor);
532
+ if (killed) this._killRing.push(killed);
349
533
  this._lineBuffer = this._lineBuffer.slice(0, this.cursor);
350
534
  this.line = this._lineBuffer;
351
535
  this._refreshLine();
352
536
  return;
353
537
  }
354
538
 
539
+ // Ctrl+H — same as backspace
355
540
  if (key.ctrl && key.name === "h") {
356
541
  if (this.cursor > 0) {
357
542
  this._lineBuffer =
@@ -364,6 +549,58 @@ Interface.prototype._onKeypress = function _onKeypress(this: any, char: string |
364
549
  return;
365
550
  }
366
551
 
552
+ // Ctrl+W — delete word backward
553
+ if (key.ctrl && key.name === "w") {
554
+ const newPos = wordLeft(this._lineBuffer, this.cursor);
555
+ const killed = this._lineBuffer.slice(newPos, this.cursor);
556
+ if (killed) this._killRing.push(killed);
557
+ this._lineBuffer =
558
+ this._lineBuffer.slice(0, newPos) +
559
+ this._lineBuffer.slice(this.cursor);
560
+ this.cursor = newPos;
561
+ this.line = this._lineBuffer;
562
+ this._refreshLine();
563
+ return;
564
+ }
565
+
566
+ // Alt+D — delete word forward
567
+ if (key.meta && key.name === "d") {
568
+ const newPos = wordRight(this._lineBuffer, this.cursor);
569
+ const killed = this._lineBuffer.slice(this.cursor, newPos);
570
+ if (killed) this._killRing.push(killed);
571
+ this._lineBuffer =
572
+ this._lineBuffer.slice(0, this.cursor) +
573
+ this._lineBuffer.slice(newPos);
574
+ this.line = this._lineBuffer;
575
+ this._refreshLine();
576
+ return;
577
+ }
578
+
579
+ // Ctrl+Y — paste last killed text
580
+ if (key.ctrl && key.name === "y") {
581
+ if (this._killRing.length > 0) {
582
+ const text = this._killRing[this._killRing.length - 1];
583
+ this._lineBuffer =
584
+ this._lineBuffer.slice(0, this.cursor) +
585
+ text +
586
+ this._lineBuffer.slice(this.cursor);
587
+ this.cursor += text.length;
588
+ this.line = this._lineBuffer;
589
+ this._refreshLine();
590
+ }
591
+ return;
592
+ }
593
+
594
+ // Ctrl+L — clear screen
595
+ if (key.ctrl && key.name === "l") {
596
+ if (this.output && typeof (this.output as any).write === "function") {
597
+ (this.output as any).write("\x1b[2J\x1b[H");
598
+ }
599
+ this._refreshLine();
600
+ return;
601
+ }
602
+
603
+ // regular printable character
367
604
  if (
368
605
  char &&
369
606
  !key.ctrl &&
@@ -390,8 +627,13 @@ Interface.prototype._onData = function _onData(this: any, text: string): void {
390
627
  this._lineBuffer = "";
391
628
  this.line = line;
392
629
 
630
+ this._addToHistory(line);
631
+
393
632
  if (this._pendingQuestions.length > 0) {
394
633
  const q = this._pendingQuestions.shift()!;
634
+ if (q.signal && q.abortListener) {
635
+ q.signal.removeEventListener("abort", q.abortListener);
636
+ }
395
637
  q.handler(line);
396
638
  }
397
639
 
@@ -420,8 +662,8 @@ Interface.prototype._onData = function _onData(this: any, text: string): void {
420
662
  };
421
663
 
422
664
  Interface.prototype.prompt = function prompt(this: any, preserveCursor?: boolean): void {
423
- if (this._output && typeof (this._output as any).write === "function") {
424
- (this._output as any).write(this._promptStr);
665
+ if (this.output && typeof (this.output as any).write === "function") {
666
+ (this.output as any).write(this._promptStr);
425
667
  }
426
668
  if (!preserveCursor) {
427
669
  this.cursor = 0;
@@ -444,53 +686,93 @@ Interface.prototype.question = function question(
444
686
  optsOrHandler?: unknown,
445
687
  handler?: (answer: string) => void,
446
688
  ): void {
447
- const actualHandler =
448
- typeof optsOrHandler === "function"
449
- ? (optsOrHandler as (answer: string) => void)
450
- : handler;
689
+ let actualHandler: ((answer: string) => void) | undefined;
690
+ let signal: AbortSignal | undefined;
691
+
692
+ if (typeof optsOrHandler === "function") {
693
+ actualHandler = optsOrHandler as (answer: string) => void;
694
+ } else if (optsOrHandler && typeof optsOrHandler === "object") {
695
+ signal = (optsOrHandler as any).signal;
696
+ actualHandler = handler;
697
+ } else {
698
+ actualHandler = handler;
699
+ }
700
+
451
701
  if (!actualHandler) return;
452
702
 
453
- if (this._output && typeof (this._output as any).write === "function") {
454
- (this._output as any).write(query);
703
+ if (signal?.aborted) {
704
+ const err = new DOMException("The operation was aborted", "AbortError");
705
+ throw err;
455
706
  }
456
707
 
457
- if (this._input && typeof (this._input as any).on === "function") {
458
- this._pendingQuestions.push({ query, handler: actualHandler });
708
+ if (this.output && typeof (this.output as any).write === "function") {
709
+ (this.output as any).write(query);
710
+ }
711
+
712
+ if (this.input && typeof (this.input as any).on === "function") {
713
+ const entry: any = { query, handler: actualHandler, signal };
714
+
715
+ if (signal) {
716
+ const onAbort = () => {
717
+ const idx = this._pendingQuestions.indexOf(entry);
718
+ if (idx !== -1) this._pendingQuestions.splice(idx, 1);
719
+ actualHandler!("");
720
+ };
721
+ entry.abortListener = onAbort;
722
+ signal.addEventListener("abort", onAbort, { once: true });
723
+ }
724
+
725
+ this._pendingQuestions.push(entry);
459
726
  } else {
460
- setTimeout(() => actualHandler(""), 0);
727
+ setTimeout(() => actualHandler!(""), 0);
461
728
  }
462
729
  };
463
730
 
464
731
  Interface.prototype.pause = function pause(this: any): any {
465
- if (this._input && typeof (this._input as any).pause === "function") {
466
- (this._input as any).pause();
732
+ if (this.input && typeof (this.input as any).pause === "function") {
733
+ (this.input as any).pause();
467
734
  }
468
735
  this.emit("pause");
469
736
  return this;
470
737
  };
471
738
 
472
739
  Interface.prototype.resume = function resume(this: any): any {
473
- if (this._input && typeof (this._input as any).resume === "function") {
474
- (this._input as any).resume();
740
+ if (this.input && typeof (this.input as any).resume === "function") {
741
+ (this.input as any).resume();
475
742
  }
476
743
  this.emit("resume");
477
744
  return this;
478
745
  };
479
746
 
480
747
  Interface.prototype.close = function close(this: any): void {
481
- if (this._closed) return;
482
- this._closed = true;
483
- if (this._input && typeof (this._input as any).on === "function") {
748
+ if (this.closed) return;
749
+ this.closed = true;
750
+ // put raw mode back how it was
751
+ if (this.terminal && this.input && typeof (this.input as any).setRawMode === "function") {
752
+ (this.input as any).setRawMode(false);
753
+ }
754
+ if (this.input && typeof (this.input as any).on === "function") {
484
755
  _activeInterfaceCount = Math.max(0, _activeInterfaceCount - 1);
485
756
  _elUnref();
486
757
  }
758
+ // answer any pending questions with empty string
487
759
  for (const q of this._pendingQuestions) {
760
+ if (q.signal && q.abortListener) {
761
+ q.signal.removeEventListener("abort", q.abortListener);
762
+ }
488
763
  q.handler("");
489
764
  }
490
765
  this._pendingQuestions.length = 0;
491
766
  this.emit("close");
492
767
  };
493
768
 
769
+ // `using rl = ...` support
770
+ if (typeof Symbol.dispose !== "undefined") {
771
+ (Interface.prototype as any)[Symbol.dispose] = function (this: any): void {
772
+ this.close();
773
+ };
774
+ }
775
+
494
776
  Interface.prototype.write = function write(
495
777
  this: any,
496
778
  data: string | null,
@@ -502,20 +784,23 @@ Interface.prototype.write = function write(
502
784
  sequence?: string;
503
785
  },
504
786
  ): void {
505
- if (this._closed) return;
787
+ if (this.closed) return;
506
788
 
507
789
  if (data === null || data === undefined) {
508
790
  if (_key) {
509
- // emit synthetic keypress on input so external listeners (e.g. @clack) see it
791
+ // fire a keypress on input so things like @clack pick it up
510
792
  const emitOnInput =
511
- this._input && typeof (this._input as any).emit === "function";
793
+ this.input && typeof (this.input as any).emit === "function";
512
794
 
513
795
  if (_key.ctrl && _key.name === "c") {
514
- this.close();
796
+ if (this.listenerCount("SIGINT") > 0) {
797
+ this.emit("SIGINT");
798
+ } else {
799
+ this.close();
800
+ }
515
801
  return;
516
802
  }
517
803
  if (_key.ctrl && _key.name === "h") {
518
- // Backspace
519
804
  if (this.cursor > 0) {
520
805
  this._lineBuffer =
521
806
  this._lineBuffer.slice(0, this.cursor - 1) +
@@ -524,7 +809,7 @@ Interface.prototype.write = function write(
524
809
  this.line = this._lineBuffer;
525
810
  }
526
811
  if (emitOnInput) {
527
- (this._input as any).emit("keypress", "\x7f", {
812
+ (this.input as any).emit("keypress", "\x7f", {
528
813
  sequence: "\x7f",
529
814
  name: "backspace",
530
815
  ctrl: false,
@@ -535,24 +820,92 @@ Interface.prototype.write = function write(
535
820
  return;
536
821
  }
537
822
  if (_key.ctrl && _key.name === "u") {
823
+ const killed = this._lineBuffer.slice(0, this.cursor);
824
+ if (killed) this._killRing.push(killed);
538
825
  this._lineBuffer = this._lineBuffer.slice(this.cursor);
539
826
  this.cursor = 0;
540
827
  this.line = this._lineBuffer;
541
828
  return;
542
829
  }
543
830
  if (_key.ctrl && _key.name === "k") {
831
+ const killed = this._lineBuffer.slice(this.cursor);
832
+ if (killed) this._killRing.push(killed);
544
833
  this._lineBuffer = this._lineBuffer.slice(0, this.cursor);
545
834
  this.line = this._lineBuffer;
546
835
  return;
547
836
  }
548
- if (_key.name === "left") {
837
+ if (_key.ctrl && _key.name === "w") {
838
+ const newPos = wordLeft(this._lineBuffer, this.cursor);
839
+ const killed = this._lineBuffer.slice(newPos, this.cursor);
840
+ if (killed) this._killRing.push(killed);
841
+ this._lineBuffer =
842
+ this._lineBuffer.slice(0, newPos) +
843
+ this._lineBuffer.slice(this.cursor);
844
+ this.cursor = newPos;
845
+ this.line = this._lineBuffer;
846
+ return;
847
+ }
848
+ if (_key.ctrl && _key.name === "y") {
849
+ if (this._killRing.length > 0) {
850
+ const text = this._killRing[this._killRing.length - 1];
851
+ this._lineBuffer =
852
+ this._lineBuffer.slice(0, this.cursor) +
853
+ text +
854
+ this._lineBuffer.slice(this.cursor);
855
+ this.cursor += text.length;
856
+ this.line = this._lineBuffer;
857
+ }
858
+ return;
859
+ }
860
+ if (_key.ctrl && _key.name === "a") {
861
+ this.cursor = 0;
862
+ return;
863
+ }
864
+ if (_key.ctrl && _key.name === "e") {
865
+ this.cursor = this._lineBuffer.length;
866
+ return;
867
+ }
868
+ if (_key.ctrl && _key.name === "b") {
549
869
  this.cursor = Math.max(0, this.cursor - 1);
550
870
  return;
551
871
  }
552
- if (_key.name === "right") {
872
+ if (_key.ctrl && _key.name === "f") {
553
873
  this.cursor = Math.min(this._lineBuffer.length, this.cursor + 1);
554
874
  return;
555
875
  }
876
+ if (_key.ctrl && _key.name === "d") {
877
+ if (this._lineBuffer.length === 0) {
878
+ this.close();
879
+ } else if (this.cursor < this._lineBuffer.length) {
880
+ this._lineBuffer =
881
+ this._lineBuffer.slice(0, this.cursor) +
882
+ this._lineBuffer.slice(this.cursor + 1);
883
+ this.line = this._lineBuffer;
884
+ }
885
+ return;
886
+ }
887
+ if (_key.ctrl && _key.name === "l") {
888
+ if (this.output && typeof (this.output as any).write === "function") {
889
+ (this.output as any).write("\x1b[2J\x1b[H");
890
+ }
891
+ return;
892
+ }
893
+ if (_key.name === "left") {
894
+ if (_key.ctrl || _key.meta) {
895
+ this.cursor = wordLeft(this._lineBuffer, this.cursor);
896
+ } else {
897
+ this.cursor = Math.max(0, this.cursor - 1);
898
+ }
899
+ return;
900
+ }
901
+ if (_key.name === "right") {
902
+ if (_key.ctrl || _key.meta) {
903
+ this.cursor = wordRight(this._lineBuffer, this.cursor);
904
+ } else {
905
+ this.cursor = Math.min(this._lineBuffer.length, this.cursor + 1);
906
+ }
907
+ return;
908
+ }
556
909
  if (_key.name === "home") {
557
910
  this.cursor = 0;
558
911
  return;
@@ -561,27 +914,90 @@ Interface.prototype.write = function write(
561
914
  this.cursor = this._lineBuffer.length;
562
915
  return;
563
916
  }
917
+ if (_key.name === "up") {
918
+ if (this.history.length > 0 && this._historyIndex < this.history.length - 1) {
919
+ if (this._historyIndex === -1) this._savedLine = this._lineBuffer;
920
+ this._historyIndex++;
921
+ this._lineBuffer = this.history[this._historyIndex];
922
+ this.cursor = this._lineBuffer.length;
923
+ this.line = this._lineBuffer;
924
+ }
925
+ return;
926
+ }
927
+ if (_key.name === "down") {
928
+ if (this._historyIndex > 0) {
929
+ this._historyIndex--;
930
+ this._lineBuffer = this.history[this._historyIndex];
931
+ this.cursor = this._lineBuffer.length;
932
+ this.line = this._lineBuffer;
933
+ } else if (this._historyIndex === 0) {
934
+ this._historyIndex = -1;
935
+ this._lineBuffer = this._savedLine;
936
+ this.cursor = this._lineBuffer.length;
937
+ this.line = this._lineBuffer;
938
+ }
939
+ return;
940
+ }
941
+ if (_key.meta && _key.name === "b") {
942
+ this.cursor = wordLeft(this._lineBuffer, this.cursor);
943
+ return;
944
+ }
945
+ if (_key.meta && _key.name === "f") {
946
+ this.cursor = wordRight(this._lineBuffer, this.cursor);
947
+ return;
948
+ }
949
+ if (_key.meta && _key.name === "d") {
950
+ const newPos = wordRight(this._lineBuffer, this.cursor);
951
+ const killed = this._lineBuffer.slice(this.cursor, newPos);
952
+ if (killed) this._killRing.push(killed);
953
+ this._lineBuffer =
954
+ this._lineBuffer.slice(0, this.cursor) +
955
+ this._lineBuffer.slice(newPos);
956
+ this.line = this._lineBuffer;
957
+ return;
958
+ }
564
959
  }
565
960
  return;
566
961
  }
567
962
 
568
963
  if (_key?.ctrl && _key?.name === "c") {
569
- this.close();
964
+ if (this.listenerCount("SIGINT") > 0) {
965
+ this.emit("SIGINT");
966
+ } else {
967
+ this.close();
968
+ }
570
969
  return;
571
970
  }
572
971
 
573
972
  this._onData(data);
574
973
  };
575
974
 
975
+ // @inquirer calls rl.clearLine(0) directly so we need this on the interface
976
+ Interface.prototype.clearLine = function clearLine(this: any, dir?: number): void {
977
+ this._lineBuffer = "";
978
+ this.line = "";
979
+ this.cursor = 0;
980
+ if (this.output && typeof (this.output as any).clearLine === "function") {
981
+ (this.output as any).clearLine(dir ?? 0);
982
+ }
983
+ };
984
+
576
985
  Interface.prototype.getCursorPos = function getCursorPos(this: any): { rows: number; cols: number } {
577
- return { rows: 0, cols: this.cursor };
986
+ // cursor position relative to prompt, wrapping at terminal width
987
+ const promptLen = typeof this._promptStr === "string" ? this._promptStr.length : 0;
988
+ const columns = (this.output && (this.output as any).columns) || 80;
989
+ const offset = promptLen + this.cursor;
990
+ return {
991
+ rows: Math.floor(offset / columns),
992
+ cols: offset % columns,
993
+ };
578
994
  };
579
995
 
580
996
  Interface.prototype[Symbol.asyncIterator] = async function*(this: any): AsyncGenerator<string, void, undefined> {
581
997
  const self = this;
582
- while (!self._closed) {
998
+ while (!self.closed) {
583
999
  const line = await new Promise<string | null>((resolve) => {
584
- if (self._closed) {
1000
+ if (self.closed) {
585
1001
  resolve(null);
586
1002
  return;
587
1003
  }
@@ -662,21 +1078,127 @@ export function moveCursor(
662
1078
  }
663
1079
 
664
1080
 
1081
+ // promises API
1082
+
1083
+ class ReadlineWriter {
1084
+ private _stream: any;
1085
+ private _buffer: string[];
1086
+ private _autoCommit: boolean;
1087
+
1088
+ constructor(stream: any, opts?: { autoCommit?: boolean }) {
1089
+ this._stream = stream;
1090
+ this._buffer = [];
1091
+ this._autoCommit = opts?.autoCommit ?? false;
1092
+ }
1093
+
1094
+ clearLine(dir: -1 | 0 | 1): this {
1095
+ let seq: string;
1096
+ if (dir === -1) seq = "\x1b[1K";
1097
+ else if (dir === 1) seq = "\x1b[0K";
1098
+ else seq = "\x1b[2K";
1099
+ if (this._autoCommit) {
1100
+ this._stream.write(seq);
1101
+ } else {
1102
+ this._buffer.push(seq);
1103
+ }
1104
+ return this;
1105
+ }
1106
+
1107
+ clearScreenDown(): this {
1108
+ const seq = "\x1b[J";
1109
+ if (this._autoCommit) {
1110
+ this._stream.write(seq);
1111
+ } else {
1112
+ this._buffer.push(seq);
1113
+ }
1114
+ return this;
1115
+ }
1116
+
1117
+ cursorTo(x: number, y?: number): this {
1118
+ let seq: string;
1119
+ if (y !== undefined) {
1120
+ seq = `\x1b[${y + 1};${x + 1}H`;
1121
+ } else {
1122
+ seq = `\x1b[${x + 1}G`;
1123
+ }
1124
+ if (this._autoCommit) {
1125
+ this._stream.write(seq);
1126
+ } else {
1127
+ this._buffer.push(seq);
1128
+ }
1129
+ return this;
1130
+ }
1131
+
1132
+ moveCursor(dx: number, dy: number): this {
1133
+ const parts: string[] = [];
1134
+ if (dx > 0) parts.push(`\x1b[${dx}C`);
1135
+ else if (dx < 0) parts.push(`\x1b[${-dx}D`);
1136
+ if (dy > 0) parts.push(`\x1b[${dy}B`);
1137
+ else if (dy < 0) parts.push(`\x1b[${-dy}A`);
1138
+ const seq = parts.join("");
1139
+ if (seq) {
1140
+ if (this._autoCommit) {
1141
+ this._stream.write(seq);
1142
+ } else {
1143
+ this._buffer.push(seq);
1144
+ }
1145
+ }
1146
+ return this;
1147
+ }
1148
+
1149
+ commit(): Promise<void> {
1150
+ if (this._buffer.length > 0) {
1151
+ this._stream.write(this._buffer.join(""));
1152
+ this._buffer.length = 0;
1153
+ }
1154
+ return Promise.resolve();
1155
+ }
1156
+
1157
+ rollback(): this {
1158
+ this._buffer.length = 0;
1159
+ return this;
1160
+ }
1161
+ }
1162
+
665
1163
  export const promises = {
666
- createInterface(cfg?: InterfaceConfig) {
1164
+ createInterface(cfg?: InterfaceConfig): any {
667
1165
  const rl = createInterface(cfg);
668
- return {
669
- question(query: string): Promise<string> {
670
- return new Promise((resolve) => rl.question(query, resolve));
671
- },
672
- close(): void {
673
- rl.close();
674
- },
675
- async *[Symbol.asyncIterator](): AsyncGenerator<string, void, undefined> {
676
- yield* rl;
677
- },
1166
+ const wrapper: any = Object.create(rl);
1167
+
1168
+ wrapper.question = function (
1169
+ query: string,
1170
+ opts?: { signal?: AbortSignal },
1171
+ ): Promise<string> {
1172
+ return new Promise((resolve, reject) => {
1173
+ if (opts?.signal?.aborted) {
1174
+ reject(new DOMException("The operation was aborted", "AbortError"));
1175
+ return;
1176
+ }
1177
+
1178
+ let onAbort: (() => void) | undefined;
1179
+
1180
+ const handler = (answer: string) => {
1181
+ if (opts?.signal && onAbort) {
1182
+ opts.signal.removeEventListener("abort", onAbort);
1183
+ }
1184
+ resolve(answer);
1185
+ };
1186
+
1187
+ if (opts?.signal) {
1188
+ onAbort = () => {
1189
+ reject(new DOMException("The operation was aborted", "AbortError"));
1190
+ };
1191
+ opts.signal.addEventListener("abort", onAbort, { once: true });
1192
+ }
1193
+
1194
+ rl.question(query, handler);
1195
+ });
678
1196
  };
1197
+
1198
+ return wrapper;
679
1199
  },
1200
+
1201
+ Readline: ReadlineWriter,
680
1202
  };
681
1203
 
682
1204