sandboxedjs 0.1.26 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -11
- package/dist/index.cjs +200 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -10
- package/dist/index.d.ts +24 -10
- package/dist/index.js +200 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -196,7 +196,7 @@ interface DirEntry {
|
|
|
196
196
|
/**
|
|
197
197
|
* The container's virtual filesystem.
|
|
198
198
|
*
|
|
199
|
-
* Real file content lives in
|
|
199
|
+
* Real file content lives in the RuntimePod's `MemoryVolume`, deliberately the
|
|
200
200
|
* *same* volume the Node.js worker processes see — so a file written by `echo`
|
|
201
201
|
* is readable by `require('fs')` inside a spawned script, and vice versa.
|
|
202
202
|
*
|
|
@@ -746,7 +746,7 @@ declare function createContext(init: ContextInit): ExecContext;
|
|
|
746
746
|
* The container's network stack.
|
|
747
747
|
*
|
|
748
748
|
* There is no real socket layer: HTTP servers started inside the container are
|
|
749
|
-
* registered with
|
|
749
|
+
* registered with the RuntimePod's request proxy, and this module is the routing and
|
|
750
750
|
* name-resolution layer on top — interfaces for `ip`/`ifconfig`, a hosts file
|
|
751
751
|
* resolver, a listening-port table for `ss`/`netstat`, and an outbound policy
|
|
752
752
|
* that decides whether `curl https://example.com` is allowed to touch the real
|
|
@@ -805,7 +805,7 @@ declare class NetworkStack {
|
|
|
805
805
|
registerListener(port: number, info: Omit<ListeningPort, "port" | "since">): void;
|
|
806
806
|
unregisterListener(port: number): void;
|
|
807
807
|
listening(): ListeningPort[];
|
|
808
|
-
/** Ports
|
|
808
|
+
/** Ports the pod's proxy has registered for this instance. */
|
|
809
809
|
private knownPodPorts;
|
|
810
810
|
/** True when something inside the container answers on `port`. */
|
|
811
811
|
isPortOpen(port: number, timeoutMs?: number): Promise<boolean>;
|
|
@@ -1732,7 +1732,7 @@ declare class Container {
|
|
|
1732
1732
|
/**
|
|
1733
1733
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
1734
1734
|
*
|
|
1735
|
-
*
|
|
1735
|
+
* A RuntimePod's public `request()` may run the body through `toString("utf8")` on
|
|
1736
1736
|
* its way in, so anything above `0x7f` is replaced: a five-byte payload
|
|
1737
1737
|
* containing `0x89` and `0xff` arrives as nine. That silently destroys every
|
|
1738
1738
|
* upload — an image or a video reaches the server the wrong size and no
|
|
@@ -1772,7 +1772,7 @@ declare class Container {
|
|
|
1772
1772
|
get cwd(): string;
|
|
1773
1773
|
get env(): Record<string, string>;
|
|
1774
1774
|
private assertActive;
|
|
1775
|
-
/** Tear down every process and release the
|
|
1775
|
+
/** Tear down every process and release the runtime pod. */
|
|
1776
1776
|
dispose(): void;
|
|
1777
1777
|
get isDisposed(): boolean;
|
|
1778
1778
|
}
|
|
@@ -2114,17 +2114,16 @@ interface RootfsOptions {
|
|
|
2114
2114
|
declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
|
|
2115
2115
|
|
|
2116
2116
|
/**
|
|
2117
|
-
* The Node.js
|
|
2117
|
+
* The Node.js command adapter, backed by the clean-room RuntimePod.
|
|
2118
2118
|
*
|
|
2119
|
-
*
|
|
2119
|
+
* The pod runs the script over the *same* memory volume
|
|
2120
2120
|
* the container's filesystem uses, so `require('fs')` inside a script sees the
|
|
2121
2121
|
* files `echo` and `tar` created, and anything the script writes is visible to
|
|
2122
2122
|
* the shell afterwards.
|
|
2123
2123
|
*
|
|
2124
|
-
* Two
|
|
2124
|
+
* Two details of the underlying `spawn` are handled here:
|
|
2125
2125
|
* - only `node` resolves as a command, so everything else is dispatched by our
|
|
2126
|
-
* own kernel rather than being handed to
|
|
2127
|
-
* unknown command);
|
|
2126
|
+
* own kernel rather than being handed to the Node process runner;
|
|
2128
2127
|
* - the worker's stdin has no end-of-stream signal, so when a pipeline feeds
|
|
2129
2128
|
* a script we materialise stdin as a file and install a real stdin stream
|
|
2130
2129
|
* over it before the script loads.
|
|
@@ -2484,6 +2483,8 @@ declare function createCoreModules(options: CoreModulesOptions): {
|
|
|
2484
2483
|
* script that has simply finished.
|
|
2485
2484
|
*/
|
|
2486
2485
|
pendingHandles(): number;
|
|
2486
|
+
/** Active timers which called `unref()` and therefore only merit startup grace. */
|
|
2487
|
+
pendingUnrefed(): number;
|
|
2487
2488
|
};
|
|
2488
2489
|
|
|
2489
2490
|
interface EsmTransformResult {
|
|
@@ -2570,9 +2571,18 @@ declare class LocalRuntimePod implements RuntimePod {
|
|
|
2570
2571
|
private readonly aliases;
|
|
2571
2572
|
private readonly modules;
|
|
2572
2573
|
private readonly esbuild;
|
|
2574
|
+
private rolldownBinding;
|
|
2573
2575
|
private constructor();
|
|
2574
2576
|
static boot(options?: LocalRuntimeOptions): Promise<LocalRuntimePod>;
|
|
2575
2577
|
spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
|
|
2578
|
+
/**
|
|
2579
|
+
* Rolldown's JavaScript API synchronously requires its compiled binding.
|
|
2580
|
+
* When a project contains Rolldown, preload the official WASI build in the
|
|
2581
|
+
* host and expose it through the module override table before evaluation.
|
|
2582
|
+
* Keeping this demand-driven avoids adding WASM startup cost to ordinary
|
|
2583
|
+
* shells and Node programs.
|
|
2584
|
+
*/
|
|
2585
|
+
private prepareRolldown;
|
|
2576
2586
|
/**
|
|
2577
2587
|
* Wait until the process has either started serving or genuinely run out of
|
|
2578
2588
|
* work.
|
|
@@ -2607,6 +2617,10 @@ interface RegistryManifest {
|
|
|
2607
2617
|
};
|
|
2608
2618
|
dependencies?: Record<string, string>;
|
|
2609
2619
|
optionalDependencies?: Record<string, string>;
|
|
2620
|
+
os?: string[];
|
|
2621
|
+
cpu?: string[];
|
|
2622
|
+
libc?: string[];
|
|
2623
|
+
main?: string;
|
|
2610
2624
|
bin?: string | Record<string, string>;
|
|
2611
2625
|
}
|
|
2612
2626
|
interface CleanInstallerOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -196,7 +196,7 @@ interface DirEntry {
|
|
|
196
196
|
/**
|
|
197
197
|
* The container's virtual filesystem.
|
|
198
198
|
*
|
|
199
|
-
* Real file content lives in
|
|
199
|
+
* Real file content lives in the RuntimePod's `MemoryVolume`, deliberately the
|
|
200
200
|
* *same* volume the Node.js worker processes see — so a file written by `echo`
|
|
201
201
|
* is readable by `require('fs')` inside a spawned script, and vice versa.
|
|
202
202
|
*
|
|
@@ -746,7 +746,7 @@ declare function createContext(init: ContextInit): ExecContext;
|
|
|
746
746
|
* The container's network stack.
|
|
747
747
|
*
|
|
748
748
|
* There is no real socket layer: HTTP servers started inside the container are
|
|
749
|
-
* registered with
|
|
749
|
+
* registered with the RuntimePod's request proxy, and this module is the routing and
|
|
750
750
|
* name-resolution layer on top — interfaces for `ip`/`ifconfig`, a hosts file
|
|
751
751
|
* resolver, a listening-port table for `ss`/`netstat`, and an outbound policy
|
|
752
752
|
* that decides whether `curl https://example.com` is allowed to touch the real
|
|
@@ -805,7 +805,7 @@ declare class NetworkStack {
|
|
|
805
805
|
registerListener(port: number, info: Omit<ListeningPort, "port" | "since">): void;
|
|
806
806
|
unregisterListener(port: number): void;
|
|
807
807
|
listening(): ListeningPort[];
|
|
808
|
-
/** Ports
|
|
808
|
+
/** Ports the pod's proxy has registered for this instance. */
|
|
809
809
|
private knownPodPorts;
|
|
810
810
|
/** True when something inside the container answers on `port`. */
|
|
811
811
|
isPortOpen(port: number, timeoutMs?: number): Promise<boolean>;
|
|
@@ -1732,7 +1732,7 @@ declare class Container {
|
|
|
1732
1732
|
/**
|
|
1733
1733
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
1734
1734
|
*
|
|
1735
|
-
*
|
|
1735
|
+
* A RuntimePod's public `request()` may run the body through `toString("utf8")` on
|
|
1736
1736
|
* its way in, so anything above `0x7f` is replaced: a five-byte payload
|
|
1737
1737
|
* containing `0x89` and `0xff` arrives as nine. That silently destroys every
|
|
1738
1738
|
* upload — an image or a video reaches the server the wrong size and no
|
|
@@ -1772,7 +1772,7 @@ declare class Container {
|
|
|
1772
1772
|
get cwd(): string;
|
|
1773
1773
|
get env(): Record<string, string>;
|
|
1774
1774
|
private assertActive;
|
|
1775
|
-
/** Tear down every process and release the
|
|
1775
|
+
/** Tear down every process and release the runtime pod. */
|
|
1776
1776
|
dispose(): void;
|
|
1777
1777
|
get isDisposed(): boolean;
|
|
1778
1778
|
}
|
|
@@ -2114,17 +2114,16 @@ interface RootfsOptions {
|
|
|
2114
2114
|
declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
|
|
2115
2115
|
|
|
2116
2116
|
/**
|
|
2117
|
-
* The Node.js
|
|
2117
|
+
* The Node.js command adapter, backed by the clean-room RuntimePod.
|
|
2118
2118
|
*
|
|
2119
|
-
*
|
|
2119
|
+
* The pod runs the script over the *same* memory volume
|
|
2120
2120
|
* the container's filesystem uses, so `require('fs')` inside a script sees the
|
|
2121
2121
|
* files `echo` and `tar` created, and anything the script writes is visible to
|
|
2122
2122
|
* the shell afterwards.
|
|
2123
2123
|
*
|
|
2124
|
-
* Two
|
|
2124
|
+
* Two details of the underlying `spawn` are handled here:
|
|
2125
2125
|
* - only `node` resolves as a command, so everything else is dispatched by our
|
|
2126
|
-
* own kernel rather than being handed to
|
|
2127
|
-
* unknown command);
|
|
2126
|
+
* own kernel rather than being handed to the Node process runner;
|
|
2128
2127
|
* - the worker's stdin has no end-of-stream signal, so when a pipeline feeds
|
|
2129
2128
|
* a script we materialise stdin as a file and install a real stdin stream
|
|
2130
2129
|
* over it before the script loads.
|
|
@@ -2484,6 +2483,8 @@ declare function createCoreModules(options: CoreModulesOptions): {
|
|
|
2484
2483
|
* script that has simply finished.
|
|
2485
2484
|
*/
|
|
2486
2485
|
pendingHandles(): number;
|
|
2486
|
+
/** Active timers which called `unref()` and therefore only merit startup grace. */
|
|
2487
|
+
pendingUnrefed(): number;
|
|
2487
2488
|
};
|
|
2488
2489
|
|
|
2489
2490
|
interface EsmTransformResult {
|
|
@@ -2570,9 +2571,18 @@ declare class LocalRuntimePod implements RuntimePod {
|
|
|
2570
2571
|
private readonly aliases;
|
|
2571
2572
|
private readonly modules;
|
|
2572
2573
|
private readonly esbuild;
|
|
2574
|
+
private rolldownBinding;
|
|
2573
2575
|
private constructor();
|
|
2574
2576
|
static boot(options?: LocalRuntimeOptions): Promise<LocalRuntimePod>;
|
|
2575
2577
|
spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
|
|
2578
|
+
/**
|
|
2579
|
+
* Rolldown's JavaScript API synchronously requires its compiled binding.
|
|
2580
|
+
* When a project contains Rolldown, preload the official WASI build in the
|
|
2581
|
+
* host and expose it through the module override table before evaluation.
|
|
2582
|
+
* Keeping this demand-driven avoids adding WASM startup cost to ordinary
|
|
2583
|
+
* shells and Node programs.
|
|
2584
|
+
*/
|
|
2585
|
+
private prepareRolldown;
|
|
2576
2586
|
/**
|
|
2577
2587
|
* Wait until the process has either started serving or genuinely run out of
|
|
2578
2588
|
* work.
|
|
@@ -2607,6 +2617,10 @@ interface RegistryManifest {
|
|
|
2607
2617
|
};
|
|
2608
2618
|
dependencies?: Record<string, string>;
|
|
2609
2619
|
optionalDependencies?: Record<string, string>;
|
|
2620
|
+
os?: string[];
|
|
2621
|
+
cpu?: string[];
|
|
2622
|
+
libc?: string[];
|
|
2623
|
+
main?: string;
|
|
2610
2624
|
bin?: string | Record<string, string>;
|
|
2611
2625
|
}
|
|
2612
2626
|
interface CleanInstallerOptions {
|
package/dist/index.js
CHANGED
|
@@ -6806,7 +6806,7 @@ var NetworkStack = class {
|
|
|
6806
6806
|
}
|
|
6807
6807
|
return [...out.values()].sort((a, b) => a.port - b.port);
|
|
6808
6808
|
}
|
|
6809
|
-
/** Ports
|
|
6809
|
+
/** Ports the pod's proxy has registered for this instance. */
|
|
6810
6810
|
knownPodPorts() {
|
|
6811
6811
|
try {
|
|
6812
6812
|
return this.pod.proxy.activePorts(this.pod.instanceId) ?? [];
|
|
@@ -18163,7 +18163,33 @@ function ffmpegCommands() {
|
|
|
18163
18163
|
return [ffmpeg, ffprobe];
|
|
18164
18164
|
}
|
|
18165
18165
|
var platformBuffer = globalThis.Buffer;
|
|
18166
|
-
var Buffer2 = platformBuffer ?? Buffer$1;
|
|
18166
|
+
var Buffer2 = platformBuffer ?? addBase64UrlSupport(Buffer$1);
|
|
18167
|
+
function addBase64UrlSupport(BufferClass) {
|
|
18168
|
+
const target = BufferClass;
|
|
18169
|
+
if (target.__sandboxedBase64Url) return BufferClass;
|
|
18170
|
+
Object.defineProperty(target, "__sandboxedBase64Url", { value: true });
|
|
18171
|
+
const from = target.from.bind(target);
|
|
18172
|
+
target.from = (value, encodingOrOffset, length) => from(value, normalizeEncoding(encodingOrOffset), length);
|
|
18173
|
+
const byteLength = target.byteLength.bind(target);
|
|
18174
|
+
target.byteLength = (value, encoding) => byteLength(value, normalizeEncoding(encoding));
|
|
18175
|
+
const isEncoding = target.isEncoding?.bind(target);
|
|
18176
|
+
if (isEncoding) target.isEncoding = (encoding) => encoding.toLowerCase() === "base64url" || isEncoding(encoding);
|
|
18177
|
+
const toString = target.prototype.toString;
|
|
18178
|
+
target.prototype.toString = function(encoding, start2, end) {
|
|
18179
|
+
if (encoding?.toLowerCase() !== "base64url") return toString.call(this, encoding, start2, end);
|
|
18180
|
+
return toString.call(this, "base64", start2, end).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
18181
|
+
};
|
|
18182
|
+
const write = target.prototype.write;
|
|
18183
|
+
target.prototype.write = function(...args) {
|
|
18184
|
+
const index = typeof args[1] === "string" ? 1 : typeof args[2] === "string" ? 2 : 3;
|
|
18185
|
+
if (typeof args[index] === "string") args[index] = normalizeEncoding(args[index]);
|
|
18186
|
+
return write.apply(this, args);
|
|
18187
|
+
};
|
|
18188
|
+
return BufferClass;
|
|
18189
|
+
}
|
|
18190
|
+
function normalizeEncoding(value) {
|
|
18191
|
+
return typeof value === "string" && value.toLowerCase() === "base64url" ? "base64" : value;
|
|
18192
|
+
}
|
|
18167
18193
|
|
|
18168
18194
|
// src/pkg/clean-installer.ts
|
|
18169
18195
|
init_path();
|
|
@@ -18205,6 +18231,9 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
18205
18231
|
const version = resolveVersion(metadata, range);
|
|
18206
18232
|
const manifest = metadata.versions[version];
|
|
18207
18233
|
if (!manifest) throw new Error(`No matching version found for ${name}@${range}`);
|
|
18234
|
+
if (!supportsPlatform(manifest)) {
|
|
18235
|
+
throw new Error(`${name}@${version} is not compatible with linux/x64/glibc`);
|
|
18236
|
+
}
|
|
18208
18237
|
const identity = `${name}@${version}`;
|
|
18209
18238
|
const target = join(modulesRoot, name);
|
|
18210
18239
|
const installed2 = this.tryReadJson(join(target, "package.json"));
|
|
@@ -18299,6 +18328,15 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
|
|
|
18299
18328
|
}
|
|
18300
18329
|
}
|
|
18301
18330
|
};
|
|
18331
|
+
function supportsPlatform(manifest) {
|
|
18332
|
+
return !manifest.main?.endsWith(".node") && platformListAllows(manifest.os, "linux") && platformListAllows(manifest.cpu, "x64") && platformListAllows(manifest.libc, "glibc");
|
|
18333
|
+
}
|
|
18334
|
+
function platformListAllows(values, current) {
|
|
18335
|
+
if (!values?.length) return true;
|
|
18336
|
+
if (values.includes(`!${current}`)) return false;
|
|
18337
|
+
const positive = values.filter((value) => !value.startsWith("!"));
|
|
18338
|
+
return positive.length === 0 || positive.includes(current) || positive.includes("any");
|
|
18339
|
+
}
|
|
18302
18340
|
function resolveVersion(metadata, range) {
|
|
18303
18341
|
const tag2 = metadata["dist-tags"]?.[range];
|
|
18304
18342
|
if (tag2) return tag2;
|
|
@@ -22259,6 +22297,7 @@ function createCoreModules(options) {
|
|
|
22259
22297
|
globals,
|
|
22260
22298
|
process: processObject,
|
|
22261
22299
|
pendingHandles: timers.pending,
|
|
22300
|
+
pendingUnrefed: timers.pendingUnrefed,
|
|
22262
22301
|
writeStdin: (data) => {
|
|
22263
22302
|
if (options.interactiveStdin) stdin.write(data);
|
|
22264
22303
|
},
|
|
@@ -22854,33 +22893,78 @@ var ERRNO_CONSTANTS = {
|
|
|
22854
22893
|
};
|
|
22855
22894
|
function createTrackedTimers() {
|
|
22856
22895
|
const live = /* @__PURE__ */ new Set();
|
|
22857
|
-
const
|
|
22896
|
+
const unrefed = /* @__PURE__ */ new Set();
|
|
22897
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
22898
|
+
const track = (native) => {
|
|
22899
|
+
const state = { native, active: true, referenced: true };
|
|
22900
|
+
const handle = {
|
|
22901
|
+
ref() {
|
|
22902
|
+
state.referenced = true;
|
|
22903
|
+
unrefed.delete(handle);
|
|
22904
|
+
if (state.active) live.add(handle);
|
|
22905
|
+
state.native?.ref?.();
|
|
22906
|
+
return handle;
|
|
22907
|
+
},
|
|
22908
|
+
unref() {
|
|
22909
|
+
state.referenced = false;
|
|
22910
|
+
live.delete(handle);
|
|
22911
|
+
if (state.active) unrefed.add(handle);
|
|
22912
|
+
state.native?.unref?.();
|
|
22913
|
+
return handle;
|
|
22914
|
+
},
|
|
22915
|
+
hasRef: () => state.referenced,
|
|
22916
|
+
refresh() {
|
|
22917
|
+
state.native?.refresh?.();
|
|
22918
|
+
return handle;
|
|
22919
|
+
},
|
|
22920
|
+
[Symbol.toPrimitive]: () => Number(state.native)
|
|
22921
|
+
};
|
|
22922
|
+
states.set(handle, state);
|
|
22858
22923
|
live.add(handle);
|
|
22859
22924
|
return handle;
|
|
22860
22925
|
};
|
|
22926
|
+
const complete = (handle) => {
|
|
22927
|
+
const state = states.get(handle);
|
|
22928
|
+
if (state) state.active = false;
|
|
22929
|
+
live.delete(handle);
|
|
22930
|
+
unrefed.delete(handle);
|
|
22931
|
+
};
|
|
22861
22932
|
const setTimeoutTracked = (fn, delay, ...args) => {
|
|
22862
|
-
|
|
22933
|
+
let handle;
|
|
22934
|
+
const native = setTimeout(
|
|
22863
22935
|
(...inner) => {
|
|
22864
|
-
|
|
22936
|
+
complete(handle);
|
|
22865
22937
|
fn(...inner);
|
|
22866
22938
|
},
|
|
22867
22939
|
delay,
|
|
22868
22940
|
...args
|
|
22869
22941
|
);
|
|
22870
|
-
|
|
22942
|
+
handle = track(native);
|
|
22871
22943
|
return handle;
|
|
22872
22944
|
};
|
|
22873
22945
|
const setIntervalTracked = (fn, delay, ...args) => track(setInterval(fn, delay, ...args));
|
|
22874
22946
|
const hostSetImmediate = globalThis.setImmediate;
|
|
22875
22947
|
const setImmediateTracked = (fn, ...args) => {
|
|
22876
|
-
|
|
22877
|
-
|
|
22948
|
+
if (!hostSetImmediate) return setTimeoutTracked(fn, 0, ...args);
|
|
22949
|
+
let handle;
|
|
22950
|
+
const native = hostSetImmediate((...inner) => {
|
|
22951
|
+
complete(handle);
|
|
22878
22952
|
fn(...inner);
|
|
22879
|
-
}, ...args)
|
|
22880
|
-
|
|
22953
|
+
}, ...args);
|
|
22954
|
+
handle = track(native);
|
|
22881
22955
|
return handle;
|
|
22882
22956
|
};
|
|
22883
22957
|
const clear2 = (handle, native) => {
|
|
22958
|
+
if (typeof handle === "object" && handle !== null) {
|
|
22959
|
+
const state = states.get(handle);
|
|
22960
|
+
if (state) {
|
|
22961
|
+
state.active = false;
|
|
22962
|
+
live.delete(handle);
|
|
22963
|
+
unrefed.delete(handle);
|
|
22964
|
+
native(state.native);
|
|
22965
|
+
return;
|
|
22966
|
+
}
|
|
22967
|
+
}
|
|
22884
22968
|
live.delete(handle);
|
|
22885
22969
|
native(handle);
|
|
22886
22970
|
};
|
|
@@ -22893,7 +22977,8 @@ function createTrackedTimers() {
|
|
|
22893
22977
|
clearInterval: (handle) => clear2(handle, clearInterval),
|
|
22894
22978
|
clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
|
|
22895
22979
|
},
|
|
22896
|
-
pending: () => live.size
|
|
22980
|
+
pending: () => live.size,
|
|
22981
|
+
pendingUnrefed: () => unrefed.size
|
|
22897
22982
|
};
|
|
22898
22983
|
}
|
|
22899
22984
|
function createTimerPromises() {
|
|
@@ -23415,7 +23500,26 @@ function ensureProcessGlobal() {
|
|
|
23415
23500
|
});
|
|
23416
23501
|
}
|
|
23417
23502
|
|
|
23503
|
+
// src/runtime/host-rolldown.ts
|
|
23504
|
+
async function loadHostRolldownBinding() {
|
|
23505
|
+
if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
|
|
23506
|
+
throw new Error(
|
|
23507
|
+
"Vite 8/Rolldown requires cross-origin isolation. Serve the app with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers."
|
|
23508
|
+
);
|
|
23509
|
+
}
|
|
23510
|
+
try {
|
|
23511
|
+
const loaded = await import('@rolldown/binding-wasm32-wasi');
|
|
23512
|
+
return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
|
|
23513
|
+
} catch (error) {
|
|
23514
|
+
if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
|
|
23515
|
+
console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
|
|
23516
|
+
}
|
|
23517
|
+
throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
|
|
23518
|
+
}
|
|
23519
|
+
}
|
|
23520
|
+
|
|
23418
23521
|
// src/runtime/local-runtime-pod.ts
|
|
23522
|
+
init_path();
|
|
23419
23523
|
var WASM_ALIASES = {
|
|
23420
23524
|
esbuild: "esbuild-wasm",
|
|
23421
23525
|
rollup: "@rollup/wasm-node"
|
|
@@ -23666,6 +23770,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
23666
23770
|
aliases;
|
|
23667
23771
|
modules;
|
|
23668
23772
|
esbuild;
|
|
23773
|
+
rolldownBinding;
|
|
23669
23774
|
constructor(options) {
|
|
23670
23775
|
ensureProcessGlobal();
|
|
23671
23776
|
this.workdir = options.workdir ?? "/";
|
|
@@ -23699,6 +23804,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
23699
23804
|
const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
|
|
23700
23805
|
const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
|
|
23701
23806
|
return new LocalProcess(async (proc) => {
|
|
23807
|
+
await this.prepareRolldown(cwd, env2);
|
|
23702
23808
|
const untrack = trackProcess(proc);
|
|
23703
23809
|
let requestedExit = 0;
|
|
23704
23810
|
let engine;
|
|
@@ -23735,7 +23841,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
23735
23841
|
});
|
|
23736
23842
|
try {
|
|
23737
23843
|
await engine.run(script);
|
|
23738
|
-
await this.settle(owner, core.pendingHandles, core.readingStdin);
|
|
23844
|
+
await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin);
|
|
23739
23845
|
if (this.router.activePorts(owner).length) {
|
|
23740
23846
|
await proc.waitForKill();
|
|
23741
23847
|
return 137;
|
|
@@ -23747,6 +23853,25 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
23747
23853
|
}
|
|
23748
23854
|
});
|
|
23749
23855
|
}
|
|
23856
|
+
/**
|
|
23857
|
+
* Rolldown's JavaScript API synchronously requires its compiled binding.
|
|
23858
|
+
* When a project contains Rolldown, preload the official WASI build in the
|
|
23859
|
+
* host and expose it through the module override table before evaluation.
|
|
23860
|
+
* Keeping this demand-driven avoids adding WASM startup cost to ordinary
|
|
23861
|
+
* shells and Node programs.
|
|
23862
|
+
*/
|
|
23863
|
+
async prepareRolldown(cwd, env2) {
|
|
23864
|
+
const specifier = "@rolldown/binding-wasm32-wasi";
|
|
23865
|
+
if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
|
|
23866
|
+
this.rolldownBinding ??= loadHostRolldownBinding();
|
|
23867
|
+
const binding = await this.rolldownBinding;
|
|
23868
|
+
if (binding) this.modules[specifier] = binding;
|
|
23869
|
+
}
|
|
23870
|
+
if (this.modules[specifier]) {
|
|
23871
|
+
syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
|
|
23872
|
+
env2.NAPI_RS_FORCE_WASI ??= "true";
|
|
23873
|
+
}
|
|
23874
|
+
}
|
|
23750
23875
|
/**
|
|
23751
23876
|
* Wait until the process has either started serving or genuinely run out of
|
|
23752
23877
|
* work.
|
|
@@ -23762,11 +23887,17 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
23762
23887
|
* A plain script that has genuinely finished falls straight through both,
|
|
23763
23888
|
* costing a handful of empty turns.
|
|
23764
23889
|
*/
|
|
23765
|
-
async settle(owner, pendingHandles, readingStdin) {
|
|
23890
|
+
async settle(owner, pendingHandles, pendingUnrefed, readingStdin) {
|
|
23766
23891
|
for (let turn = 0; turn < DRAIN_TURNS; turn++) {
|
|
23767
23892
|
if (this.router.activePorts(owner).length) return;
|
|
23768
23893
|
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
23769
23894
|
}
|
|
23895
|
+
if (pendingHandles() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
|
|
23896
|
+
const deadline = Date.now() + 1e3;
|
|
23897
|
+
while (!this.router.activePorts(owner).length && Date.now() < deadline) {
|
|
23898
|
+
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
23899
|
+
}
|
|
23900
|
+
}
|
|
23770
23901
|
while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || readingStdin())) {
|
|
23771
23902
|
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
23772
23903
|
}
|
|
@@ -23819,6 +23950,60 @@ function formatError(error) {
|
|
|
23819
23950
|
function isRecord(value) {
|
|
23820
23951
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23821
23952
|
}
|
|
23953
|
+
function packageInstalled(volume, cwd, wanted) {
|
|
23954
|
+
for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
|
|
23955
|
+
if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
|
|
23956
|
+
if (dir3 === "/") return false;
|
|
23957
|
+
}
|
|
23958
|
+
}
|
|
23959
|
+
function packageTreeContains(volume, modulesRoot, wanted, visited) {
|
|
23960
|
+
if (visited.has(modulesRoot) || !directory(volume, modulesRoot)) return false;
|
|
23961
|
+
visited.add(modulesRoot);
|
|
23962
|
+
if (directory(volume, join(modulesRoot, wanted))) return true;
|
|
23963
|
+
for (const entry of volume.readdirSync(modulesRoot)) {
|
|
23964
|
+
if (entry === ".bin") continue;
|
|
23965
|
+
const first = join(modulesRoot, entry);
|
|
23966
|
+
const packages = entry.startsWith("@") && directory(volume, first) ? volume.readdirSync(first).map((name) => join(first, name)) : [first];
|
|
23967
|
+
for (const root of packages) {
|
|
23968
|
+
if (directory(volume, root) && packageTreeContains(volume, join(root, "node_modules"), wanted, visited)) {
|
|
23969
|
+
return true;
|
|
23970
|
+
}
|
|
23971
|
+
}
|
|
23972
|
+
}
|
|
23973
|
+
return false;
|
|
23974
|
+
}
|
|
23975
|
+
function directory(volume, path) {
|
|
23976
|
+
try {
|
|
23977
|
+
return volume.lstatSync(path).isDirectory();
|
|
23978
|
+
} catch {
|
|
23979
|
+
return false;
|
|
23980
|
+
}
|
|
23981
|
+
}
|
|
23982
|
+
function syncRolldownFileSystem(binding, volume, root) {
|
|
23983
|
+
const fs = binding?.__fs;
|
|
23984
|
+
if (!fs?.mkdirSync || !fs?.writeFileSync) return;
|
|
23985
|
+
try {
|
|
23986
|
+
fs.rmSync?.(root, { recursive: true, force: true });
|
|
23987
|
+
} catch {
|
|
23988
|
+
}
|
|
23989
|
+
const copy = (path) => {
|
|
23990
|
+
const stat2 = volume.lstatSync(path);
|
|
23991
|
+
if (stat2.isDirectory()) {
|
|
23992
|
+
fs.mkdirSync(path, { recursive: true });
|
|
23993
|
+
for (const name of volume.readdirSync(path)) copy(join(path, name));
|
|
23994
|
+
} else if (stat2.isSymbolicLink()) {
|
|
23995
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
23996
|
+
try {
|
|
23997
|
+
fs.symlinkSync(volume.readlinkSync(path), path);
|
|
23998
|
+
} catch {
|
|
23999
|
+
}
|
|
24000
|
+
} else if (!path.endsWith(".node")) {
|
|
24001
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
24002
|
+
fs.writeFileSync(path, volume.readFileSync(path));
|
|
24003
|
+
}
|
|
24004
|
+
};
|
|
24005
|
+
copy(clean(root));
|
|
24006
|
+
}
|
|
23822
24007
|
|
|
23823
24008
|
// src/container/container.ts
|
|
23824
24009
|
var Container = class _Container {
|
|
@@ -24162,7 +24347,7 @@ var Container = class _Container {
|
|
|
24162
24347
|
/**
|
|
24163
24348
|
* Deliver a request whose body is bytes, without letting them become text.
|
|
24164
24349
|
*
|
|
24165
|
-
*
|
|
24350
|
+
* A RuntimePod's public `request()` may run the body through `toString("utf8")` on
|
|
24166
24351
|
* its way in, so anything above `0x7f` is replaced: a five-byte payload
|
|
24167
24352
|
* containing `0x89` and `0xff` arrives as nine. That silently destroys every
|
|
24168
24353
|
* upload — an image or a video reaches the server the wrong size and no
|
|
@@ -24264,7 +24449,7 @@ var Container = class _Container {
|
|
|
24264
24449
|
assertActive() {
|
|
24265
24450
|
if (this.disposed) throw new Error("container has been disposed");
|
|
24266
24451
|
}
|
|
24267
|
-
/** Tear down every process and release the
|
|
24452
|
+
/** Tear down every process and release the runtime pod. */
|
|
24268
24453
|
dispose() {
|
|
24269
24454
|
if (this.disposed) return;
|
|
24270
24455
|
this.disposed = true;
|