@volter/tabnode 0.4.0 → 0.5.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.
Files changed (80) hide show
  1. package/dist/__sw__.js +259 -5
  2. package/dist/assets/{runtime-worker-DoV8lqBn.js → runtime-worker-B9buriZs.js} +4021 -2961
  3. package/dist/fetch-transport.d.ts +20 -0
  4. package/dist/guest-timers.d.ts +15 -0
  5. package/dist/http-client-transport.d.ts +19 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.mjs +5204 -3057
  8. package/dist/native-stream-binding.d.ts +57 -0
  9. package/dist/native-stream-owner.d.ts +121 -0
  10. package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
  11. package/dist/node-lib/binding/fds.d.ts +8 -0
  12. package/dist/node-lib/binding/fs.d.ts +311 -78
  13. package/dist/node-lib/binding/misc.d.ts +1 -0
  14. package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
  15. package/dist/node-lib/binding/process_wrap.d.ts +13 -2
  16. package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
  17. package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
  18. package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
  19. package/dist/node-lib/binding/util.d.ts +83 -0
  20. package/dist/node-lib/binding/uv.d.ts +4 -0
  21. package/dist/node-lib/fs-module.d.ts +1 -0
  22. package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
  23. package/dist/node-lib/internals/index.d.ts +1 -1
  24. package/dist/node-lib/load.d.ts +17 -3
  25. package/dist/node-lib/module-hooks.d.ts +1 -0
  26. package/dist/node-lib/public-modules.d.ts +1 -1
  27. package/dist/node-lib/timers.d.ts +38 -0
  28. package/dist/node-process-host.d.ts +41 -0
  29. package/dist/process-registry.d.ts +39 -0
  30. package/dist/process-tokens.d.ts +16 -0
  31. package/dist/promise-ownership.d.ts +3 -0
  32. package/dist/runtime.d.ts +10 -9
  33. package/dist/shims/child_process.d.ts +2 -0
  34. package/dist/shims/crypto.d.ts +660 -119
  35. package/dist/shims/dns.d.ts +76 -2
  36. package/dist/shims/process.d.ts +1 -0
  37. package/dist/shims/string_decoder.d.ts +19 -3
  38. package/dist/shims/tls.d.ts +853 -31
  39. package/dist/source-edits.d.ts +7 -0
  40. package/package.json +1 -1
  41. package/src/code-transforms.ts +2 -3
  42. package/src/fetch-transport.ts +98 -0
  43. package/src/guest-timers.ts +89 -0
  44. package/src/http-client-transport.ts +169 -0
  45. package/src/index.ts +12 -0
  46. package/src/native-stream-binding.ts +393 -0
  47. package/src/native-stream-owner.ts +317 -0
  48. package/src/node-lib/binding/duplex_stream.ts +95 -0
  49. package/src/node-lib/binding/fds.ts +18 -1
  50. package/src/node-lib/binding/fs.ts +45 -17
  51. package/src/node-lib/binding/misc.ts +2 -0
  52. package/src/node-lib/binding/pipe_wrap.ts +35 -12
  53. package/src/node-lib/binding/process_wrap.ts +72 -18
  54. package/src/node-lib/binding/stream_wrap.ts +196 -72
  55. package/src/node-lib/binding/tcp_wrap.ts +43 -8
  56. package/src/node-lib/binding/tty_wrap.ts +135 -8
  57. package/src/node-lib/binding/util.ts +24 -15
  58. package/src/node-lib/binding/uv.ts +4 -0
  59. package/src/node-lib/fs-module.ts +2 -18
  60. package/src/node-lib/internal/navigator.js +149 -0
  61. package/src/node-lib/internals/buffer-and-streams.ts +50 -11
  62. package/src/node-lib/internals/events-util.ts +5 -0
  63. package/src/node-lib/internals/index.ts +12 -2
  64. package/src/node-lib/load.ts +170 -62
  65. package/src/node-lib/module-hooks.ts +5 -2
  66. package/src/node-lib/public-modules.ts +12 -48
  67. package/src/node-lib/sources.ts +4 -0
  68. package/src/node-lib/timers.ts +93 -0
  69. package/src/node-process-host.ts +124 -0
  70. package/src/process-registry.ts +144 -0
  71. package/src/process-tokens.ts +60 -6
  72. package/src/promise-ownership.ts +28 -0
  73. package/src/runtime.ts +90 -214
  74. package/src/shims/child_process.ts +313 -80
  75. package/src/shims/crypto.ts +115 -100
  76. package/src/shims/dns.ts +17 -1
  77. package/src/shims/process.ts +33 -11
  78. package/src/shims/string_decoder.ts +10 -6
  79. package/src/shims/tls.ts +49 -19
  80. package/src/source-edits.ts +20 -0
