@volter/tabnode 0.5.15 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{runtime-worker-C5pP4aT7.js → runtime-worker-DCIwIWHJ.js} +6596 -3015
- package/dist/code-transforms.d.ts +2 -1
- package/dist/index.mjs +6581 -2993
- package/dist/node-lib/binding/fs.d.ts +10 -0
- package/dist/node-lib/binding/zlib.d.ts +14 -1
- package/dist/shims/perf_hooks.d.ts +0 -4
- package/dist/shims/tls.d.ts +3 -1
- package/dist/shims/worker_threads.d.ts +4 -0
- package/package.json +1 -1
- package/src/code-transforms.ts +28 -13
- package/src/node-lib/binding/fs.ts +34 -0
- package/src/node-lib/binding/process_wrap.ts +3 -1
- package/src/node-lib/binding/zlib.ts +80 -3
- package/src/node-lib/internals/runtime.ts +6 -2
- package/src/runtime.ts +59 -4
- package/src/shims/child_process.ts +70 -6
- package/src/shims/esbuild.ts +12 -1
- package/src/shims/perf_hooks.ts +18 -0
- package/src/shims/string_decoder.ts +5 -0
- package/src/shims/tls.ts +13 -1
- package/src/shims/worker_threads.ts +19 -0
|
@@ -936,8 +936,10 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
936
936
|
// engine's own registries. A run that still holds a handle or a timer
|
|
937
937
|
// is not that -- Node's loop runs while one is open, for as long as it
|
|
938
938
|
// is open -- and cutting it at a minute ended a forked child that was
|
|
939
|
-
// a server, which is every extension host and every pty host.
|
|
940
|
-
|
|
939
|
+
// a server, which is every extension host and every pty host. Work the
|
|
940
|
+
// host holds for it (a build, a pre-bundle) is seen work too: a dev
|
|
941
|
+
// server whose start passed a minute mid pre-bundle was cut with exit 0.
|
|
942
|
+
if (!isLongRunning && !stillWorking() && heldWork().count === 0 && Date.now() - startTime >= MAX_TOTAL_MS) break;
|
|
941
943
|
}
|
|
942
944
|
|
|
943
945
|
return { stdout, stderr, exitCode: exitCalled ? exitCode : (typeof proc !== 'undefined' && typeof proc.exitCode === 'number' ? proc.exitCode : 0) };
|
|
@@ -1011,6 +1013,10 @@ export function initChildProcess(vfs: VirtualFS): void {
|
|
|
1011
1013
|
case 'ls':
|
|
1012
1014
|
case 'list':
|
|
1013
1015
|
return handleNpmList(ctx);
|
|
1016
|
+
case 'init':
|
|
1017
|
+
case 'create':
|
|
1018
|
+
case 'innit':
|
|
1019
|
+
return handleNpmInit(args.slice(1), ctx);
|
|
1014
1020
|
default:
|
|
1015
1021
|
return {
|
|
1016
1022
|
stdout: '',
|
|
@@ -1289,6 +1295,46 @@ async function handleNpmInstall(args: string[], ctx: CommandContext): Promise<Ju
|
|
|
1289
1295
|
}
|
|
1290
1296
|
}
|
|
1291
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* `npm init -y`: npm's defaults written to package.json, the fields a
|
|
1300
|
+
* package.json already has kept, and the file printed as npm prints it. The
|
|
1301
|
+
* questionnaire without `-y` needs a person at npm's prompts; it is refused by
|
|
1302
|
+
* name, as is an initializer (`npm init <pkg>`), which runs another package.
|
|
1303
|
+
*/
|
|
1304
|
+
async function handleNpmInit(args: string[], ctx: CommandContext): Promise<JustBashExecResult> {
|
|
1305
|
+
const yes = args.includes('-y') || args.includes('--yes');
|
|
1306
|
+
const initializer = args.find((a) => !a.startsWith('-'));
|
|
1307
|
+
if (initializer) {
|
|
1308
|
+
return { stdout: '', stderr: `npm ERR! npm init ${initializer} runs an initializer package, which this engine's npm does not; write package.json, or run \`npm init -y\`.\n`, exitCode: 1 };
|
|
1309
|
+
}
|
|
1310
|
+
if (!yes) {
|
|
1311
|
+
return { stdout: '', stderr: 'npm ERR! npm init asks its questions at a prompt, which this engine\'s npm does not; `npm init -y` writes the defaults.\n', exitCode: 1 };
|
|
1312
|
+
}
|
|
1313
|
+
const vfs = currentVfs!;
|
|
1314
|
+
const file = __resolvePath(ctx.cwd, 'package.json');
|
|
1315
|
+
let existing: Record<string, unknown> = {};
|
|
1316
|
+
if (vfs.existsSync(file)) {
|
|
1317
|
+
try { existing = JSON.parse(String(vfs.readFileSync(file, 'utf8'))) as Record<string, unknown>; }
|
|
1318
|
+
catch { return { stdout: '', stderr: `npm ERR! ${file} is not valid JSON\n`, exitCode: 1 }; }
|
|
1319
|
+
}
|
|
1320
|
+
const base = ctx.cwd.split('/').filter(Boolean).pop() ?? 'package';
|
|
1321
|
+
const name = base.toLowerCase().replace(/^[._]+/, '').replace(/[^a-z0-9._~-]+/g, '-') || 'package';
|
|
1322
|
+
const manifest = {
|
|
1323
|
+
name,
|
|
1324
|
+
version: '1.0.0',
|
|
1325
|
+
description: '',
|
|
1326
|
+
main: 'index.js',
|
|
1327
|
+
scripts: { test: 'echo "Error: no test specified" && exit 1' },
|
|
1328
|
+
keywords: [],
|
|
1329
|
+
author: '',
|
|
1330
|
+
license: 'ISC',
|
|
1331
|
+
...existing,
|
|
1332
|
+
};
|
|
1333
|
+
const text = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
1334
|
+
vfs.writeFileSync(file, text);
|
|
1335
|
+
return { stdout: `Wrote to ${file}:\n\n${text}\n\n`, stderr: '', exitCode: 0 };
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1292
1338
|
/**
|
|
1293
1339
|
* Handle `npm ls` — list installed packages
|
|
1294
1340
|
*/
|
|
@@ -1711,6 +1757,15 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1711
1757
|
const admittedNode = nodeProcessHostInstalled() && engineProgramFor(request.file, request.cwd) === 'node';
|
|
1712
1758
|
const hostTerminal = request.terminal !== undefined && hostExecutor() !== null && !admittedNode;
|
|
1713
1759
|
let wakeInput: (() => void) | undefined;
|
|
1760
|
+
/**
|
|
1761
|
+
* A piped fd 0 that stays open for a program the host runs (a program pack,
|
|
1762
|
+
* not a builtin of the engine's shell): what the parent writes reaches it
|
|
1763
|
+
* as it is written, through the same live stream a terminal gets. A
|
|
1764
|
+
* builtin reads its input once, before it runs; a server that speaks
|
|
1765
|
+
* JSON-RPC over stdio (`supercode harness serve`) read that one string,
|
|
1766
|
+
* saw EOF and exited before its client's first request.
|
|
1767
|
+
*/
|
|
1768
|
+
let liveInput = false;
|
|
1714
1769
|
const hostInput: AsyncIterable<Uint8Array> = {
|
|
1715
1770
|
async *[Symbol.asyncIterator]() {
|
|
1716
1771
|
while (!finished && !controller.signal.aborted) {
|
|
@@ -1810,6 +1865,12 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1810
1865
|
const begin = (): void => {
|
|
1811
1866
|
if (started || finished) return;
|
|
1812
1867
|
started = true;
|
|
1868
|
+
const engineFirst = !hostTerminal && programExists(request.file, request.cwd, request.env);
|
|
1869
|
+
liveInput = !hostTerminal && !admittedNode && request.stdinIsPipe && !engineFirst;
|
|
1870
|
+
if (liveInput) {
|
|
1871
|
+
pendingStdin.unshift(...initialStdin);
|
|
1872
|
+
initialStdin.length = 0;
|
|
1873
|
+
}
|
|
1813
1874
|
const stdin = initialStdin.length > 0 ? Buffer.concat(initialStdin).toString('utf8') : undefined;
|
|
1814
1875
|
void (async () => {
|
|
1815
1876
|
let outcome: CommandOutcome;
|
|
@@ -1823,11 +1884,11 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1823
1884
|
filesystem: currentVfs!, env, streams, ...(stdin !== undefined ? { stdin } : {}),
|
|
1824
1885
|
}) : await enterRun(token, () => routeCommand({
|
|
1825
1886
|
command: __substrateLineFor(request.file, request.args, request.cwd),
|
|
1826
|
-
engineFirst
|
|
1887
|
+
engineFirst,
|
|
1827
1888
|
cwd: request.cwd,
|
|
1828
1889
|
env: { ...request.env, [PROCESS_TOKEN_ENV]: token },
|
|
1829
1890
|
stdin,
|
|
1830
|
-
...(hostTerminal ? { stdinStream: hostInput, terminal: request.terminal } : {}),
|
|
1891
|
+
...(hostTerminal ? { stdinStream: hostInput, terminal: request.terminal } : liveInput ? { stdinStream: hostInput } : {}),
|
|
1831
1892
|
signal: controller.signal,
|
|
1832
1893
|
onStdout: streams.onStdout,
|
|
1833
1894
|
onStderr: streams.onStderr,
|
|
@@ -1868,18 +1929,20 @@ function startChildRun(request: RunRequest): StartedRun {
|
|
|
1868
1929
|
return 0;
|
|
1869
1930
|
},
|
|
1870
1931
|
writeStdin(bytes: Uint8Array): void | Promise<void> {
|
|
1871
|
-
if (hostTerminal) { pendingStdin.push(bytes); wakeInput?.(); wakeInput = undefined; return; }
|
|
1932
|
+
if (hostTerminal || liveInput) { pendingStdin.push(bytes); wakeInput?.(); wakeInput = undefined; return; }
|
|
1872
1933
|
if (!started) { initialStdin.push(bytes); return; }
|
|
1873
1934
|
pendingStdin.push(bytes);
|
|
1874
1935
|
return flushStdin();
|
|
1875
1936
|
},
|
|
1876
1937
|
endStdin(): void {
|
|
1877
|
-
if (hostTerminal) { pendingStdin.push(null); wakeInput?.(); wakeInput = undefined; return; }
|
|
1938
|
+
if (hostTerminal || liveInput) { pendingStdin.push(null); wakeInput?.(); wakeInput = undefined; return; }
|
|
1878
1939
|
if (!started) {
|
|
1879
1940
|
// The writer closed before the command began: the run's fd 0 is the
|
|
1880
1941
|
// string already collected and nothing more, so it is not held open.
|
|
1881
1942
|
streams.stdinOpen = false;
|
|
1882
1943
|
begin();
|
|
1944
|
+
// A live pipe closed before it began still ends where it was closed.
|
|
1945
|
+
if (liveInput) { pendingStdin.push(null); wakeInput?.(); wakeInput = undefined; }
|
|
1883
1946
|
return;
|
|
1884
1947
|
}
|
|
1885
1948
|
pendingStdin.push(null);
|
|
@@ -1907,3 +1970,4 @@ export default {
|
|
|
1907
1970
|
runCommand,
|
|
1908
1971
|
sendStdin,
|
|
1909
1972
|
};
|
|
1973
|
+
|
package/src/shims/esbuild.ts
CHANGED
|
@@ -398,6 +398,16 @@ export interface EsbuildHost {
|
|
|
398
398
|
}
|
|
399
399
|
let currentHost: EsbuildHost | null = null;
|
|
400
400
|
|
|
401
|
+
/**
|
|
402
|
+
* esbuild-wasm starts its compiler on a Worker unless told not to. A realm
|
|
403
|
+
* that runs guests may withhold `Worker` (the execution worker confines its
|
|
404
|
+
* guests that way), and there the compiler runs on the calling thread, which
|
|
405
|
+
* is esbuild's own `worker: false`.
|
|
406
|
+
*/
|
|
407
|
+
function realmCanStartWorker(): boolean {
|
|
408
|
+
return typeof (globalThis as { Worker?: unknown }).Worker === 'function';
|
|
409
|
+
}
|
|
410
|
+
|
|
401
411
|
let inlineEsbuild: Promise<typeof import('esbuild-wasm')> | null = null;
|
|
402
412
|
function ownEsbuild(): Promise<typeof import('esbuild-wasm')> {
|
|
403
413
|
// A build that brings plugins of its own, a bundler such as tsup does,
|
|
@@ -408,7 +418,7 @@ function ownEsbuild(): Promise<typeof import('esbuild-wasm')> {
|
|
|
408
418
|
/* @vite-ignore */
|
|
409
419
|
esbuildModuleURL
|
|
410
420
|
).then(async (esbuild) => {
|
|
411
|
-
await esbuild.initialize({ wasmURL });
|
|
421
|
+
await esbuild.initialize({ wasmURL, worker: realmCanStartWorker() });
|
|
412
422
|
return esbuild;
|
|
413
423
|
}).catch((cause) => {
|
|
414
424
|
// A load that failed, a page whose server was away for a moment, is not kept: the next build tries again.
|
|
@@ -527,6 +537,7 @@ export async function initialize(options?: { wasmURL?: string }): Promise<void>
|
|
|
527
537
|
|
|
528
538
|
await esbuild.initialize({
|
|
529
539
|
wasmURL: options?.wasmURL || wasmURL,
|
|
540
|
+
worker: realmCanStartWorker(),
|
|
530
541
|
});
|
|
531
542
|
|
|
532
543
|
esbuildInstance = esbuild;
|
package/src/shims/perf_hooks.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* perf_hooks shim - Performance measurement APIs
|
|
3
3
|
* Wraps browser Performance API
|
|
4
4
|
*/
|
|
5
|
+
import { forGuestRealm } from '../host-globals';
|
|
5
6
|
|
|
6
7
|
export const performance = globalThis.performance || {
|
|
7
8
|
now: () => Date.now(),
|
|
@@ -16,6 +17,23 @@ export const performance = globalThis.performance || {
|
|
|
16
17
|
clearResourceTimings: () => {},
|
|
17
18
|
};
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Node's `performance.markResourceTiming`, which a browser's `performance`
|
|
22
|
+
* does not have. undici's fetch reads it off the global `performance` at load
|
|
23
|
+
* and calls it after every response, so each fetch through an undici a
|
|
24
|
+
* program installed (Pi's does) threw once the body was read. Node records a
|
|
25
|
+
* PerformanceResourceTiming entry there; this engine keeps no resource
|
|
26
|
+
* timeline for a guest's own HTTP, so the call records nothing.
|
|
27
|
+
*/
|
|
28
|
+
forGuestRealm(() => {
|
|
29
|
+
const own = globalThis.performance as (Performance & { markResourceTiming?: unknown }) | undefined;
|
|
30
|
+
if (own && typeof own.markResourceTiming !== 'function') {
|
|
31
|
+
try {
|
|
32
|
+
Object.defineProperty(own, 'markResourceTiming', { value: () => undefined, writable: true, configurable: true, enumerable: false });
|
|
33
|
+
} catch { /* a performance object that refuses the name keeps its own shape */ }
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
19
37
|
export class PerformanceObserver {
|
|
20
38
|
private callback: (list: PerformanceObserverEntryList) => void;
|
|
21
39
|
private entryTypes: string[] = [];
|
|
@@ -308,6 +308,7 @@ function base64End(this: StringDecoder, buf?: unknown): string {
|
|
|
308
308
|
// ---------------------------------------------------------------------------
|
|
309
309
|
|
|
310
310
|
function simpleWrite(this: StringDecoder, buf: unknown): string {
|
|
311
|
+
if (typeof buf === 'string') return buf;
|
|
311
312
|
return decoderInput(this, buf).toString(this.encoding as BufferEncoding);
|
|
312
313
|
}
|
|
313
314
|
|
|
@@ -353,6 +354,10 @@ const decoderPrototype = StringDecoderImpl.prototype as unknown as StringDecoder
|
|
|
353
354
|
|
|
354
355
|
decoderPrototype.write = function write(this: unknown, buffer: unknown): string {
|
|
355
356
|
const self = assertDecoder(this);
|
|
357
|
+
// Node returns a string as it is given: readline decodes every chunk of its
|
|
358
|
+
// input, and an input stream with an encoding (`createReadStream(path,
|
|
359
|
+
// 'utf8')`) hands it strings.
|
|
360
|
+
if (typeof buffer === 'string') return buffer;
|
|
356
361
|
const buf = decoderInput(self, buffer);
|
|
357
362
|
if (buf.length === 0) return '';
|
|
358
363
|
let r: string | undefined;
|
package/src/shims/tls.ts
CHANGED
|
@@ -102,6 +102,17 @@ const DEFAULT_MIN_VERSION = 'TLSv1.2';
|
|
|
102
102
|
|
|
103
103
|
const rootCertificates: string[] = [];
|
|
104
104
|
|
|
105
|
+
// Node's certificate-store door, read by callers that build their own agents
|
|
106
|
+
// (VS Code's proxy agent, on every fetch it patches). The engine holds no
|
|
107
|
+
// store: certificates are the browser's, so every kind answers the same empty
|
|
108
|
+
// set `rootCertificates` does.
|
|
109
|
+
const getCACertificates = (type: string = 'default'): string[] => {
|
|
110
|
+
if (!['default', 'system', 'bundled', 'extra'].includes(type)) {
|
|
111
|
+
throw Object.assign(new TypeError(`The argument 'type' must be one of: 'default', 'system', 'bundled', 'extra'. Received '${String(type)}'`), { code: 'ERR_INVALID_ARG_VALUE' });
|
|
112
|
+
}
|
|
113
|
+
return [];
|
|
114
|
+
};
|
|
115
|
+
|
|
105
116
|
return {
|
|
106
117
|
TLSSocket,
|
|
107
118
|
Server,
|
|
@@ -109,6 +120,7 @@ return {
|
|
|
109
120
|
connect,
|
|
110
121
|
createSecureContext,
|
|
111
122
|
getCiphers,
|
|
123
|
+
getCACertificates,
|
|
112
124
|
DEFAULT_ECDH_CURVE,
|
|
113
125
|
DEFAULT_MAX_VERSION,
|
|
114
126
|
DEFAULT_MIN_VERSION,
|
|
@@ -117,7 +129,7 @@ return {
|
|
|
117
129
|
|
|
118
130
|
}
|
|
119
131
|
const tls = createTlsModule();
|
|
120
|
-
export const { TLSSocket, Server, createServer, connect, createSecureContext, getCiphers,
|
|
132
|
+
export const { TLSSocket, Server, createServer, connect, createSecureContext, getCiphers, getCACertificates,
|
|
121
133
|
DEFAULT_ECDH_CURVE, DEFAULT_MAX_VERSION, DEFAULT_MIN_VERSION, rootCertificates } = tls;
|
|
122
134
|
export type TLSSocket = InstanceType<typeof TLSSocket>;
|
|
123
135
|
export type Server = InstanceType<typeof Server>;
|
|
@@ -88,6 +88,23 @@ export function markAsUntransferable(object: unknown): void {
|
|
|
88
88
|
// No-op
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Node 22's `markAsUncloneable` and its query: an object so marked is refused
|
|
93
|
+
* by `structuredClone` and `postMessage`. undici's WebIDL layer marks every
|
|
94
|
+
* `CacheStorage`, `Request` and `Response` it makes, at construction, so a
|
|
95
|
+
* program that loads undici 7.x+ or 8.x died on `markAsUncloneable is not a
|
|
96
|
+
* function` before its first line. The mark is kept; this engine's message
|
|
97
|
+
* channels do not yet read it, so a marked object that is posted is copied
|
|
98
|
+
* rather than refused.
|
|
99
|
+
*/
|
|
100
|
+
const uncloneable = new WeakSet<object>();
|
|
101
|
+
export function markAsUncloneable(object: unknown): void {
|
|
102
|
+
if (object !== null && (typeof object === 'object' || typeof object === 'function')) uncloneable.add(object as object);
|
|
103
|
+
}
|
|
104
|
+
export function isMarkedAsUncloneable(object: unknown): boolean {
|
|
105
|
+
return object !== null && (typeof object === 'object' || typeof object === 'function') && uncloneable.has(object as object);
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
export function getEnvironmentData(key: unknown): unknown {
|
|
92
109
|
return undefined;
|
|
93
110
|
}
|
|
@@ -109,6 +126,8 @@ export default {
|
|
|
109
126
|
receiveMessageOnPort,
|
|
110
127
|
SHARE_ENV,
|
|
111
128
|
markAsUntransferable,
|
|
129
|
+
markAsUncloneable,
|
|
130
|
+
isMarkedAsUncloneable,
|
|
112
131
|
getEnvironmentData,
|
|
113
132
|
setEnvironmentData,
|
|
114
133
|
};
|