@volter/tabnode 0.5.8 → 0.5.10

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.
@@ -6771,6 +6771,11 @@ function __recordRun(token, run) {
6771
6771
  if (runs.get(token) === run) runs.delete(token);
6772
6772
  };
6773
6773
  }
6774
+ const terminations = /* @__PURE__ */ new Map();
6775
+ function __recordTermination(process2, signal) {
6776
+ const token = tokensByProcess.get(process2);
6777
+ if (token !== void 0) terminations.set(token, signal);
6778
+ }
6774
6779
  function __runFor(token) {
6775
6780
  return runs.get(token);
6776
6781
  }
@@ -17616,17 +17621,10 @@ var implementation$1 = function bind2(that) {
17616
17621
  var implementation = implementation$1;
17617
17622
  var functionBind = Function.prototype.bind || implementation;
17618
17623
  var functionCall = Function.prototype.call;
17619
- var functionApply;
17620
- var hasRequiredFunctionApply;
17621
- function requireFunctionApply() {
17622
- if (hasRequiredFunctionApply) return functionApply;
17623
- hasRequiredFunctionApply = 1;
17624
- functionApply = Function.prototype.apply;
17625
- return functionApply;
17626
- }
17624
+ var functionApply = Function.prototype.apply;
17627
17625
  var reflectApply = typeof Reflect !== "undefined" && Reflect && Reflect.apply;
17628
17626
  var bind$2 = functionBind;
17629
- var $apply$1 = requireFunctionApply();
17627
+ var $apply$1 = functionApply;
17630
17628
  var $call$2 = functionCall;
17631
17629
  var $reflectApply = reflectApply;
17632
17630
  var actualApply = $reflectApply || bind$2.call($call$2, $apply$1);
@@ -17746,7 +17744,7 @@ var hasSymbols = requireHasSymbols()();
17746
17744
  var getProto = requireGetProto();
17747
17745
  var $ObjectGPO = requireObject_getPrototypeOf();
17748
17746
  var $ReflectGPO = requireReflect_getPrototypeOf();
17749
- var $apply = requireFunctionApply();
17747
+ var $apply = functionApply;
17750
17748
  var $call = functionCall;
17751
17749
  var needsEval = {};
17752
17750
  var TypedArray = typeof Uint8Array === "undefined" || !getProto ? undefined$1 : getProto(Uint8Array);
@@ -18387,7 +18385,7 @@ function requireApplyBind() {
18387
18385
  if (hasRequiredApplyBind) return applyBind;
18388
18386
  hasRequiredApplyBind = 1;
18389
18387
  var bind3 = functionBind;
18390
- var $apply2 = requireFunctionApply();
18388
+ var $apply2 = functionApply;
18391
18389
  var actualApply$1 = actualApply;
18392
18390
  applyBind = function applyBind2() {
18393
18391
  return actualApply$1(bind3, $apply2, arguments);
@@ -26656,11 +26654,13 @@ function createProcessStream(isWritable, writeImpl) {
26656
26654
  return stream;
26657
26655
  },
26658
26656
  // Default write implementation (no-op for readable streams)
26659
- write(_data, _encoding, callback) {
26657
+ write(_data, ...rest) {
26658
+ const callback = trailingCallback(rest);
26660
26659
  if (callback) queueMicrotask(callback);
26661
26660
  return true;
26662
26661
  },
26663
- end(_data, callback) {
26662
+ end(...rest) {
26663
+ const callback = trailingCallback(rest);
26664
26664
  if (callback) queueMicrotask(callback);
26665
26665
  },
26666
26666
  pipe(destination) {
@@ -26676,14 +26676,31 @@ function createProcessStream(isWritable, writeImpl) {
26676
26676
  }
26677
26677
  };
26678
26678
  if (writeImpl) {
26679
- stream.write = (data2, _encoding, callback) => {
26680
- const result = writeImpl(typeof data2 === "string" ? data2 : data2.toString());
26679
+ stream.write = (data2, ...rest) => {
26680
+ const result = writeImpl(textOf(data2));
26681
+ const callback = trailingCallback(rest);
26681
26682
  if (callback) queueMicrotask(callback);
26682
26683
  return result;
26683
26684
  };
26685
+ stream.end = (...args) => {
26686
+ const data2 = args[0];
26687
+ if (typeof data2 === "string" || data2 instanceof Uint8Array) writeImpl(textOf(data2));
26688
+ const callback = trailingCallback(args);
26689
+ if (callback) queueMicrotask(callback);
26690
+ };
26684
26691
  }
26685
26692
  return stream;
26686
26693
  }
26694
+ function textOf(data2) {
26695
+ return typeof data2 === "string" ? data2 : new TextDecoder().decode(data2);
26696
+ }
26697
+ function trailingCallback(args) {
26698
+ for (let index2 = args.length - 1; index2 >= 0; index2 -= 1) {
26699
+ const value = args[index2];
26700
+ if (typeof value === "function") return value;
26701
+ }
26702
+ return void 0;
26703
+ }
26687
26704
  const __substrateSignals = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGILL: 4, SIGTRAP: 5, SIGABRT: 6, SIGBUS: 7, SIGFPE: 8, SIGKILL: 9, SIGUSR1: 10, SIGSEGV: 11, SIGUSR2: 12, SIGPIPE: 13, SIGALRM: 14, SIGTERM: 15, SIGCHLD: 17, SIGCONT: 18, SIGSTOP: 19, SIGTSTP: 20, SIGTTIN: 21, SIGTTOU: 22, SIGURG: 23, SIGWINCH: 28 };
26688
26705
  const __substrateSignalNames = Object.fromEntries(Object.entries(__substrateSignals).map(([name, number]) => [number, name]));
26689
26706
  const __substrateBindingWarned = /* @__PURE__ */ new WeakSet();
@@ -26825,6 +26842,7 @@ function createProcess(options) {
26825
26842
  return true;
26826
26843
  }
26827
26844
  if (__substrateIgnoredSignals.has(name)) return true;
26845
+ __recordTermination(proc, name);
26828
26846
  proc.exit(128 + __substrateSignals[name]);
26829
26847
  return true;
26830
26848
  },
@@ -70835,6 +70853,7 @@ function startChildRun(request) {
70835
70853
  let finished = false;
70836
70854
  let started = false;
70837
70855
  let killedBy = null;
70856
+ let hosted = false;
70838
70857
  const initialStdin = [];
70839
70858
  let flushingInput;
70840
70859
  const flushStdin = () => {
@@ -70915,6 +70934,7 @@ function startChildRun(request) {
70915
70934
  let outcome;
70916
70935
  try {
70917
70936
  const processHost = admittedNode ? nodeProcessHostFor(token) : void 0;
70937
+ hosted = processHost !== void 0;
70918
70938
  const env = { ...request.env };
70919
70939
  delete env[PROCESS_TOKEN_ENV];
70920
70940
  outcome = processHost ? await runHostedNode(processHost, {
@@ -70943,7 +70963,7 @@ function startChildRun(request) {
70943
70963
  }
70944
70964
  if (outcome.stdout.length > streamedOut) (_a4 = request.stdout) == null ? void 0 : _a4.call(request, outcome.stdout.slice(streamedOut));
70945
70965
  if (outcome.stderr.length > streamedErr) (_b3 = request.stderr) == null ? void 0 : _b3.call(request, outcome.stderr.slice(streamedErr));
70946
- end(outcome.exitCode, null);
70966
+ end(outcome.exitCode, outcome.signal ?? null);
70947
70967
  })();
70948
70968
  };
70949
70969
  _activeForkedChildren += 1;
@@ -70954,6 +70974,7 @@ function startChildRun(request) {
70954
70974
  pid,
70955
70975
  kill(signal) {
70956
70976
  if (finished) return UV_ESRCH;
70977
+ if (hosted && signal !== "SIGKILL" && signal !== "SIGSTOP" && signalPid(pid, signal)) return 0;
70957
70978
  killedBy = signal;
70958
70979
  controller.abort(signal);
70959
70980
  __releaseOwnedServers(token, false);
@@ -15,5 +15,13 @@ export interface HttpClientExchangeResponse {
15
15
  export type HttpClientExchange = (request: HttpClientExchangeRequest) => Promise<HttpClientExchangeResponse>;
16
16
  /** Capture process attribution synchronously when the connection is requested. */
17
17
  export type HttpClientTransportFactory = () => HttpClientExchange;
18
+ export interface HttpClientTransportOptions {
19
+ /**
20
+ * Loopback ports the host itself serves (a service of the page, not a
21
+ * guest's listener). Plain HTTP to one of them goes through the host
22
+ * exchange; every other loopback port stays the engine's own network.
23
+ */
24
+ hostPorts?: () => ReadonlySet<number>;
25
+ }
18
26
  /** Host-only installation; guest modules receive Node's normal exports. */
19
- export declare function installHttpClientTransport(factory: HttpClientTransportFactory): () => void;
27
+ export declare function installHttpClientTransport(factory: HttpClientTransportFactory, transportOptions?: HttpClientTransportOptions): () => void;
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export { createProcess } from './shims/process';
20
20
  export type { Process, ProcessEnv } from './shims/process';
21
21
  export * as path from './shims/path';
22
22
  export { httpModule as http, httpsModule as https } from './node-lib/http-module';
23
- export { installHttpClientTransport } from './http-client-transport';
23
+ export { installHttpClientTransport, type HttpClientTransportOptions } from './http-client-transport';
24
24
  export type { HttpClientExchange, HttpClientExchangeRequest, HttpClientExchangeResponse, HttpClientTransportFactory } from './http-client-transport';
25
25
  export { netModule as net } from './node-lib/net-module';
26
26
  export { eventsModule as events } from './node-lib/events-module';
@@ -57,6 +57,8 @@ export interface RunResult {
57
57
  stdout: string;
58
58
  stderr: string;
59
59
  exitCode: number;
60
+ /** The signal whose default action ended the run's guest, as Node reports a process a signal ended. */
61
+ signal?: string;
60
62
  }
61
63
  export interface RunOptions {
62
64
  cwd?: string;
package/dist/index.mjs CHANGED
@@ -6480,6 +6480,16 @@ function __recordRun(token, run) {
6480
6480
  if (runs.get(token) === run) runs.delete(token);
6481
6481
  };
6482
6482
  }
6483
+ const terminations = /* @__PURE__ */ new Map();
6484
+ function __recordTermination(process2, signal) {
6485
+ const token = tokensByProcess.get(process2);
6486
+ if (token !== void 0) terminations.set(token, signal);
6487
+ }
6488
+ function __takeTermination(token) {
6489
+ const signal = terminations.get(token);
6490
+ terminations.delete(token);
6491
+ return signal;
6492
+ }
6483
6493
  function __tokenForProcess(process2) {
6484
6494
  return tokensByProcess.get(process2) ?? null;
6485
6495
  }
@@ -26839,11 +26849,13 @@ function createProcessStream(isWritable, writeImpl) {
26839
26849
  return stream;
26840
26850
  },
26841
26851
  // Default write implementation (no-op for readable streams)
26842
- write(_data, _encoding, callback) {
26852
+ write(_data, ...rest) {
26853
+ const callback = trailingCallback(rest);
26843
26854
  if (callback) queueMicrotask(callback);
26844
26855
  return true;
26845
26856
  },
26846
- end(_data, callback) {
26857
+ end(...rest) {
26858
+ const callback = trailingCallback(rest);
26847
26859
  if (callback) queueMicrotask(callback);
26848
26860
  },
26849
26861
  pipe(destination) {
@@ -26859,14 +26871,31 @@ function createProcessStream(isWritable, writeImpl) {
26859
26871
  }
26860
26872
  };
26861
26873
  if (writeImpl) {
26862
- stream.write = (data2, _encoding, callback) => {
26863
- const result = writeImpl(typeof data2 === "string" ? data2 : data2.toString());
26874
+ stream.write = (data2, ...rest) => {
26875
+ const result = writeImpl(textOf(data2));
26876
+ const callback = trailingCallback(rest);
26864
26877
  if (callback) queueMicrotask(callback);
26865
26878
  return result;
26866
26879
  };
26880
+ stream.end = (...args) => {
26881
+ const data2 = args[0];
26882
+ if (typeof data2 === "string" || data2 instanceof Uint8Array) writeImpl(textOf(data2));
26883
+ const callback = trailingCallback(args);
26884
+ if (callback) queueMicrotask(callback);
26885
+ };
26867
26886
  }
26868
26887
  return stream;
26869
26888
  }
26889
+ function textOf(data2) {
26890
+ return typeof data2 === "string" ? data2 : new TextDecoder().decode(data2);
26891
+ }
26892
+ function trailingCallback(args) {
26893
+ for (let index2 = args.length - 1; index2 >= 0; index2 -= 1) {
26894
+ const value = args[index2];
26895
+ if (typeof value === "function") return value;
26896
+ }
26897
+ return void 0;
26898
+ }
26870
26899
  const __substrateSignals = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGILL: 4, SIGTRAP: 5, SIGABRT: 6, SIGBUS: 7, SIGFPE: 8, SIGKILL: 9, SIGUSR1: 10, SIGSEGV: 11, SIGUSR2: 12, SIGPIPE: 13, SIGALRM: 14, SIGTERM: 15, SIGCHLD: 17, SIGCONT: 18, SIGSTOP: 19, SIGTSTP: 20, SIGTTIN: 21, SIGTTOU: 22, SIGURG: 23, SIGWINCH: 28 };
26871
26900
  const __substrateSignalNames = Object.fromEntries(Object.entries(__substrateSignals).map(([name, number]) => [number, name]));
26872
26901
  const __substrateBindingWarned = /* @__PURE__ */ new WeakSet();
@@ -27008,6 +27037,7 @@ function createProcess(options) {
27008
27037
  return true;
27009
27038
  }
27010
27039
  if (__substrateIgnoredSignals.has(name)) return true;
27040
+ __recordTermination(proc, name);
27011
27041
  proc.exit(128 + __substrateSignals[name]);
27012
27042
  return true;
27013
27043
  },
@@ -30527,6 +30557,7 @@ function startChildRun(request) {
30527
30557
  let finished = false;
30528
30558
  let started2 = false;
30529
30559
  let killedBy = null;
30560
+ let hosted = false;
30530
30561
  const initialStdin = [];
30531
30562
  let flushingInput;
30532
30563
  const flushStdin = () => {
@@ -30607,6 +30638,7 @@ function startChildRun(request) {
30607
30638
  let outcome;
30608
30639
  try {
30609
30640
  const processHost = admittedNode ? nodeProcessHostFor(token) : void 0;
30641
+ hosted = processHost !== void 0;
30610
30642
  const env = { ...request.env };
30611
30643
  delete env[PROCESS_TOKEN_ENV];
30612
30644
  outcome = processHost ? await runHostedNode(processHost, {
@@ -30635,7 +30667,7 @@ function startChildRun(request) {
30635
30667
  }
30636
30668
  if (outcome.stdout.length > streamedOut) (_a3 = request.stdout) == null ? void 0 : _a3.call(request, outcome.stdout.slice(streamedOut));
30637
30669
  if (outcome.stderr.length > streamedErr) (_b2 = request.stderr) == null ? void 0 : _b2.call(request, outcome.stderr.slice(streamedErr));
30638
- end(outcome.exitCode, null);
30670
+ end(outcome.exitCode, outcome.signal ?? null);
30639
30671
  })();
30640
30672
  };
30641
30673
  _activeForkedChildren += 1;
@@ -30646,6 +30678,7 @@ function startChildRun(request) {
30646
30678
  pid,
30647
30679
  kill(signal) {
30648
30680
  if (finished) return UV_ESRCH;
30681
+ if (hosted && signal !== "SIGKILL" && signal !== "SIGSTOP" && signalPid(pid, signal)) return 0;
30649
30682
  killedBy = signal;
30650
30683
  controller.abort(signal);
30651
30684
  __releaseOwnedServers(token, false);
@@ -35695,7 +35728,7 @@ class WorkerRuntime {
35695
35728
  this.worker = new Worker(
35696
35729
  new URL(
35697
35730
  /* @vite-ignore */
35698
- "/assets/runtime-worker-Cl9uKRen.js",
35731
+ "/assets/runtime-worker-CQv8H3sj.js",
35699
35732
  import.meta.url
35700
35733
  ),
35701
35734
  { type: "module" }
@@ -35913,7 +35946,7 @@ class DuplexStreamHandle extends LibuvStreamWrap {
35913
35946
  const unsupported = (detail) => Object.assign(new Error(`HTTP transport does not support ${detail}.`), { code: "ERR_HTTP_TRANSPORT_UNSUPPORTED" });
35914
35947
  const local = (host2) => host2 === "localhost" || host2 === "::1" || host2 === "[::1]" || /^127\./u.test(host2);
35915
35948
  const tlsOptions = ["ca", "cert", "key", "pfx", "passphrase", "ciphers", "secureContext", "checkServerIdentity", "minVersion", "maxVersion", "secureProtocol", "secureOptions", "session", "ALPNProtocols", "pskCallback"];
35916
- function installHttpClientTransport(factory) {
35949
+ function installHttpClientTransport(factory, transportOptions = {}) {
35917
35950
  const installed2 = /* @__PURE__ */ new WeakMap();
35918
35951
  const live = /* @__PURE__ */ new Set();
35919
35952
  const collected = new FinalizationRegistry((ref2) => live.delete(ref2));
@@ -35922,8 +35955,11 @@ function installHttpClientTransport(factory) {
35922
35955
  if (installed2.has(prototype)) return;
35923
35956
  const original = prototype.createConnection;
35924
35957
  const connect2 = function(options, callback) {
35958
+ var _a2;
35925
35959
  const host2 = String(options.hostname ?? options.host ?? "localhost").toLowerCase();
35926
- if (protocol === "http:" && (local(host2) || options.socketPath)) return original.call(this, options, callback);
35960
+ if (protocol === "http:" && (local(host2) || options.socketPath) && !(local(host2) && !options.socketPath && ((_a2 = transportOptions.hostPorts) == null ? void 0 : _a2.call(transportOptions).has(Number(options.port ?? 80))))) {
35961
+ return original.call(this, options, callback);
35962
+ }
35927
35963
  try {
35928
35964
  if (options.socketPath || options.localAddress || options.localPort || options.lookup || options.family) throw unsupported("custom socket routing");
35929
35965
  if (tlsOptions.some((key) => options[key] !== void 0) || options.rejectUnauthorized === false || options.servername !== void 0 && options.servername !== host2) throw unsupported("custom TLS options");
@@ -38401,10 +38437,12 @@ function createContainer(options) {
38401
38437
  runCommand(command, { cwd: runOptions == null ? void 0 : runOptions.cwd, env: runOptions == null ? void 0 : runOptions.env, stdin: typeof (runOptions == null ? void 0 : runOptions.stdin) === "string" ? runOptions.stdin : void 0, processToken, vfs: vfs2 }, (error, stdout, stderr) => {
38402
38438
  var _a3;
38403
38439
  releaseRunStreams(processToken);
38440
+ const terminatedBy = __takeTermination(processToken);
38404
38441
  resolve2({
38405
38442
  stdout: String(stdout),
38406
38443
  stderr: String(stderr),
38407
- exitCode: ((_a3 = runOptions == null ? void 0 : runOptions.signal) == null ? void 0 : _a3.aborted) ? 143 : error ? error.code ?? 1 : 0
38444
+ exitCode: ((_a3 = runOptions == null ? void 0 : runOptions.signal) == null ? void 0 : _a3.aborted) ? 143 : error ? error.code ?? 1 : 0,
38445
+ ...terminatedBy ? { signal: terminatedBy } : {}
38408
38446
  });
38409
38447
  });
38410
38448
  });
@@ -21,10 +21,12 @@ export interface NodeProcessLaunch {
21
21
  }[];
22
22
  }
23
23
  export interface NodeProcessHost {
24
+ /** `signal` names the signal whose default action ended the process, as Node's exit event does. */
24
25
  run(launch: NodeProcessLaunch): Promise<{
25
26
  stdout: string;
26
27
  stderr: string;
27
28
  exitCode: number;
29
+ signal?: string;
28
30
  }>;
29
31
  }
30
32
  export declare function nodeProcessHostInstalled(): boolean;
@@ -44,6 +44,8 @@ export declare function enterRun<T>(token: ProcessToken, fn: () => T): T;
44
44
  * releasing twice is harmless, as a run ends once.
45
45
  */
46
46
  export declare function __recordRun(token: ProcessToken, run: OwnedRun): () => void;
47
+ export declare function __recordTermination(process: Process, signal: string): void;
48
+ export declare function __takeTermination(token: ProcessToken): string | undefined;
47
49
  /** Bound guest globals retain their actual process across native await turns. */
48
50
  export declare function __tokenForProcess(process: Process): ProcessToken | null;
49
51
  /** The run recorded under this name, while it lasts. */
@@ -42,8 +42,8 @@ interface ProcessStream {
42
42
  getColorDepth: () => number;
43
43
  }
44
44
  interface ProcessWritableStream extends ProcessStream {
45
- write: (data: string | Buffer, encoding?: string, callback?: () => void) => boolean;
46
- end?: (data?: string, callback?: () => void) => void;
45
+ write: (data: string | Buffer, ...rest: unknown[]) => boolean;
46
+ end?: (...args: unknown[]) => void;
47
47
  }
48
48
  /**
49
49
  * `process.stdin` is a readable stream on Node -- a `net.Socket` when fd 0 is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/tabnode",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "description": "Node's own library, in a browser tab.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,8 +33,17 @@ const unsupported = (detail: string): Error => Object.assign(new Error(`HTTP tra
33
33
  const local = (host: string): boolean => host === 'localhost' || host === '::1' || host === '[::1]' || /^127\./u.test(host);
34
34
  const tlsOptions = ['ca', 'cert', 'key', 'pfx', 'passphrase', 'ciphers', 'secureContext', 'checkServerIdentity', 'minVersion', 'maxVersion', 'secureProtocol', 'secureOptions', 'session', 'ALPNProtocols', 'pskCallback'];
35
35
 
36
+ export interface HttpClientTransportOptions {
37
+ /**
38
+ * Loopback ports the host itself serves (a service of the page, not a
39
+ * guest's listener). Plain HTTP to one of them goes through the host
40
+ * exchange; every other loopback port stays the engine's own network.
41
+ */
42
+ hostPorts?: () => ReadonlySet<number>;
43
+ }
44
+
36
45
  /** Host-only installation; guest modules receive Node's normal exports. */
37
- export function installHttpClientTransport(factory: HttpClientTransportFactory): () => void {
46
+ export function installHttpClientTransport(factory: HttpClientTransportFactory, transportOptions: HttpClientTransportOptions = {}): () => void {
38
47
  type AgentPrototype = { createConnection: (...args: any[]) => any };
39
48
  const installed = new WeakMap<AgentPrototype, { original: AgentPrototype['createConnection']; connect: AgentPrototype['createConnection'] }>();
40
49
  const live = new Set<WeakRef<AgentPrototype>>();
@@ -45,8 +54,11 @@ export function installHttpClientTransport(factory: HttpClientTransportFactory):
45
54
  const original = prototype.createConnection;
46
55
  const connect = function(this: unknown, options: Record<string, any>, callback: (error: Error | null, socket?: unknown) => void): unknown {
47
56
  const host = String(options.hostname ?? options.host ?? 'localhost').toLowerCase();
48
- // Plain local HTTP is the engine's existing virtual network.
49
- if (protocol === 'http:' && (local(host) || options.socketPath)) return original.call(this, options, callback);
57
+ // Plain local HTTP is the engine's existing virtual network, except a port the host serves itself.
58
+ if (protocol === 'http:' && (local(host) || options.socketPath)
59
+ && !(local(host) && !options.socketPath && transportOptions.hostPorts?.().has(Number(options.port ?? 80)))) {
60
+ return original.call(this, options, callback);
61
+ }
50
62
  try {
51
63
  if (options.socketPath || options.localAddress || options.localPort || options.lookup || options.family) throw unsupported('custom socket routing');
52
64
  if (tlsOptions.some(key => options[key] !== undefined) || options.rejectUnauthorized === false
package/src/index.ts CHANGED
@@ -24,7 +24,7 @@ export { createProcess } from './shims/process';
24
24
  export type { Process, ProcessEnv } from './shims/process';
25
25
  export * as path from './shims/path';
26
26
  export { httpModule as http, httpsModule as https } from './node-lib/http-module';
27
- export { installHttpClientTransport } from './http-client-transport';
27
+ export { installHttpClientTransport, type HttpClientTransportOptions } from './http-client-transport';
28
28
  export type { HttpClientExchange, HttpClientExchangeRequest, HttpClientExchangeResponse, HttpClientTransportFactory } from './http-client-transport';
29
29
  import { __releaseOwnedServers, __ownedServerPorts } from './node-lib/net-module';
30
30
  export { netModule as net } from './node-lib/net-module';
@@ -58,7 +58,7 @@ import { __adoptHandle, ownerOf, type OwnedHandle } from './node-lib/binding/han
58
58
  import { listenerOnPort } from './node-lib/binding/tcp_wrap';
59
59
  import { nativeStreamOwnerPid } from './native-stream-owner';
60
60
 
61
- import { __currentProcessToken, __runFor, __signalOwnedProcess, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
61
+ import { __currentProcessToken, __runFor, __signalOwnedProcess, __takeTermination, __stopOwnedProcess, runPid, processByPid } from './process-tokens';
62
62
  export { runPid, processByPid };
63
63
  export { createProcessRegistryScope, installProcessRegistry, ownerProcessRegistryScope, ownerProcessTable } from './process-tokens';
64
64
  export { installNodeProcessHost, nodeProcessHostInstalled } from './node-process-host';
@@ -72,6 +72,8 @@ export interface RunResult {
72
72
  stdout: string;
73
73
  stderr: string;
74
74
  exitCode: number;
75
+ /** The signal whose default action ended the run's guest, as Node reports a process a signal ended. */
76
+ signal?: string;
75
77
  }
76
78
 
77
79
  export interface RunOptions {
@@ -198,10 +200,12 @@ export function createContainer(options?: ContainerOptions): {
198
200
  // piped, as it does in a Node shell.
199
201
  runCommand(command, { cwd: runOptions?.cwd, env: runOptions?.env, stdin: typeof runOptions?.stdin === 'string' ? runOptions.stdin : undefined, processToken, vfs }, (error, stdout, stderr) => {
200
202
  releaseRunStreams(processToken);
203
+ const terminatedBy = __takeTermination(processToken);
201
204
  resolve({
202
205
  stdout: String(stdout),
203
206
  stderr: String(stderr),
204
207
  exitCode: runOptions?.signal?.aborted ? 143 : error ? (error.code ?? 1) : 0,
208
+ ...(terminatedBy ? { signal: terminatedBy } : {}),
205
209
  });
206
210
  });
207
211
  });
@@ -20,7 +20,8 @@ export interface NodeProcessLaunch {
20
20
  }
21
21
 
22
22
  export interface NodeProcessHost {
23
- run(launch: NodeProcessLaunch): Promise<{ stdout: string; stderr: string; exitCode: number }>;
23
+ /** `signal` names the signal whose default action ended the process, as Node's exit event does. */
24
+ run(launch: NodeProcessLaunch): Promise<{ stdout: string; stderr: string; exitCode: number; signal?: string }>;
24
25
  }
25
26
 
26
27
  let host: NodeProcessHost | undefined;
@@ -78,6 +78,22 @@ export function __recordRun(token: ProcessToken, run: OwnedRun): () => void {
78
78
  };
79
79
  }
80
80
 
81
+ /**
82
+ * The signal whose default action ended a run's guest, kept under the run's
83
+ * name until the run reports its result: Node reports such a process as ended
84
+ * by that signal, not by the exit code its default action implies.
85
+ */
86
+ const terminations = new Map<ProcessToken, string>();
87
+ export function __recordTermination(process: Process, signal: string): void {
88
+ const token = tokensByProcess.get(process);
89
+ if (token !== undefined) terminations.set(token, signal);
90
+ }
91
+ export function __takeTermination(token: ProcessToken): string | undefined {
92
+ const signal = terminations.get(token);
93
+ terminations.delete(token);
94
+ return signal;
95
+ }
96
+
81
97
  /** Bound guest globals retain their actual process across native await turns. */
82
98
  export function __tokenForProcess(process: Process): ProcessToken | null {
83
99
  return tokensByProcess.get(process) ?? null;
@@ -60,7 +60,7 @@ import { __substrateExecPath, __substrateProgramName, __substrateLineFor, __subs
60
60
 
61
61
  import { __substrateChildren, __onUncaughtException, __reportUncaughtException } from './process';
62
62
 
63
- import { PROCESS_TOKEN_ENV, __recordRun, __runFor, __currentProcessToken, __lastLaunchedToken, __setLastLaunchedToken, __stopOwnedProcess, enterRun, mintPid, setRunPid, runPid, forgetRunPid, type ProcessToken } from '../process-tokens';
63
+ import { PROCESS_TOKEN_ENV, __recordRun, __runFor, __currentProcessToken, __lastLaunchedToken, __setLastLaunchedToken, __stopOwnedProcess, enterRun, mintPid, setRunPid, runPid, forgetRunPid, signalPid, type ProcessToken } from '../process-tokens';
64
64
  /** The host's own `process`, where it has one that emits, taken as the shim loads and before any guest's takes the global name. */
65
65
  const __hostProcess: { on(event: string, listener: (reason: unknown) => void): unknown; off(event: string, listener: (reason: unknown) => void): unknown } | null =
66
66
  typeof process !== 'undefined' && process !== null && typeof (process as { on?: unknown }).on === 'function' && typeof (process as { off?: unknown }).off === 'function'
@@ -1399,6 +1399,7 @@ interface CommandOutcome {
1399
1399
  stdout: string;
1400
1400
  stderr: string;
1401
1401
  exitCode: number;
1402
+ signal?: string;
1402
1403
  }
1403
1404
 
1404
1405
  /** What a caller gives one run of a command line. */
@@ -1726,6 +1727,8 @@ function startChildRun(request: RunRequest): StartedRun {
1726
1727
  let finished = false;
1727
1728
  let started = false;
1728
1729
  let killedBy: string | null = null;
1730
+ // Whether the child runs on the process host, in a realm of its own.
1731
+ let hosted = false;
1729
1732
  /** What the parent wrote to the child's fd 0 before the command began. */
1730
1733
  const initialStdin: Uint8Array[] = [];
1731
1734
 
@@ -1812,6 +1815,7 @@ function startChildRun(request: RunRequest): StartedRun {
1812
1815
  let outcome: CommandOutcome;
1813
1816
  try {
1814
1817
  const processHost = admittedNode ? nodeProcessHostFor(token) : undefined;
1818
+ hosted = processHost !== undefined;
1815
1819
  const env = { ...request.env };
1816
1820
  delete env[PROCESS_TOKEN_ENV];
1817
1821
  outcome = processHost ? await runHostedNode(processHost, {
@@ -1837,7 +1841,7 @@ function startChildRun(request: RunRequest): StartedRun {
1837
1841
  // its own as it went, and only what it has not already sent is left.
1838
1842
  if (outcome.stdout.length > streamedOut) request.stdout?.(outcome.stdout.slice(streamedOut));
1839
1843
  if (outcome.stderr.length > streamedErr) request.stderr?.(outcome.stderr.slice(streamedErr));
1840
- end(outcome.exitCode, null);
1844
+ end(outcome.exitCode, outcome.signal ?? null);
1841
1845
  })();
1842
1846
  };
1843
1847
 
@@ -1850,6 +1854,11 @@ function startChildRun(request: RunRequest): StartedRun {
1850
1854
  pid,
1851
1855
  kill(signal: string): number {
1852
1856
  if (finished) return UV_ESRCH;
1857
+ // A child in a realm of its own takes a catchable signal as a machine's
1858
+ // process does: its listeners run, else its default action ends it, and
1859
+ // it ends by its own outcome. Only when nothing there receives it does
1860
+ // the parent end it here.
1861
+ if (hosted && signal !== 'SIGKILL' && signal !== 'SIGSTOP' && signalPid(pid, signal)) return 0;
1853
1862
  killedBy = signal;
1854
1863
  controller.abort(signal);
1855
1864
  __releaseOwnedServers(token, false);
@@ -10,7 +10,7 @@ import { Readable } from '../node-lib/stream-module';
10
10
  import { loadNodeLibFor } from '../node-lib/load';
11
11
  import { constantsBinding } from './constants';
12
12
  import ttyWrapBinding from '../node-lib/binding/tty_wrap';
13
- import { mintPid, pidIsLive, signalPid } from '../process-tokens';
13
+ import { mintPid, pidIsLive, signalPid, __recordTermination } from '../process-tokens';
14
14
  import { NODE_LTS_VERSION, nodeVersions } from '../node-lib/node-versions';
15
15
  import { freemem as osFreemem } from './os';
16
16
 
@@ -60,8 +60,8 @@ interface ProcessStream {
60
60
  }
61
61
 
62
62
  interface ProcessWritableStream extends ProcessStream {
63
- write: (data: string | Buffer, encoding?: string, callback?: () => void) => boolean;
64
- end?: (data?: string, callback?: () => void) => void;
63
+ write: (data: string | Buffer, ...rest: unknown[]) => boolean;
64
+ end?: (...args: unknown[]) => void;
65
65
  }
66
66
 
67
67
  /**
@@ -302,11 +302,13 @@ function createProcessStream(
302
302
  return stream;
303
303
  },
304
304
  // Default write implementation (no-op for readable streams)
305
- write(_data: string | Buffer, _encoding?: string, callback?: () => void) {
305
+ write(_data: string | Buffer, ...rest: unknown[]) {
306
+ const callback = trailingCallback(rest);
306
307
  if (callback) queueMicrotask(callback);
307
308
  return true;
308
309
  },
309
- end(_data?: string, callback?: () => void) {
310
+ end(...rest: unknown[]) {
311
+ const callback = trailingCallback(rest);
310
312
  if (callback) queueMicrotask(callback);
311
313
  },
312
314
  pipe(destination: unknown) {
@@ -324,16 +326,42 @@ function createProcessStream(
324
326
 
325
327
  // Override write for actual writable streams
326
328
  if (isWritable && writeImpl) {
327
- stream.write = (data: string | Buffer, _encoding?: string, callback?: () => void) => {
328
- const result = writeImpl(typeof data === 'string' ? data : data.toString());
329
+ stream.write = (data: string | Buffer, ...rest: unknown[]) => {
330
+ const result = writeImpl(textOf(data));
331
+ const callback = trailingCallback(rest);
329
332
  if (callback) queueMicrotask(callback);
330
333
  return result;
331
334
  };
335
+ stream.end = (...args: unknown[]) => {
336
+ const data = args[0];
337
+ if (typeof data === 'string' || data instanceof Uint8Array) writeImpl(textOf(data));
338
+ const callback = trailingCallback(args);
339
+ if (callback) queueMicrotask(callback);
340
+ };
332
341
  }
333
342
 
334
343
  return stream;
335
344
  }
336
345
 
346
+ /** A chunk's text: bytes, a Buffer's or a plain Uint8Array's, are UTF-8. */
347
+ function textOf(data: string | Uint8Array): string {
348
+ return typeof data === 'string' ? data : new TextDecoder().decode(data);
349
+ }
350
+
351
+ /**
352
+ * Node's `write(chunk[, encoding][, callback])` and `end([chunk][, encoding][, callback])`:
353
+ * the callback is whichever argument is a function. `stdout.write('', done)`
354
+ * is how a program waits for its output to be flushed before it exits, and a
355
+ * callback read only from the third place never ran.
356
+ */
357
+ function trailingCallback(args: readonly unknown[]): (() => void) | undefined {
358
+ for (let index = args.length - 1; index >= 0; index -= 1) {
359
+ const value = args[index];
360
+ if (typeof value === 'function') return value as () => void;
361
+ }
362
+ return undefined;
363
+ }
364
+
337
365
  /** The signal numbers a guest sees, and the signals a tab must ignore. */
338
366
  const __substrateSignals: Record<string, number> = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGILL: 4, SIGTRAP: 5, SIGABRT: 6, SIGBUS: 7, SIGFPE: 8, SIGKILL: 9, SIGUSR1: 10, SIGSEGV: 11, SIGUSR2: 12, SIGPIPE: 13, SIGALRM: 14, SIGTERM: 15, SIGCHLD: 17, SIGCONT: 18, SIGSTOP: 19, SIGTSTP: 20, SIGTTIN: 21, SIGTTOU: 22, SIGURG: 23, SIGWINCH: 28 };
339
367
  const __substrateSignalNames: Record<number, string> = Object.fromEntries(Object.entries(__substrateSignals).map(([name, number]) => [number, name]));
@@ -584,6 +612,7 @@ export function createProcess(options?: {
584
612
  return true;
585
613
  }
586
614
  if (__substrateIgnoredSignals.has(name)) return true;
615
+ __recordTermination(proc as never, name);
587
616
  proc.exit(128 + __substrateSignals[name]);
588
617
  return true;
589
618
  },