@@ -15,6 +15,7 @@ import { LibuvStreamWrap, type WriteWrap } from './stream_wrap';
15
15
  import { UV_EADDRINUSE, UV_ENOENT, UV_EBADF } from './uv';
16
16
  import { handleForFd, registerFd, releaseFd } from './fds';
17
17
  import { __adoptHandle, ownerOf } from './handles';
18
+ import { nativeStreamFor, registerNativeStreamConstructor } from '../../native-stream-binding';
18
19
 
19
20
  /** libuv's `uv_pipe_t` flavours, and the two chmod bits `listen` reads. */
20
21
  export const constants = {
@@ -51,9 +52,12 @@ export class Pipe extends LibuvStreamWrap {
51
52
  constructor(type: number = constants.SOCKET) {
52
53
  super();
53
54
  this.type = type;
55
+ this.attachNative('pipe', type);
54
56
  }
55
57
 
56
58
  bind(path: string): number {
59
+ const native = nativeStreamFor(this);
60
+ if (native) return native.call({ operation: 'bind', id: native.descriptor.id, address: path }).status;
57
61
  const held = boundPaths.get(path);
58
62
  if (held && held !== this) return UV_EADDRINUSE;
59
63
  this.path = path;
@@ -62,12 +66,20 @@ export class Pipe extends LibuvStreamWrap {
62
66
  }
63
67
 
64
68
  listen(_backlog: number): number {
69
+ const native = nativeStreamFor(this);
70
+ if (native) {
71
+ const status = native.call({ operation: 'listen', id: native.descriptor.id, backlog: _backlog }).status;
72
+ if (status === 0) this.listening = true;
73
+ return status;
74
+ }
65
75
  if (this.path === null) return UV_EBADF;
66
76
  this.listening = true;
67
77
  return 0;
68
78
  }
69
79
 
70
80
  connect(req: PipeConnectWrap, path: string): number {
81
+ const native = nativeStreamFor(this);
82
+ if (native) return native.connect(path, undefined, false, status => req.oncomplete?.(status, this, req, true, true));
71
83
  // libuv answers a connect through the request, never from the call.
72
84
  queueMicrotask(() => {
73
85
  if (this.closed) return;
@@ -76,7 +88,7 @@ export class Pipe extends LibuvStreamWrap {
76
88
  req.oncomplete?.(UV_ENOENT, this, req, true, true);
77
89
  return;
78
90
  }
79
- const accepted = new Pipe(server.type === constants.IPC ? constants.IPC : constants.SOCKET);
91
+ const accepted = new (server.constructor as typeof Pipe)(server.type === constants.IPC ? constants.IPC : constants.SOCKET);
80
92
  accepted.path = path;
81
93
  // The serving run owns what it accepted, not the connecting one.
82
94
  __adoptHandle(accepted, ownerOf(server));
@@ -95,6 +107,12 @@ export class Pipe extends LibuvStreamWrap {
95
107
  * opened it, because a child's channel is written to before the child runs.
96
108
  */
97
109
  open(fd: number): number {
110
+ const native = nativeStreamFor(this);
111
+ if (native) {
112
+ const status = native.call({ operation: 'open', id: native.descriptor.id, fd }).status;
113
+ if (status === 0) this.fd = fd;
114
+ return status;
115
+ }
98
116
  const held = handleForFd(fd) as LibuvStreamWrap | undefined;
99
117
  if (!held) return UV_EBADF;
100
118
  this.takeOverFrom(held);
@@ -111,8 +129,9 @@ export class Pipe extends LibuvStreamWrap {
111
129
 
112
130
  /** libuv's `dup()`: the same pairing, under the same flavour. */
113
131
  override duplicate(): Pipe {
114
- const copy = new Pipe(this.type);
115
- copy.takeOverFrom(this);
132
+ const native = nativeStreamFor(this);
133
+ const copy = native ? native.duplicate() as Pipe : new (this.constructor as typeof Pipe)(this.type);
134
+ if (!native) copy.shareConnectionFrom(this);
116
135
  return copy;
117
136
  }
118
137
 
@@ -133,14 +152,16 @@ export class Pipe extends LibuvStreamWrap {
133
152
  *
134
153
  * The handle becomes the receiving run's. Node's `send(message, socket)`
135
154
  * duplicates the sender's descriptor into the other process, and from then
136
- * on the socket is an active handle of that process's loop; here parent and
137
- * child are one realm and there is nothing to duplicate, so the move is the
138
- * run ownership the engine counts. Without it, openvscode-server's extension
155
+ * on the socket is an active handle of that process's loop. The binding
156
+ * duplicates the wrapper and assigns that reference to the receiver.
157
+ * Without that ownership, openvscode-server's extension
139
158
  * host -- a program whose only handle is the socket its parent sent it --
140
159
  * read as idle and was settled with exit 0.
141
160
  */
142
- override writeUtf8String(req: WriteWrap, text: string, handle?: unknown): number {
143
- if (handle === undefined || handle === null) return super.writeUtf8String(req, text);
161
+ protected override dispatchWrite(req: WriteWrap, bytes: Uint8Array, handle?: unknown): number {
162
+ const native = nativeStreamFor(this);
163
+ if (native) return native.write(req, [bytes], handle);
164
+ if (handle === undefined || handle === null) return super.dispatchWrite(req, bytes);
144
165
  // A descriptor crosses as a duplicate, as `SCM_RIGHTS` hands one over: the
145
166
  // receiver gets its own handle on the same connection, so the sender's
146
167
  // close on `NODE_HANDLE_ACK` -- which Node's `child_process.js` does the
@@ -148,11 +169,12 @@ export class Pipe extends LibuvStreamWrap {
148
169
  // a listening server's, has nothing to duplicate and crosses as it is.
149
170
  const sent = handle as LibuvStreamWrap;
150
171
  const given = sent.peer !== null ? sent.duplicate() : sent;
151
- // The receiving run owns it from here: parent and child are one realm and
152
- // there is no descriptor table to move it in, so the move is the count the
153
- // engine keeps of what holds a run open.
172
+ // The receiving run owns this reference; the sender retains its own until
173
+ // Node closes it on acknowledgement, or continues using it with keepOpen.
154
174
  __adoptHandle(given, ownerOf(this.peer ?? this));
155
- return super.writeUtf8String(req, text, given);
175
+ const status = super.dispatchWrite(req, bytes, given);
176
+ if (status !== 0 && given !== sent) given.close();
177
+ return status;
156
178
  }
157
179
 
158
180
  protected override onCloseHandle(): void {
@@ -163,4 +185,5 @@ export class Pipe extends LibuvStreamWrap {
163
185
  }
164
186
  }
165
187
 
188
+ registerNativeStreamConstructor('pipe', Pipe);
166
189
  export default { Pipe, PipeConnectWrap, constants };
@@ -25,9 +25,11 @@ import { Pipe, constants as pipeConstants } from './pipe_wrap';
25
25
  import { UV_ENOENT, UV_ESRCH } from './uv';
26
26
  import {
27
27
  registerHandle, refHandle, unrefHandle, handleHasRef, releaseHandle, __adoptHandle,
28
- type OwnedHandle,
28
+ ownerOf, type OwnedHandle,
29
29
  } from './handles';
30
- import type { ProcessToken } from '../../process-tokens';
30
+ import { __runFor, mintPid, type ProcessToken } from '../../process-tokens';
31
+ import { handleForFd } from './fds';
32
+ import { TTY, type TerminalState } from './tty_wrap';
31
33
 
32
34
  /** One entry of Node's `options.stdio`, as `getValidStdio` builds it. */
33
35
  export interface StdioEntry {
@@ -85,8 +87,14 @@ export interface RunRequest {
85
87
  stderrIsPipe: boolean;
86
88
  /** True when the child's fd 0 is a pipe whose writer has not closed. */
87
89
  stdinIsPipe: boolean;
90
+ terminal?: TerminalState;
88
91
  /** The IPC channel the child is started with, where it has one. */
89
92
  channel?: RunChannel;
93
+ /**
94
+ * The child's pipes past fd 2 (`stdio: [..., 'pipe', 'pipe']`), each at the
95
+ * number the child reads or writes it by, with the child's end of it.
96
+ */
97
+ descriptors: { fd: number; pipe: Pipe }[];
90
98
  /** Called once, when the run has ended. */
91
99
  exit(code: number, signal: string | null): void;
92
100
  }
@@ -100,7 +108,7 @@ export interface StartedRun {
100
108
  /** libuv's `uv_process_kill`: 0, or `UV_ESRCH` for a run already over. */
101
109
  kill(signal: string): number;
102
110
  /** Bytes the parent wrote to the child's fd 0. */
103
- writeStdin(bytes: Uint8Array): void;
111
+ writeStdin(bytes: Uint8Array): void | Promise<void>;
104
112
  /** The parent closed the child's fd 0. */
105
113
  endStdin(): void;
106
114
  }
@@ -123,7 +131,6 @@ export function setProcessRunner(next: ProcessRunner): void {
123
131
  runner = next;
124
132
  }
125
133
 
126
- let nextPid = 1001 + Math.floor(Math.random() * 30000);
127
134
  let nextAsyncId = 1;
128
135
 
129
136
  /** `envPairs` is `KEY=value` strings; the engine's shell takes a record. */
@@ -154,18 +161,31 @@ function signalNameOf(signal: number | string): string {
154
161
  * `/work` means `/work/child.js`, and resolved against the engine's shell's
155
162
  * own root it meant `/child.js` and a child that exited 1.
156
163
  */
157
- function spawningDirectory(): string | undefined {
158
- const realm = (globalThis as unknown as { process?: { cwd?: () => string } }).process;
164
+ // A shared realm's process global is whichever guest ran last. The spawning
165
+ // handle belongs to the parent: inherited stdio and cwd must come from that
166
+ // parent's process, even while a sibling starts or exits. A failed fork was
167
+ // replaying stderr into its own closed IPC-backed stream (write EBADF).
168
+ function parentProcess(token: ProcessToken | null) {
169
+ return token !== null ? __runFor(token)?.process
170
+ : (globalThis as unknown as { process?: { cwd?: () => string; stdout?: { write(text: string): unknown }; stderr?: { write(text: string): unknown } } }).process;
171
+ }
172
+
173
+ function spawningDirectory(token: ProcessToken | null): string | undefined {
174
+ const realm = parentProcess(token);
159
175
  if (typeof realm?.cwd !== 'function') return undefined;
160
176
  try { return realm.cwd(); } catch { return undefined; }
161
177
  }
162
178
 
163
179
  /** Where an `inherit` entry's bytes go: the spawning program's own stream. */
164
- function inheritedWriter(fd: number): ((text: string) => void) | null {
180
+ function inheritedWriter(fd: number, token: ProcessToken | null): ((text: string) => void) | null {
165
181
  if (fd !== 1 && fd !== 2) return null;
166
- const realm = (globalThis as unknown as {
167
- process?: { stdout?: { write(text: string): unknown }; stderr?: { write(text: string): unknown } };
168
- }).process;
182
+ // Inherit the descriptor's original sink, not a guest replacement of
183
+ // process.stderr.write (a child commonly redirects its console over IPC).
184
+ if (token !== null) {
185
+ const parent = __runFor(token);
186
+ return (fd === 1 ? parent?.stdout : parent?.stderr) ?? null;
187
+ }
188
+ const realm = parentProcess(token);
169
189
  const stream = fd === 1 ? realm?.stdout : realm?.stderr;
170
190
  if (!stream || typeof stream.write !== 'function') return null;
171
191
  return (text: string) => {
@@ -195,6 +215,7 @@ export class Process implements OwnedHandle {
195
215
  private closed = false;
196
216
  /** The stdio entries this run was started with, and their far ends. */
197
217
  private stdio: StdioEntry[] = [];
218
+ private terminalEnds = new Map<number, TTY>();
198
219
  private readonly asyncId = nextAsyncId++;
199
220
 
200
221
  constructor() {
@@ -230,20 +251,37 @@ export class Process implements OwnedHandle {
230
251
  const request: RunRequest = {
231
252
  file: options.file,
232
253
  args: options.args ?? [options.file],
233
- cwd: options.cwd ?? spawningDirectory(),
254
+ cwd: options.cwd ?? spawningDirectory(ownerOf(this)),
234
255
  env,
235
256
  detached: options.detached === true,
236
257
  stdout: null,
237
258
  stderr: null,
238
259
  stderrIsPipe: false,
239
260
  stdinIsPipe: false,
261
+ descriptors: [],
240
262
  exit: (code, signal) => { this.reportExit(code, signal); },
241
263
  };
242
264
 
243
- let stdinFar: Pipe | null = null;
265
+ let stdinFar: LibuvStreamWrap | null = null;
244
266
  for (let index = 0; index < this.stdio.length; index += 1) {
245
267
  const entry = this.stdio[index];
246
268
  if (!entry) continue;
269
+ if (index < 3 && !entry.ipc) {
270
+ const descriptor = entry.type === 'fd' || entry.type === 'inherit'
271
+ ? handleForFd(entry.fd ?? index) : entry.handle;
272
+ if (descriptor instanceof TTY && descriptor.slave) {
273
+ // Inherit an independent descriptor reference, never the parent's
274
+ // wrapper. Failed spawn and child exit release only this copy.
275
+ const inherited = descriptor.duplicate();
276
+ __adoptHandle(inherited, null);
277
+ this.terminalEnds.set(index, inherited);
278
+ request.terminal = inherited.terminal;
279
+ if (index === 0) { stdinFar = inherited; request.stdinIsPipe = true; }
280
+ else if (index === 1) request.stdout = text => this.toPipe(index, text);
281
+ else request.stderr = text => this.toPipe(index, text);
282
+ continue;
283
+ }
284
+ }
247
285
  const far = this.pairFarEnd(entry);
248
286
  if (entry.ipc) {
249
287
  if (far) {
@@ -260,7 +298,12 @@ export class Process implements OwnedHandle {
260
298
  request.stdinIsPipe = far !== null;
261
299
  continue;
262
300
  }
263
- if (index !== 1 && index !== 2) continue;
301
+ if (index > 2) {
302
+ // A pipe past fd 2 is the child's too, at its own number: libuv
303
+ // hands a child every `UV_CREATE_PIPE` entry, not only stdio's three.
304
+ if (far) request.descriptors.push({ fd: index, pipe: far });
305
+ continue;
306
+ }
264
307
  if (far) {
265
308
  const to = index;
266
309
  const write = (text: string): void => { this.toPipe(to, text); };
@@ -270,7 +313,7 @@ export class Process implements OwnedHandle {
270
313
  request.stderrIsPipe = true;
271
314
  }
272
315
  } else if (entry.type === 'inherit' || entry.type === 'fd') {
273
- const inherited = inheritedWriter(entry.fd ?? index);
316
+ const inherited = inheritedWriter(entry.fd ?? index, ownerOf(this));
274
317
  if (index === 1) request.stdout = inherited;
275
318
  else request.stderr = inherited;
276
319
  }
@@ -284,7 +327,7 @@ export class Process implements OwnedHandle {
284
327
  this.run = runner.start(request);
285
328
  // The number the parent reads off the handle is the child's own
286
329
  // `process.pid`, as it is in Node; a runner that names none is given one.
287
- this.pid = this.run.pid ?? nextPid++;
330
+ this.pid = this.run.pid ?? mintPid();
288
331
  if (stdinFar) this.readStdinFrom(stdinFar);
289
332
  return 0;
290
333
  }
@@ -324,7 +367,7 @@ export class Process implements OwnedHandle {
324
367
 
325
368
  /** The far end of fd `index`, whoever holds it now. */
326
369
  private farEndAt(index: number): LibuvStreamWrap | null {
327
- return this.stdio[index]?.handle?.peer ?? null;
370
+ return this.terminalEnds.get(index) ?? this.stdio[index]?.handle?.peer ?? null;
328
371
  }
329
372
 
330
373
  private closeFarEnds(): void {
@@ -343,11 +386,22 @@ export class Process implements OwnedHandle {
343
386
  }
344
387
 
345
388
  /** Bytes the parent writes reach the child's fd 0; the parent's EOF ends it. */
346
- private readStdinFrom(far: Pipe): void {
389
+ private readStdinFrom(far: LibuvStreamWrap): void {
347
390
  far.onread = (arrayBuffer: ArrayBuffer | null): void => {
348
391
  const length = streamBaseState[kReadBytesOrError];
349
392
  if (length <= 0 || arrayBuffer === null) { this.run?.endStdin(); return; }
350
- this.run?.writeStdin(new Uint8Array(arrayBuffer, streamBaseState[kArrayBufferOffset], length).slice());
393
+ const run = this.run;
394
+ const consumed = run?.writeStdin(new Uint8Array(arrayBuffer, streamBaseState[kArrayBufferOffset], length).slice());
395
+ if (consumed) {
396
+ far.readStop();
397
+ void consumed.then(() => {
398
+ if (this.run === run && !this.ended && !far.closed) far.readStart();
399
+ }, () => {
400
+ // A stopped receiver closes its input pipe. Do not resume a reader
401
+ // or deliver its queued bytes to a later run.
402
+ if (this.run === run && !this.ended) { far.close(); run?.endStdin(); }
403
+ });
404
+ }
351
405
  };
352
406
  far.readStart();
353
407
  }