sandboxedjs 0.1.28 → 0.1.29
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 +52 -7
- package/dist/index.cjs +904 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -8
- package/dist/index.d.ts +198 -8
- package/dist/index.js +903 -53
- package/dist/index.js.map +1 -1
- package/dist/worker-entry.js +30822 -0
- package/dist/worker-entry.js.map +1 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17959,22 +17959,22 @@ async function runCPythonProgram(ctx, source, argv, scriptDir, stdinText) {
|
|
|
17959
17959
|
});
|
|
17960
17960
|
} catch {
|
|
17961
17961
|
}
|
|
17962
|
-
const
|
|
17963
|
-
const
|
|
17962
|
+
const encoder8 = new TextEncoder();
|
|
17963
|
+
const decoder8 = new TextDecoder();
|
|
17964
17964
|
py.setStdout({
|
|
17965
17965
|
write: (buffer) => {
|
|
17966
|
-
ctx.write(
|
|
17966
|
+
ctx.write(decoder8.decode(buffer));
|
|
17967
17967
|
return buffer.length;
|
|
17968
17968
|
}
|
|
17969
17969
|
});
|
|
17970
17970
|
py.setStderr({
|
|
17971
17971
|
write: (buffer) => {
|
|
17972
|
-
ctx.stderr.write(
|
|
17972
|
+
ctx.stderr.write(decoder8.decode(buffer));
|
|
17973
17973
|
return buffer.length;
|
|
17974
17974
|
}
|
|
17975
17975
|
});
|
|
17976
17976
|
if (stdinText !== null) {
|
|
17977
|
-
const bytes2 =
|
|
17977
|
+
const bytes2 = encoder8.encode(stdinText);
|
|
17978
17978
|
let offset = 0;
|
|
17979
17979
|
py.setStdin({
|
|
17980
17980
|
read: (buffer) => {
|
|
@@ -18025,9 +18025,9 @@ async function runCPythonRepl(ctx) {
|
|
|
18025
18025
|
}
|
|
18026
18026
|
mountContainerDirs(py, ctx);
|
|
18027
18027
|
bootstrap(py, ctx, [""], null);
|
|
18028
|
-
const
|
|
18029
|
-
py.setStdout({ write: (data) => (ctx.write(
|
|
18030
|
-
py.setStderr({ write: (data) => (ctx.stderr.write(
|
|
18028
|
+
const decoder8 = new TextDecoder();
|
|
18029
|
+
py.setStdout({ write: (data) => (ctx.write(decoder8.decode(data)), data.length) });
|
|
18030
|
+
py.setStderr({ write: (data) => (ctx.stderr.write(decoder8.decode(data)), data.length) });
|
|
18031
18031
|
ctx.line(`Python ${String(py.runPython("import sys; sys.version.split()[0]"))} (Pyodide)`);
|
|
18032
18032
|
ctx.line('Type "help()" for more information.');
|
|
18033
18033
|
let source = "";
|
|
@@ -19638,15 +19638,15 @@ var Session = class {
|
|
|
19638
19638
|
async run(command, opts = {}) {
|
|
19639
19639
|
if (this.closed) throw new Error("session is closed");
|
|
19640
19640
|
const combined = [];
|
|
19641
|
-
const
|
|
19641
|
+
const decoder8 = new TextDecoder();
|
|
19642
19642
|
const stdout = new BufferSink((chunk) => {
|
|
19643
|
-
const text2 =
|
|
19643
|
+
const text2 = decoder8.decode(chunk, { stream: true });
|
|
19644
19644
|
combined.push(text2);
|
|
19645
19645
|
opts.onStdout?.(text2);
|
|
19646
19646
|
this.hooks.onStdout?.(text2);
|
|
19647
19647
|
});
|
|
19648
19648
|
const stderr = new BufferSink((chunk) => {
|
|
19649
|
-
const text2 =
|
|
19649
|
+
const text2 = decoder8.decode(chunk, { stream: true });
|
|
19650
19650
|
combined.push(text2);
|
|
19651
19651
|
opts.onStderr?.(text2);
|
|
19652
19652
|
this.hooks.onStderr?.(text2);
|
|
@@ -21832,7 +21832,7 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
|
|
|
21832
21832
|
};
|
|
21833
21833
|
let settled = false;
|
|
21834
21834
|
try {
|
|
21835
|
-
const response =
|
|
21835
|
+
const response = isLoopback(this.target.hostname) ? await this.viaRouter(body) : await this.viaFetch(body);
|
|
21836
21836
|
if (this.destroyedByUser) return;
|
|
21837
21837
|
clearTimeout(this.timer);
|
|
21838
21838
|
settled = true;
|
|
@@ -21854,6 +21854,12 @@ var VirtualClientRequest = class extends streamModule4__default.default.Writable
|
|
|
21854
21854
|
}
|
|
21855
21855
|
}
|
|
21856
21856
|
async viaRouter(body) {
|
|
21857
|
+
if (!this.router.activePortsIncludes(this.target.port)) {
|
|
21858
|
+
throw Object.assign(
|
|
21859
|
+
new Error(`connect ECONNREFUSED ${this.target.hostname}:${this.target.port}`),
|
|
21860
|
+
{ code: "ECONNREFUSED", errno: -61, syscall: "connect", address: this.target.hostname, port: this.target.port }
|
|
21861
|
+
);
|
|
21862
|
+
}
|
|
21857
21863
|
const result = await this.router.request(this.target.port, {
|
|
21858
21864
|
method: this.target.method,
|
|
21859
21865
|
path: this.target.path,
|
|
@@ -22110,7 +22116,7 @@ var ChildProcess = class extends EventEmitter4__default.default {
|
|
|
22110
22116
|
queueMicrotask(() => this.emit("close", code, null));
|
|
22111
22117
|
}
|
|
22112
22118
|
};
|
|
22113
|
-
function createChildProcessModule(spawnChild, defaultCwd) {
|
|
22119
|
+
function createChildProcessModule(spawnChild, defaultCwd, syncSpawn) {
|
|
22114
22120
|
const throughShell = (command, options) => {
|
|
22115
22121
|
const shell = typeof options.shell === "string" ? options.shell : "/bin/sh";
|
|
22116
22122
|
return { file: shell, args: ["-c", command] };
|
|
@@ -22167,20 +22173,79 @@ ${err.join("")}`),
|
|
|
22167
22173
|
exec,
|
|
22168
22174
|
execFile,
|
|
22169
22175
|
fork: (modulePath, args = [], options = {}) => spawn("node", [modulePath, ...args], options),
|
|
22170
|
-
|
|
22171
|
-
execFileSync: unavailable("execFileSync"),
|
|
22172
|
-
spawnSync: unavailable("spawnSync"),
|
|
22176
|
+
...buildSyncFamily(syncSpawn, throughShell, defaultCwd),
|
|
22173
22177
|
ChildProcess
|
|
22174
22178
|
};
|
|
22175
22179
|
}
|
|
22180
|
+
function buildSyncFamily(syncSpawn, throughShell, defaultCwd) {
|
|
22181
|
+
if (!syncSpawn) {
|
|
22182
|
+
return {
|
|
22183
|
+
execSync: unavailable("execSync"),
|
|
22184
|
+
execFileSync: unavailable("execFileSync"),
|
|
22185
|
+
spawnSync: unavailable("spawnSync")
|
|
22186
|
+
};
|
|
22187
|
+
}
|
|
22188
|
+
const run = (file3, args, options) => {
|
|
22189
|
+
const resolved = options.shell ? throughShell([file3, ...args].join(" "), options) : { file: file3, args };
|
|
22190
|
+
const input = options.input === void 0 ? void 0 : typeof options.input === "string" ? options.input : new TextDecoder().decode(options.input);
|
|
22191
|
+
return syncSpawn({
|
|
22192
|
+
command: resolved.file,
|
|
22193
|
+
args: resolved.args,
|
|
22194
|
+
cwd: options.cwd ?? defaultCwd(),
|
|
22195
|
+
...options.env ? { env: options.env } : {},
|
|
22196
|
+
...input === void 0 ? {} : { input }
|
|
22197
|
+
});
|
|
22198
|
+
};
|
|
22199
|
+
const asOutput = (text2, options) => options.encoding === "buffer" || options.encoding === void 0 ? Buffer2.from(text2) : text2;
|
|
22200
|
+
const orThrow = (result, command, options) => {
|
|
22201
|
+
if (result.error) {
|
|
22202
|
+
throw Object.assign(new Error(result.error.message), {
|
|
22203
|
+
...result.error.code ? { code: result.error.code } : {},
|
|
22204
|
+
stdout: asOutput(result.stdout, options),
|
|
22205
|
+
stderr: asOutput(result.stderr, options)
|
|
22206
|
+
});
|
|
22207
|
+
}
|
|
22208
|
+
if (result.status !== 0) {
|
|
22209
|
+
throw Object.assign(new Error(`Command failed: ${command}
|
|
22210
|
+
${result.stderr}`), {
|
|
22211
|
+
status: result.status,
|
|
22212
|
+
stdout: asOutput(result.stdout, options),
|
|
22213
|
+
stderr: asOutput(result.stderr, options)
|
|
22214
|
+
});
|
|
22215
|
+
}
|
|
22216
|
+
return asOutput(result.stdout, options);
|
|
22217
|
+
};
|
|
22218
|
+
const spawnSync = (file3, args = [], options = {}) => {
|
|
22219
|
+
const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
|
|
22220
|
+
const result = run(file3, list, opts);
|
|
22221
|
+
return {
|
|
22222
|
+
pid: 0,
|
|
22223
|
+
status: result.status,
|
|
22224
|
+
signal: result.signal,
|
|
22225
|
+
stdout: asOutput(result.stdout, opts),
|
|
22226
|
+
stderr: asOutput(result.stderr, opts),
|
|
22227
|
+
output: [null, asOutput(result.stdout, opts), asOutput(result.stderr, opts)],
|
|
22228
|
+
...result.error ? { error: Object.assign(new Error(result.error.message), result.error.code ? { code: result.error.code } : {}) } : {}
|
|
22229
|
+
};
|
|
22230
|
+
};
|
|
22231
|
+
const execFileSync = (file3, args = [], options = {}) => {
|
|
22232
|
+
const [list, opts] = Array.isArray(args) ? [args, options] : [[], args];
|
|
22233
|
+
return orThrow(run(file3, list, opts), [file3, ...list].join(" "), opts);
|
|
22234
|
+
};
|
|
22235
|
+
const execSync = (command, options = {}) => orThrow(run(command, [], { ...options, shell: options.shell ?? true }), command, options);
|
|
22236
|
+
return { spawnSync, execFileSync, execSync };
|
|
22237
|
+
}
|
|
22176
22238
|
function normalize2(options, callback) {
|
|
22177
22239
|
if (typeof options === "function") return [{}, options];
|
|
22178
22240
|
return [options ?? {}, callback];
|
|
22179
22241
|
}
|
|
22180
22242
|
function unavailable(name) {
|
|
22181
|
-
return () => {
|
|
22243
|
+
return (...args) => {
|
|
22244
|
+
const file3 = typeof args[0] === "string" ? args[0] : void 0;
|
|
22245
|
+
const list = Array.isArray(args[1]) ? args[1].map(String) : [];
|
|
22246
|
+
const command = file3 ? [file3, ...list].join(" ") : void 0;
|
|
22182
22247
|
const error = new Error(
|
|
22183
|
-
`child_process.${name} is not supported in the sandboxed runtime
|
|
22248
|
+
`child_process.${name} is not supported in the sandboxed runtime` + (command ? ` (tried to run: ${command})` : "") + `: the caller, the child and the event loop share one thread, so blocking the caller would also stop the child. Run it with the asynchronous form (spawn/exec/execFile), or run the command from the container shell.`
|
|
22184
22249
|
);
|
|
22185
22250
|
error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
22186
22251
|
throw error;
|
|
@@ -22244,7 +22309,11 @@ function parseKeys(input) {
|
|
|
22244
22309
|
const name = CSI_KEYS[sequence];
|
|
22245
22310
|
keys.push({
|
|
22246
22311
|
sequence: `\x1B${sequence}`,
|
|
22247
|
-
|
|
22312
|
+
/* Node leaves this undefined for a sequence it does not recognise.
|
|
22313
|
+
* The string "undefined" is not the same thing: a caller testing
|
|
22314
|
+
* `key.name === undefined` would take an unknown key for a known
|
|
22315
|
+
* one. */
|
|
22316
|
+
name,
|
|
22248
22317
|
ctrl: false,
|
|
22249
22318
|
/* xterm reports modifiers as `;5` (control) and `;2` (shift) before
|
|
22250
22319
|
* the final letter. */
|
|
@@ -22804,7 +22873,7 @@ function createCoreModules(options) {
|
|
|
22804
22873
|
};
|
|
22805
22874
|
const http = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "http:") : createUnsupportedModule("http");
|
|
22806
22875
|
const https = options.http ? createHttpModule(options.http.router, options.http.owner, httpOptions, "https:") : createUnsupportedModule("https");
|
|
22807
|
-
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd) : createUnsupportedModule("child_process");
|
|
22876
|
+
const childProcess = options.spawnChild ? createChildProcessModule(options.spawnChild, () => cwd, options.syncSpawn) : createUnsupportedModule("child_process");
|
|
22808
22877
|
const readline = createReadlineModule(() => processObject.stdin, () => processObject.stdout);
|
|
22809
22878
|
const dns = createDnsModule();
|
|
22810
22879
|
const builtins = {
|
|
@@ -22865,6 +22934,7 @@ function createCoreModules(options) {
|
|
|
22865
22934
|
pendingUnrefed: timers.pendingUnrefed,
|
|
22866
22935
|
/** Client requests sent but not yet read to completion. */
|
|
22867
22936
|
pendingRequests: () => inFlightRequests,
|
|
22937
|
+
cancelTimers: timers.cancelAll,
|
|
22868
22938
|
writeStdin: (data) => {
|
|
22869
22939
|
if (options.interactiveStdin) stdin.write(data);
|
|
22870
22940
|
},
|
|
@@ -23546,6 +23616,27 @@ function createTrackedTimers() {
|
|
|
23546
23616
|
live.delete(handle);
|
|
23547
23617
|
native(handle);
|
|
23548
23618
|
};
|
|
23619
|
+
const cancelAll = () => {
|
|
23620
|
+
for (const handle of [...live, ...unrefed]) {
|
|
23621
|
+
const state = states.get(handle);
|
|
23622
|
+
if (!state) continue;
|
|
23623
|
+
state.active = false;
|
|
23624
|
+
try {
|
|
23625
|
+
clearTimeout(state.native);
|
|
23626
|
+
} catch {
|
|
23627
|
+
}
|
|
23628
|
+
try {
|
|
23629
|
+
clearInterval(state.native);
|
|
23630
|
+
} catch {
|
|
23631
|
+
}
|
|
23632
|
+
try {
|
|
23633
|
+
(globalThis.clearImmediate ?? clearTimeout)(state.native);
|
|
23634
|
+
} catch {
|
|
23635
|
+
}
|
|
23636
|
+
}
|
|
23637
|
+
live.clear();
|
|
23638
|
+
unrefed.clear();
|
|
23639
|
+
};
|
|
23549
23640
|
return {
|
|
23550
23641
|
api: {
|
|
23551
23642
|
setTimeout: setTimeoutTracked,
|
|
@@ -23556,7 +23647,8 @@ function createTrackedTimers() {
|
|
|
23556
23647
|
clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
|
|
23557
23648
|
},
|
|
23558
23649
|
pending: () => live.size,
|
|
23559
|
-
pendingUnrefed: () => unrefed.size
|
|
23650
|
+
pendingUnrefed: () => unrefed.size,
|
|
23651
|
+
cancelAll
|
|
23560
23652
|
};
|
|
23561
23653
|
}
|
|
23562
23654
|
function createTimerPromises() {
|
|
@@ -24010,6 +24102,193 @@ var MemoryVolume = class {
|
|
|
24010
24102
|
}
|
|
24011
24103
|
};
|
|
24012
24104
|
|
|
24105
|
+
// src/runtime/mirroring-volume.ts
|
|
24106
|
+
init_path();
|
|
24107
|
+
var MirroringVolume = class {
|
|
24108
|
+
constructor(inner = new MemoryVolume()) {
|
|
24109
|
+
this.inner = inner;
|
|
24110
|
+
}
|
|
24111
|
+
inner;
|
|
24112
|
+
mirror;
|
|
24113
|
+
root = "/";
|
|
24114
|
+
/**
|
|
24115
|
+
* Start mirroring the tree under `root`, seeding it with what is there now.
|
|
24116
|
+
*
|
|
24117
|
+
* Called once the Rolldown binding is known to be in play; before that a
|
|
24118
|
+
* container pays nothing for this.
|
|
24119
|
+
*/
|
|
24120
|
+
attach(mirror, root) {
|
|
24121
|
+
this.mirror = mirror;
|
|
24122
|
+
this.root = clean(root);
|
|
24123
|
+
this.seed();
|
|
24124
|
+
}
|
|
24125
|
+
detach() {
|
|
24126
|
+
this.mirror = void 0;
|
|
24127
|
+
}
|
|
24128
|
+
/** Copy the whole subtree across. The only bulk operation that remains. */
|
|
24129
|
+
seed() {
|
|
24130
|
+
const mirror = this.mirror;
|
|
24131
|
+
if (!mirror) return;
|
|
24132
|
+
try {
|
|
24133
|
+
mirror.rmSync?.(this.root, { recursive: true, force: true });
|
|
24134
|
+
} catch {
|
|
24135
|
+
}
|
|
24136
|
+
const copy = (path) => {
|
|
24137
|
+
let stat2;
|
|
24138
|
+
try {
|
|
24139
|
+
stat2 = this.inner.lstatSync(path);
|
|
24140
|
+
} catch {
|
|
24141
|
+
return;
|
|
24142
|
+
}
|
|
24143
|
+
if (stat2.isDirectory()) {
|
|
24144
|
+
this.safely(() => mirror.mkdirSync(path, { recursive: true }));
|
|
24145
|
+
for (const name of this.inner.readdirSync(path)) copy(join(path, name));
|
|
24146
|
+
} else if (stat2.isSymbolicLink()) {
|
|
24147
|
+
this.safely(() => {
|
|
24148
|
+
mirror.mkdirSync(dirname(path), { recursive: true });
|
|
24149
|
+
mirror.symlinkSync(this.inner.readlinkSync(path), path);
|
|
24150
|
+
});
|
|
24151
|
+
} else if (!path.endsWith(".node")) {
|
|
24152
|
+
this.safely(() => {
|
|
24153
|
+
mirror.mkdirSync(dirname(path), { recursive: true });
|
|
24154
|
+
mirror.writeFileSync(path, this.inner.readFileSync(path));
|
|
24155
|
+
});
|
|
24156
|
+
}
|
|
24157
|
+
};
|
|
24158
|
+
copy(this.root);
|
|
24159
|
+
}
|
|
24160
|
+
/** Is this path inside the mirrored subtree? */
|
|
24161
|
+
mirrored(path) {
|
|
24162
|
+
if (!this.mirror) return false;
|
|
24163
|
+
const clean2 = clean(path);
|
|
24164
|
+
return this.root === "/" || clean2 === this.root || clean2.startsWith(`${this.root}/`);
|
|
24165
|
+
}
|
|
24166
|
+
/**
|
|
24167
|
+
* Run a mirror update, swallowing failure.
|
|
24168
|
+
*
|
|
24169
|
+
* The mirror is a read-only cache for another engine. If it rejects
|
|
24170
|
+
* something — an unsupported operation, a path it has not seen — the
|
|
24171
|
+
* container's own write has still happened and must still succeed.
|
|
24172
|
+
*/
|
|
24173
|
+
safely(update) {
|
|
24174
|
+
try {
|
|
24175
|
+
update();
|
|
24176
|
+
} catch {
|
|
24177
|
+
}
|
|
24178
|
+
}
|
|
24179
|
+
/** Push a path's current state across, whatever it is now. */
|
|
24180
|
+
sync(path) {
|
|
24181
|
+
const mirror = this.mirror;
|
|
24182
|
+
if (!mirror || !this.mirrored(path)) return;
|
|
24183
|
+
let stat2;
|
|
24184
|
+
try {
|
|
24185
|
+
stat2 = this.inner.lstatSync(path);
|
|
24186
|
+
} catch {
|
|
24187
|
+
this.safely(() => {
|
|
24188
|
+
if (mirror.rmSync) mirror.rmSync(path, { recursive: true, force: true });
|
|
24189
|
+
else mirror.unlinkSync?.(path);
|
|
24190
|
+
});
|
|
24191
|
+
return;
|
|
24192
|
+
}
|
|
24193
|
+
if (stat2.isDirectory()) {
|
|
24194
|
+
this.safely(() => mirror.mkdirSync(path, { recursive: true }));
|
|
24195
|
+
return;
|
|
24196
|
+
}
|
|
24197
|
+
if (stat2.isSymbolicLink()) {
|
|
24198
|
+
this.safely(() => {
|
|
24199
|
+
mirror.mkdirSync(dirname(path), { recursive: true });
|
|
24200
|
+
mirror.symlinkSync(this.inner.readlinkSync(path), path);
|
|
24201
|
+
});
|
|
24202
|
+
return;
|
|
24203
|
+
}
|
|
24204
|
+
if (path.endsWith(".node")) return;
|
|
24205
|
+
this.safely(() => {
|
|
24206
|
+
mirror.mkdirSync(dirname(path), { recursive: true });
|
|
24207
|
+
mirror.writeFileSync(path, this.inner.readFileSync(path));
|
|
24208
|
+
});
|
|
24209
|
+
}
|
|
24210
|
+
// ── reads: straight through ───────────────────────────────────────────────
|
|
24211
|
+
readFileSync(path) {
|
|
24212
|
+
return this.inner.readFileSync(path);
|
|
24213
|
+
}
|
|
24214
|
+
readdirSync(path) {
|
|
24215
|
+
return this.inner.readdirSync(path);
|
|
24216
|
+
}
|
|
24217
|
+
lstatSync(path) {
|
|
24218
|
+
return this.inner.lstatSync(path);
|
|
24219
|
+
}
|
|
24220
|
+
readlinkSync(path) {
|
|
24221
|
+
return this.inner.readlinkSync(path);
|
|
24222
|
+
}
|
|
24223
|
+
getStats() {
|
|
24224
|
+
return this.inner.getStats();
|
|
24225
|
+
}
|
|
24226
|
+
// ── writes: applied, then mirrored ────────────────────────────────────────
|
|
24227
|
+
writeFileSync(path, data) {
|
|
24228
|
+
this.inner.writeFileSync(path, data);
|
|
24229
|
+
this.sync(path);
|
|
24230
|
+
}
|
|
24231
|
+
appendFileSync(path, data) {
|
|
24232
|
+
this.inner.appendFileSync(path, data);
|
|
24233
|
+
this.sync(path);
|
|
24234
|
+
}
|
|
24235
|
+
mkdirSync(path, options) {
|
|
24236
|
+
this.inner.mkdirSync(path, options);
|
|
24237
|
+
this.sync(path);
|
|
24238
|
+
}
|
|
24239
|
+
rmdirSync(path) {
|
|
24240
|
+
this.inner.rmdirSync(path);
|
|
24241
|
+
this.sync(path);
|
|
24242
|
+
}
|
|
24243
|
+
unlinkSync(path) {
|
|
24244
|
+
this.inner.unlinkSync(path);
|
|
24245
|
+
this.sync(path);
|
|
24246
|
+
}
|
|
24247
|
+
renameSync(from, to) {
|
|
24248
|
+
this.inner.renameSync(from, to);
|
|
24249
|
+
this.sync(from);
|
|
24250
|
+
this.sync(to);
|
|
24251
|
+
}
|
|
24252
|
+
symlinkSync(target, path) {
|
|
24253
|
+
this.inner.symlinkSync(target, path);
|
|
24254
|
+
this.sync(path);
|
|
24255
|
+
}
|
|
24256
|
+
linkSync(existing, path) {
|
|
24257
|
+
this.inner.linkSync(existing, path);
|
|
24258
|
+
this.sync(path);
|
|
24259
|
+
}
|
|
24260
|
+
truncateSync(path, length) {
|
|
24261
|
+
this.inner.truncateSync(path, length);
|
|
24262
|
+
this.sync(path);
|
|
24263
|
+
}
|
|
24264
|
+
/* Permissions and timestamps do not change what a bundler resolves, and
|
|
24265
|
+
* `memfs` is stricter about them than this volume is. Applied here only. */
|
|
24266
|
+
chmodSync(path, mode) {
|
|
24267
|
+
this.inner.chmodSync(path, mode);
|
|
24268
|
+
}
|
|
24269
|
+
lchmodSync(path, mode) {
|
|
24270
|
+
this.inner.lchmodSync(path, mode);
|
|
24271
|
+
}
|
|
24272
|
+
chownSync(path, uid, gid) {
|
|
24273
|
+
this.inner.chownSync(path, uid, gid);
|
|
24274
|
+
}
|
|
24275
|
+
lchownSync(path, uid, gid) {
|
|
24276
|
+
this.inner.lchownSync(path, uid, gid);
|
|
24277
|
+
}
|
|
24278
|
+
utimesSync(path, atime, mtime) {
|
|
24279
|
+
this.inner.utimesSync(path, atime, mtime);
|
|
24280
|
+
}
|
|
24281
|
+
// ── snapshots ─────────────────────────────────────────────────────────────
|
|
24282
|
+
snapshot() {
|
|
24283
|
+
return this.inner.snapshot();
|
|
24284
|
+
}
|
|
24285
|
+
/** A restore replaces everything, so the mirror is rebuilt rather than patched. */
|
|
24286
|
+
restore(entries) {
|
|
24287
|
+
this.inner.restore(entries);
|
|
24288
|
+
this.seed();
|
|
24289
|
+
}
|
|
24290
|
+
};
|
|
24291
|
+
|
|
24013
24292
|
// src/runtime/host-esbuild.ts
|
|
24014
24293
|
var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
|
|
24015
24294
|
function createHostEsbuild() {
|
|
@@ -24079,6 +24358,7 @@ function ensureProcessGlobal() {
|
|
|
24079
24358
|
}
|
|
24080
24359
|
|
|
24081
24360
|
// src/runtime/host-rolldown.ts
|
|
24361
|
+
var BUNDLER_HINT = "If this is a bundler pre-bundling the worker away, exclude the binding from dependency optimisation \u2014 in Vite:\n\n optimizeDeps: { exclude: ['@rolldown/binding-wasm32-wasi'] }";
|
|
24082
24362
|
async function loadHostRolldownBinding() {
|
|
24083
24363
|
if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
|
|
24084
24364
|
throw new Error(
|
|
@@ -24092,7 +24372,13 @@ async function loadHostRolldownBinding() {
|
|
|
24092
24372
|
if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
|
|
24093
24373
|
console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
|
|
24094
24374
|
}
|
|
24095
|
-
|
|
24375
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
24376
|
+
throw new Error(
|
|
24377
|
+
`The optional Rolldown WASI binding could not be loaded: ${reason}` + (typeof window === "undefined" ? "" : `
|
|
24378
|
+
|
|
24379
|
+
${BUNDLER_HINT}`),
|
|
24380
|
+
{ cause: error }
|
|
24381
|
+
);
|
|
24096
24382
|
}
|
|
24097
24383
|
}
|
|
24098
24384
|
|
|
@@ -24132,6 +24418,7 @@ var LocalProcess = class extends EventEmitter4__default.default {
|
|
|
24132
24418
|
inputEnded = false;
|
|
24133
24419
|
pendingInput = [];
|
|
24134
24420
|
resolveKilled;
|
|
24421
|
+
cleanup;
|
|
24135
24422
|
killedPromise = new Promise((resolve2) => {
|
|
24136
24423
|
this.resolveKilled = resolve2;
|
|
24137
24424
|
});
|
|
@@ -24170,13 +24457,32 @@ var LocalProcess = class extends EventEmitter4__default.default {
|
|
|
24170
24457
|
for (const chunk of this.pendingInput.splice(0)) input(chunk);
|
|
24171
24458
|
if (this.inputEnded) end();
|
|
24172
24459
|
}
|
|
24460
|
+
/**
|
|
24461
|
+
* End the process now.
|
|
24462
|
+
*
|
|
24463
|
+
* Killing has to settle `completion`, not merely record the intent. A
|
|
24464
|
+
* program with an interval outstanding has work pending forever, so the
|
|
24465
|
+
* runtime's own idle check will never end it — before this, killing such a
|
|
24466
|
+
* process left the caller awaiting a promise that could not resolve.
|
|
24467
|
+
*/
|
|
24173
24468
|
kill(_signal = "SIGTERM") {
|
|
24469
|
+
if (this.killed) return;
|
|
24174
24470
|
this.killed = true;
|
|
24175
24471
|
this.resolveKilled();
|
|
24472
|
+
this.cleanup?.();
|
|
24473
|
+
this.finish(137);
|
|
24176
24474
|
}
|
|
24177
24475
|
waitForKill() {
|
|
24178
24476
|
return this.killedPromise;
|
|
24179
24477
|
}
|
|
24478
|
+
isKilled() {
|
|
24479
|
+
return this.killed;
|
|
24480
|
+
}
|
|
24481
|
+
/** Registered by the task so a kill can release what the program still holds. */
|
|
24482
|
+
onKill(cleanup) {
|
|
24483
|
+
this.cleanup = cleanup;
|
|
24484
|
+
if (this.killed) cleanup();
|
|
24485
|
+
}
|
|
24180
24486
|
/** End the process now, for an asynchronous `process.exit`. */
|
|
24181
24487
|
exitNow(code) {
|
|
24182
24488
|
this.finish(code);
|
|
@@ -24338,7 +24644,10 @@ function removeEscapedErrorReporting() {
|
|
|
24338
24644
|
}
|
|
24339
24645
|
var DRAIN_TURNS = 4;
|
|
24340
24646
|
var LocalRuntimePod = class _LocalRuntimePod {
|
|
24341
|
-
|
|
24647
|
+
/* Wrapped so that a foreign filesystem — Rolldown's WebAssembly memfs — can
|
|
24648
|
+
* be kept in step as writes happen, rather than deep-copied once per spawn.
|
|
24649
|
+
* With nothing attached this is a straight pass-through. */
|
|
24650
|
+
volume = new MirroringVolume(new MemoryVolume());
|
|
24342
24651
|
packages;
|
|
24343
24652
|
instanceId = `sbx-${Math.random().toString(36).slice(2)}`;
|
|
24344
24653
|
router = new VirtualHttpRouter();
|
|
@@ -24429,6 +24738,10 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24429
24738
|
onRawMode: (enabled) => proc.emit("rawmode", enabled)
|
|
24430
24739
|
});
|
|
24431
24740
|
proc.acceptInput((data) => core.writeStdin(data), () => core.endStdin());
|
|
24741
|
+
proc.onKill(() => {
|
|
24742
|
+
core.cancelTimers();
|
|
24743
|
+
this.router.closeOwner(owner);
|
|
24744
|
+
});
|
|
24432
24745
|
engine = new CommonJsEngine(this.volume, {
|
|
24433
24746
|
cwd,
|
|
24434
24747
|
builtins: core.builtins,
|
|
@@ -24438,7 +24751,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24438
24751
|
});
|
|
24439
24752
|
try {
|
|
24440
24753
|
await engine.run(script);
|
|
24441
|
-
await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests);
|
|
24754
|
+
await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin, core.pendingRequests, () => proc.isKilled());
|
|
24442
24755
|
if (this.router.activePorts(owner).length) {
|
|
24443
24756
|
await proc.waitForKill();
|
|
24444
24757
|
return 137;
|
|
@@ -24459,13 +24772,13 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24459
24772
|
*/
|
|
24460
24773
|
async prepareRolldown(cwd, env2) {
|
|
24461
24774
|
const specifier = "@rolldown/binding-wasm32-wasi";
|
|
24462
|
-
if (!this.modules[specifier] &&
|
|
24775
|
+
if (!this.modules[specifier] && packageIsInstalled(this.volume, cwd, "rolldown")) {
|
|
24463
24776
|
this.rolldownBinding ??= loadHostRolldownBinding();
|
|
24464
24777
|
const binding = await this.rolldownBinding;
|
|
24465
24778
|
if (binding) this.modules[specifier] = binding;
|
|
24466
24779
|
}
|
|
24467
24780
|
if (this.modules[specifier]) {
|
|
24468
|
-
|
|
24781
|
+
attachRolldownMirror(this.modules[specifier], this.volume, cwd);
|
|
24469
24782
|
env2.NAPI_RS_FORCE_WASI ??= "true";
|
|
24470
24783
|
}
|
|
24471
24784
|
}
|
|
@@ -24484,18 +24797,18 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
24484
24797
|
* A plain script that has genuinely finished falls straight through both,
|
|
24485
24798
|
* costing a handful of empty turns.
|
|
24486
24799
|
*/
|
|
24487
|
-
async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests) {
|
|
24800
|
+
async settle(owner, pendingHandles, pendingUnrefed, readingStdin, pendingRequests, killed) {
|
|
24488
24801
|
for (let turn = 0; turn < DRAIN_TURNS; turn++) {
|
|
24489
|
-
if (this.router.activePorts(owner).length) return;
|
|
24802
|
+
if (this.router.activePorts(owner).length || killed()) return;
|
|
24490
24803
|
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
24491
24804
|
}
|
|
24492
24805
|
if (pendingHandles() === 0 && pendingRequests() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
|
|
24493
24806
|
const deadline = Date.now() + 1e3;
|
|
24494
|
-
while (!this.router.activePorts(owner).length && Date.now() < deadline) {
|
|
24807
|
+
while (!this.router.activePorts(owner).length && !killed() && Date.now() < deadline) {
|
|
24495
24808
|
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
24496
24809
|
}
|
|
24497
24810
|
}
|
|
24498
|
-
while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
|
|
24811
|
+
while (!this.router.activePorts(owner).length && !killed() && (pendingHandles() > 0 || pendingRequests() > 0 || readingStdin())) {
|
|
24499
24812
|
await new Promise((resolve2) => setTimeout(resolve2, 5));
|
|
24500
24813
|
}
|
|
24501
24814
|
}
|
|
@@ -24547,7 +24860,7 @@ function formatError(error) {
|
|
|
24547
24860
|
function isRecord(value) {
|
|
24548
24861
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24549
24862
|
}
|
|
24550
|
-
function
|
|
24863
|
+
function packageIsInstalled(volume, cwd, wanted) {
|
|
24551
24864
|
for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
|
|
24552
24865
|
if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
|
|
24553
24866
|
if (dir3 === "/") return false;
|
|
@@ -24576,30 +24889,565 @@ function directory(volume, path) {
|
|
|
24576
24889
|
return false;
|
|
24577
24890
|
}
|
|
24578
24891
|
}
|
|
24579
|
-
function
|
|
24892
|
+
function attachRolldownMirror(binding, volume, root) {
|
|
24580
24893
|
const fs = binding?.__fs;
|
|
24581
24894
|
if (!fs?.mkdirSync || !fs?.writeFileSync) return;
|
|
24582
|
-
|
|
24583
|
-
|
|
24584
|
-
|
|
24895
|
+
volume.attach(fs, root);
|
|
24896
|
+
}
|
|
24897
|
+
|
|
24898
|
+
// src/runtime/sync-channel.ts
|
|
24899
|
+
var STATE = 0;
|
|
24900
|
+
var LENGTH = 1;
|
|
24901
|
+
var MORE = 2;
|
|
24902
|
+
var CONTROL_WORDS = 4;
|
|
24903
|
+
var STATE_REQUEST = 1;
|
|
24904
|
+
var STATE_RESPONSE = 2;
|
|
24905
|
+
var STATE_CONTINUE = 3;
|
|
24906
|
+
var STATE_CLOSED = 4;
|
|
24907
|
+
function createSyncChannelBuffers(capacityBytes = 1 << 20) {
|
|
24908
|
+
return {
|
|
24909
|
+
control: new SharedArrayBuffer(CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT),
|
|
24910
|
+
data: new SharedArrayBuffer(capacityBytes)
|
|
24911
|
+
};
|
|
24912
|
+
}
|
|
24913
|
+
function syncChannelSupported() {
|
|
24914
|
+
if (typeof SharedArrayBuffer !== "function") return false;
|
|
24915
|
+
if (typeof Atomics !== "object" || typeof Atomics.wait !== "function") return false;
|
|
24916
|
+
if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) return false;
|
|
24917
|
+
return true;
|
|
24918
|
+
}
|
|
24919
|
+
var SyncChannelServer = class {
|
|
24920
|
+
constructor(buffers, handle) {
|
|
24921
|
+
this.handle = handle;
|
|
24922
|
+
this.control = new Int32Array(buffers.control);
|
|
24923
|
+
this.data = new Uint8Array(buffers.data);
|
|
24924
|
+
this.capacity = this.data.length;
|
|
24585
24925
|
}
|
|
24586
|
-
|
|
24587
|
-
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24592
|
-
|
|
24926
|
+
handle;
|
|
24927
|
+
control;
|
|
24928
|
+
data;
|
|
24929
|
+
capacity;
|
|
24930
|
+
/** Request chunks gathered so far, and the response still to be sent. */
|
|
24931
|
+
incoming = [];
|
|
24932
|
+
outgoing = null;
|
|
24933
|
+
sent = 0;
|
|
24934
|
+
closed = false;
|
|
24935
|
+
/** Call from the wake message the client sends after each chunk. */
|
|
24936
|
+
async pump() {
|
|
24937
|
+
if (this.closed) return;
|
|
24938
|
+
const state = Atomics.load(this.control, STATE);
|
|
24939
|
+
if (state === STATE_REQUEST) {
|
|
24940
|
+
const size = Atomics.load(this.control, LENGTH);
|
|
24941
|
+
this.incoming.push(this.data.slice(0, size));
|
|
24942
|
+
if (Atomics.load(this.control, MORE) === 1) {
|
|
24943
|
+
this.publish(STATE_CONTINUE);
|
|
24944
|
+
return;
|
|
24945
|
+
}
|
|
24946
|
+
const request = concat3(this.incoming);
|
|
24947
|
+
this.incoming = [];
|
|
24948
|
+
let response;
|
|
24593
24949
|
try {
|
|
24594
|
-
|
|
24950
|
+
response = await this.handle(request);
|
|
24595
24951
|
} catch {
|
|
24952
|
+
response = new Uint8Array();
|
|
24596
24953
|
}
|
|
24597
|
-
|
|
24598
|
-
|
|
24599
|
-
|
|
24954
|
+
if (this.closed) return;
|
|
24955
|
+
this.outgoing = response;
|
|
24956
|
+
this.sent = 0;
|
|
24957
|
+
this.sendChunk();
|
|
24958
|
+
return;
|
|
24600
24959
|
}
|
|
24960
|
+
if (state === STATE_CONTINUE) this.sendChunk();
|
|
24961
|
+
}
|
|
24962
|
+
/** Release a blocked client, e.g. when the container is torn down. */
|
|
24963
|
+
close() {
|
|
24964
|
+
if (this.closed) return;
|
|
24965
|
+
this.closed = true;
|
|
24966
|
+
Atomics.store(this.control, STATE, STATE_CLOSED);
|
|
24967
|
+
Atomics.notify(this.control, STATE);
|
|
24968
|
+
}
|
|
24969
|
+
sendChunk() {
|
|
24970
|
+
const payload = this.outgoing ?? new Uint8Array();
|
|
24971
|
+
const size = Math.min(this.capacity, payload.length - this.sent);
|
|
24972
|
+
this.data.set(payload.subarray(this.sent, this.sent + size), 0);
|
|
24973
|
+
this.sent += size;
|
|
24974
|
+
Atomics.store(this.control, LENGTH, size);
|
|
24975
|
+
Atomics.store(this.control, MORE, this.sent < payload.length ? 1 : 0);
|
|
24976
|
+
this.publish(STATE_RESPONSE);
|
|
24977
|
+
}
|
|
24978
|
+
publish(state) {
|
|
24979
|
+
Atomics.store(this.control, STATE, state);
|
|
24980
|
+
Atomics.notify(this.control, STATE);
|
|
24981
|
+
}
|
|
24982
|
+
};
|
|
24983
|
+
function concat3(parts) {
|
|
24984
|
+
if (parts.length === 1) return parts[0];
|
|
24985
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
24986
|
+
const joined = new Uint8Array(total);
|
|
24987
|
+
let at = 0;
|
|
24988
|
+
for (const part of parts) {
|
|
24989
|
+
joined.set(part, at);
|
|
24990
|
+
at += part.length;
|
|
24991
|
+
}
|
|
24992
|
+
return joined;
|
|
24993
|
+
}
|
|
24994
|
+
|
|
24995
|
+
// src/runtime/remote-volume.ts
|
|
24996
|
+
var encoder7 = new TextEncoder();
|
|
24997
|
+
var decoder7 = new TextDecoder();
|
|
24998
|
+
function encodeFrame(header, body) {
|
|
24999
|
+
const json = encoder7.encode(JSON.stringify(header));
|
|
25000
|
+
const frame = new Uint8Array(4 + json.length + (body?.length ?? 0));
|
|
25001
|
+
new DataView(frame.buffer).setUint32(0, json.length, true);
|
|
25002
|
+
frame.set(json, 4);
|
|
25003
|
+
if (body?.length) frame.set(body, 4 + json.length);
|
|
25004
|
+
return frame;
|
|
25005
|
+
}
|
|
25006
|
+
function decodeFrame(frame) {
|
|
25007
|
+
const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
25008
|
+
const headerLength = view.getUint32(0, true);
|
|
25009
|
+
const header = JSON.parse(decoder7.decode(frame.subarray(4, 4 + headerLength)));
|
|
25010
|
+
return { header, body: frame.subarray(4 + headerLength) };
|
|
25011
|
+
}
|
|
25012
|
+
function flattenStat(stat2) {
|
|
25013
|
+
return {
|
|
25014
|
+
kind: stat2.isDirectory() ? "directory" : stat2.isSymbolicLink() ? "symlink" : "file",
|
|
25015
|
+
mode: stat2.mode,
|
|
25016
|
+
size: stat2.size,
|
|
25017
|
+
uid: stat2.uid,
|
|
25018
|
+
gid: stat2.gid,
|
|
25019
|
+
ino: stat2.ino,
|
|
25020
|
+
nlink: stat2.nlink,
|
|
25021
|
+
atimeMs: stat2.atimeMs,
|
|
25022
|
+
mtimeMs: stat2.mtimeMs,
|
|
25023
|
+
ctimeMs: stat2.ctimeMs,
|
|
25024
|
+
birthtimeMs: stat2.birthtimeMs
|
|
24601
25025
|
};
|
|
24602
|
-
|
|
25026
|
+
}
|
|
25027
|
+
var BINARY_RESULTS = /* @__PURE__ */ new Set(["readFileSync"]);
|
|
25028
|
+
var BINARY_ARGUMENTS = /* @__PURE__ */ new Set(["writeFileSync", "appendFileSync"]);
|
|
25029
|
+
function serveVolume(volume) {
|
|
25030
|
+
return (request) => {
|
|
25031
|
+
const { header, body } = decodeFrame(request);
|
|
25032
|
+
const target = volume;
|
|
25033
|
+
try {
|
|
25034
|
+
const method = target[header.op];
|
|
25035
|
+
if (typeof method !== "function") {
|
|
25036
|
+
return encodeFrame({ ok: false, error: { message: `unknown volume operation: ${header.op}` } });
|
|
25037
|
+
}
|
|
25038
|
+
const plain = header.args.map((value) => value === null ? void 0 : value);
|
|
25039
|
+
const args = BINARY_ARGUMENTS.has(header.op) ? [plain[0], body] : header.op === "utimesSync" ? [plain[0], new Date(plain[1]), new Date(plain[2])] : plain;
|
|
25040
|
+
const result = method.apply(volume, args);
|
|
25041
|
+
if (BINARY_RESULTS.has(header.op)) {
|
|
25042
|
+
return encodeFrame({ ok: true }, result);
|
|
25043
|
+
}
|
|
25044
|
+
if (header.op === "lstatSync") {
|
|
25045
|
+
return encodeFrame({ ok: true, value: flattenStat(result) });
|
|
25046
|
+
}
|
|
25047
|
+
return encodeFrame({ ok: true, value: result });
|
|
25048
|
+
} catch (error) {
|
|
25049
|
+
const failure = error;
|
|
25050
|
+
return encodeFrame({
|
|
25051
|
+
ok: false,
|
|
25052
|
+
error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
|
|
25053
|
+
});
|
|
25054
|
+
}
|
|
25055
|
+
};
|
|
25056
|
+
}
|
|
25057
|
+
|
|
25058
|
+
// src/runtime/sync-syscalls.ts
|
|
25059
|
+
var SPAWN_OP = "spawnSync";
|
|
25060
|
+
function serveSyncSyscalls(options) {
|
|
25061
|
+
const volumeHandler = serveVolume(options.volume);
|
|
25062
|
+
return async (request) => {
|
|
25063
|
+
const { header } = decodeFrame(request);
|
|
25064
|
+
if (header.op !== SPAWN_OP) return volumeHandler(request);
|
|
25065
|
+
try {
|
|
25066
|
+
const result = await options.spawnChild(header.args[0]);
|
|
25067
|
+
return encodeFrame({ ok: true, value: result });
|
|
25068
|
+
} catch (error) {
|
|
25069
|
+
const failure = error;
|
|
25070
|
+
return encodeFrame({
|
|
25071
|
+
ok: true,
|
|
25072
|
+
value: {
|
|
25073
|
+
status: null,
|
|
25074
|
+
stdout: "",
|
|
25075
|
+
stderr: "",
|
|
25076
|
+
signal: null,
|
|
25077
|
+
error: { ...failure.code ? { code: failure.code } : {}, message: failure.message ?? String(error) }
|
|
25078
|
+
}
|
|
25079
|
+
});
|
|
25080
|
+
}
|
|
25081
|
+
};
|
|
25082
|
+
}
|
|
25083
|
+
|
|
25084
|
+
// src/runtime/worker-host.ts
|
|
25085
|
+
function defaultWorkerUrl() {
|
|
25086
|
+
return new URL("./worker-entry.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
25087
|
+
}
|
|
25088
|
+
function hasDomWorker() {
|
|
25089
|
+
return typeof Worker === "function" && typeof document !== "undefined";
|
|
25090
|
+
}
|
|
25091
|
+
async function startRuntimeWorker(options = {}) {
|
|
25092
|
+
const url = options.url ?? defaultWorkerUrl();
|
|
25093
|
+
const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
|
|
25094
|
+
await new Promise((resolve2, reject) => {
|
|
25095
|
+
const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
|
|
25096
|
+
worker.onMessage((message) => {
|
|
25097
|
+
if (message?.type === "sandboxedjs:ready") {
|
|
25098
|
+
clearTimeout(timer);
|
|
25099
|
+
resolve2();
|
|
25100
|
+
}
|
|
25101
|
+
});
|
|
25102
|
+
worker.onError((error) => {
|
|
25103
|
+
clearTimeout(timer);
|
|
25104
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
25105
|
+
});
|
|
25106
|
+
});
|
|
25107
|
+
return worker;
|
|
25108
|
+
}
|
|
25109
|
+
async function startDomWorker(url) {
|
|
25110
|
+
const worker = new Worker(url, { type: "module" });
|
|
25111
|
+
const listeners = [];
|
|
25112
|
+
const errors = [];
|
|
25113
|
+
worker.addEventListener("message", (event) => {
|
|
25114
|
+
for (const listener of listeners) listener(event.data);
|
|
25115
|
+
});
|
|
25116
|
+
worker.addEventListener("error", (event) => {
|
|
25117
|
+
const detail = event.message || "the runtime worker failed to load";
|
|
25118
|
+
for (const listener of errors) listener(new Error(detail));
|
|
25119
|
+
});
|
|
25120
|
+
return {
|
|
25121
|
+
postMessage: (message) => worker.postMessage(message),
|
|
25122
|
+
onMessage: (listener) => {
|
|
25123
|
+
listeners.push(listener);
|
|
25124
|
+
},
|
|
25125
|
+
onError: (listener) => {
|
|
25126
|
+
errors.push(listener);
|
|
25127
|
+
},
|
|
25128
|
+
terminate: () => worker.terminate()
|
|
25129
|
+
};
|
|
25130
|
+
}
|
|
25131
|
+
async function startNodeWorker(url, workerData) {
|
|
25132
|
+
const specifier = ["node", "worker_threads"].join(":");
|
|
25133
|
+
const { Worker: NodeWorker } = await import(
|
|
25134
|
+
/* @vite-ignore */
|
|
25135
|
+
/* webpackIgnore: true */
|
|
25136
|
+
specifier
|
|
25137
|
+
);
|
|
25138
|
+
const worker = new NodeWorker(url, { workerData });
|
|
25139
|
+
return {
|
|
25140
|
+
postMessage: (message) => worker.postMessage(message),
|
|
25141
|
+
onMessage: (listener) => worker.on("message", listener),
|
|
25142
|
+
onError: (listener) => worker.on("error", listener),
|
|
25143
|
+
terminate: () => worker.terminate()
|
|
25144
|
+
};
|
|
25145
|
+
}
|
|
25146
|
+
var WorkerProcess = class extends EventEmitter4__default.default {
|
|
25147
|
+
constructor(worker, release) {
|
|
25148
|
+
super();
|
|
25149
|
+
this.worker = worker;
|
|
25150
|
+
this.release = release;
|
|
25151
|
+
super.on("error", () => {
|
|
25152
|
+
});
|
|
25153
|
+
this.completion = new Promise((resolve2) => {
|
|
25154
|
+
this.resolveCompletion = resolve2;
|
|
25155
|
+
});
|
|
25156
|
+
}
|
|
25157
|
+
worker;
|
|
25158
|
+
release;
|
|
25159
|
+
completion;
|
|
25160
|
+
resolveCompletion;
|
|
25161
|
+
settled = false;
|
|
25162
|
+
out = [];
|
|
25163
|
+
err = [];
|
|
25164
|
+
started = false;
|
|
25165
|
+
pendingInput = [];
|
|
25166
|
+
inputEnded = false;
|
|
25167
|
+
on(event, listener) {
|
|
25168
|
+
return super.on(event, listener);
|
|
25169
|
+
}
|
|
25170
|
+
/** Called once the guest has been told to start; releases anything buffered. */
|
|
25171
|
+
begin() {
|
|
25172
|
+
this.started = true;
|
|
25173
|
+
for (const chunk of this.pendingInput.splice(0)) this.worker.postMessage({ type: "stdin", data: chunk });
|
|
25174
|
+
if (this.inputEnded) this.worker.postMessage({ type: "stdin-end" });
|
|
25175
|
+
}
|
|
25176
|
+
output(text2) {
|
|
25177
|
+
this.out.push(text2);
|
|
25178
|
+
this.emit("output", text2);
|
|
25179
|
+
}
|
|
25180
|
+
error(text2) {
|
|
25181
|
+
this.err.push(text2);
|
|
25182
|
+
this.emit("error", text2);
|
|
25183
|
+
}
|
|
25184
|
+
/**
|
|
25185
|
+
* Deliver input to the running program.
|
|
25186
|
+
*
|
|
25187
|
+
* Held until the guest has been started, for the same reason the in-realm
|
|
25188
|
+
* pod holds it: an interactive session's first keystrokes arrive while the
|
|
25189
|
+
* program is still loading, and dropping them loses the answer to a prompt.
|
|
25190
|
+
*/
|
|
25191
|
+
write(data) {
|
|
25192
|
+
if (this.started) this.worker.postMessage({ type: "stdin", data });
|
|
25193
|
+
else this.pendingInput.push(data);
|
|
25194
|
+
}
|
|
25195
|
+
endInput() {
|
|
25196
|
+
this.inputEnded = true;
|
|
25197
|
+
if (this.started) this.worker.postMessage({ type: "stdin-end" });
|
|
25198
|
+
}
|
|
25199
|
+
kill(_signal = "SIGTERM") {
|
|
25200
|
+
this.worker.postMessage({ type: "kill" });
|
|
25201
|
+
this.finish(137);
|
|
25202
|
+
}
|
|
25203
|
+
finish(exitCode) {
|
|
25204
|
+
if (this.settled) return;
|
|
25205
|
+
this.settled = true;
|
|
25206
|
+
this.emit("exit", exitCode);
|
|
25207
|
+
this.resolveCompletion({ exitCode, stdout: this.out.join(""), stderr: this.err.join("") });
|
|
25208
|
+
this.release();
|
|
25209
|
+
}
|
|
25210
|
+
};
|
|
25211
|
+
var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
|
|
25212
|
+
workerUrl;
|
|
25213
|
+
/** Live workers, so teardown can stop them all. */
|
|
25214
|
+
live = /* @__PURE__ */ new Set();
|
|
25215
|
+
constructor(options) {
|
|
25216
|
+
super(options);
|
|
25217
|
+
this.workerUrl = options.workerUrl;
|
|
25218
|
+
}
|
|
25219
|
+
/**
|
|
25220
|
+
* Boot a Worker-backed pod, or return null when this host cannot support one.
|
|
25221
|
+
*
|
|
25222
|
+
* Declining is a first-class outcome. `SharedArrayBuffer` needs cross-origin
|
|
25223
|
+
* isolation, a bundler may have made the guest script unreachable, and a host
|
|
25224
|
+
* that supplied its own module objects has handed over things no thread
|
|
25225
|
+
* boundary can carry. In every case the caller falls back to the in-realm pod
|
|
25226
|
+
* and keeps working.
|
|
25227
|
+
*/
|
|
25228
|
+
static async tryBoot(options = {}) {
|
|
25229
|
+
if (!syncChannelSupported()) return null;
|
|
25230
|
+
if (options.modules && Object.keys(options.modules).length > 0) return null;
|
|
25231
|
+
const pod = new _WorkerRuntimePod(options);
|
|
25232
|
+
try {
|
|
25233
|
+
const probe = await startRuntimeWorker({ ...options.workerUrl ? { url: options.workerUrl } : {}, timeoutMs: 1e4 });
|
|
25234
|
+
await probe.terminate();
|
|
25235
|
+
return pod;
|
|
25236
|
+
} catch {
|
|
25237
|
+
pod.teardown();
|
|
25238
|
+
return null;
|
|
25239
|
+
}
|
|
25240
|
+
}
|
|
25241
|
+
async spawn(command, args = [], options = {}) {
|
|
25242
|
+
if (command !== "node" && command !== "nodejs") return super.spawn(command, args, options);
|
|
25243
|
+
const script = args[0];
|
|
25244
|
+
if (!script) return super.spawn(command, args, options);
|
|
25245
|
+
const cwd = typeof options.cwd === "string" ? options.cwd : this.workdir;
|
|
25246
|
+
if (this.needsHostModules(cwd)) return super.spawn(command, args, options);
|
|
25247
|
+
return await this.spawnInWorker(script, args, cwd, options);
|
|
25248
|
+
}
|
|
25249
|
+
async spawnInWorker(script, args, cwd, options) {
|
|
25250
|
+
const env2 = { ...this.env, ...isRecord2(options.env) ? options.env : {} };
|
|
25251
|
+
const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
|
|
25252
|
+
const buffers = createSyncChannelBuffers();
|
|
25253
|
+
const worker = await startRuntimeWorker({
|
|
25254
|
+
...this.workerUrl ? { url: this.workerUrl } : {},
|
|
25255
|
+
timeoutMs: 15e3
|
|
25256
|
+
});
|
|
25257
|
+
const server = new SyncChannelServer(buffers, serveSyncSyscalls({
|
|
25258
|
+
volume: this.volume,
|
|
25259
|
+
spawnChild: (request) => this.runChildToCompletion(request)
|
|
25260
|
+
}));
|
|
25261
|
+
const entry = { worker, server };
|
|
25262
|
+
this.live.add(entry);
|
|
25263
|
+
const process2 = new WorkerProcess(worker, () => {
|
|
25264
|
+
this.live.delete(entry);
|
|
25265
|
+
server.close();
|
|
25266
|
+
this.closeProxies(owner);
|
|
25267
|
+
void worker.terminate();
|
|
25268
|
+
});
|
|
25269
|
+
const children = /* @__PURE__ */ new Map();
|
|
25270
|
+
worker.onMessage((raw) => {
|
|
25271
|
+
const message = raw;
|
|
25272
|
+
switch (message?.type) {
|
|
25273
|
+
case "wake":
|
|
25274
|
+
void server.pump();
|
|
25275
|
+
return;
|
|
25276
|
+
case "output":
|
|
25277
|
+
process2.output(String(message.text));
|
|
25278
|
+
return;
|
|
25279
|
+
case "error":
|
|
25280
|
+
process2.error(String(message.text));
|
|
25281
|
+
return;
|
|
25282
|
+
case "rawmode":
|
|
25283
|
+
process2.emit("rawmode", Boolean(message.enabled));
|
|
25284
|
+
return;
|
|
25285
|
+
case "exit":
|
|
25286
|
+
process2.finish(Number(message.code));
|
|
25287
|
+
return;
|
|
25288
|
+
case "listen":
|
|
25289
|
+
this.proxyPort(Number(message.port), owner, worker);
|
|
25290
|
+
return;
|
|
25291
|
+
case "http-response":
|
|
25292
|
+
this.settleProxied(Number(message.id), message.response);
|
|
25293
|
+
return;
|
|
25294
|
+
case "child-start":
|
|
25295
|
+
this.startChild(worker, children, message);
|
|
25296
|
+
return;
|
|
25297
|
+
case "child-stdin":
|
|
25298
|
+
children.get(message.id)?.sendStdin?.(String(message.data));
|
|
25299
|
+
return;
|
|
25300
|
+
case "child-stdin-end":
|
|
25301
|
+
children.get(message.id)?.endStdin?.();
|
|
25302
|
+
return;
|
|
25303
|
+
case "child-kill":
|
|
25304
|
+
children.get(message.id)?.kill(String(message.signal));
|
|
25305
|
+
return;
|
|
25306
|
+
default:
|
|
25307
|
+
return;
|
|
25308
|
+
}
|
|
25309
|
+
});
|
|
25310
|
+
worker.postMessage({
|
|
25311
|
+
type: "start",
|
|
25312
|
+
buffers,
|
|
25313
|
+
script,
|
|
25314
|
+
cwd,
|
|
25315
|
+
env: env2,
|
|
25316
|
+
argv: Array.isArray(options.argv) ? options.argv : ["/usr/bin/node", script, ...args.slice(1)],
|
|
25317
|
+
aliases: this.aliases,
|
|
25318
|
+
...typeof options.stdinPath === "string" ? { stdinPath: options.stdinPath } : {},
|
|
25319
|
+
...options.interactiveStdin ? { interactiveStdin: true } : {},
|
|
25320
|
+
...options.tty ? { tty: true } : {}
|
|
25321
|
+
});
|
|
25322
|
+
process2.begin();
|
|
25323
|
+
return process2;
|
|
25324
|
+
}
|
|
25325
|
+
/** Start an asynchronous child on the host's behalf and relay its events. */
|
|
25326
|
+
startChild(worker, children, message) {
|
|
25327
|
+
const handle = this.processManager.spawn(message.config);
|
|
25328
|
+
children.set(message.id, handle);
|
|
25329
|
+
for (const event of ["stdout", "stderr", "exit"]) {
|
|
25330
|
+
handle.on(event, (value) => worker.postMessage({ type: "child-event", id: message.id, event, value }));
|
|
25331
|
+
}
|
|
25332
|
+
handle.exec();
|
|
25333
|
+
}
|
|
25334
|
+
/** Run a child to completion and collect it, for the guest's `spawnSync`. */
|
|
25335
|
+
runChildToCompletion(request) {
|
|
25336
|
+
return new Promise((resolve2) => {
|
|
25337
|
+
let handle;
|
|
25338
|
+
try {
|
|
25339
|
+
handle = this.processManager.spawn({
|
|
25340
|
+
command: request.command,
|
|
25341
|
+
args: request.args,
|
|
25342
|
+
cwd: request.cwd,
|
|
25343
|
+
...request.env ? { env: request.env } : {}
|
|
25344
|
+
});
|
|
25345
|
+
} catch (error) {
|
|
25346
|
+
const failure = error;
|
|
25347
|
+
resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure.code ? { code: failure.code } : {}, message: failure.message } });
|
|
25348
|
+
return;
|
|
25349
|
+
}
|
|
25350
|
+
let stdout = "";
|
|
25351
|
+
let stderr = "";
|
|
25352
|
+
handle.on("stdout", (text2) => {
|
|
25353
|
+
stdout += text2;
|
|
25354
|
+
});
|
|
25355
|
+
handle.on("stderr", (text2) => {
|
|
25356
|
+
stderr += text2;
|
|
25357
|
+
});
|
|
25358
|
+
handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
|
|
25359
|
+
handle.exec();
|
|
25360
|
+
if (request.input !== void 0) {
|
|
25361
|
+
handle.sendStdin?.(request.input);
|
|
25362
|
+
}
|
|
25363
|
+
handle.endStdin?.();
|
|
25364
|
+
});
|
|
25365
|
+
}
|
|
25366
|
+
// ── HTTP servers living on another thread ─────────────────────────────────
|
|
25367
|
+
proxies = /* @__PURE__ */ new Map();
|
|
25368
|
+
waiting = /* @__PURE__ */ new Map();
|
|
25369
|
+
nextRequestId = 1;
|
|
25370
|
+
/**
|
|
25371
|
+
* Register a stand-in for a server that is actually running in the Worker.
|
|
25372
|
+
*
|
|
25373
|
+
* The router only knows how to reach servers on this thread, so each bound
|
|
25374
|
+
* port gets a local server whose whole job is to forward and wait.
|
|
25375
|
+
*/
|
|
25376
|
+
proxyPort(port, owner, worker) {
|
|
25377
|
+
if (this.proxies.has(port)) return;
|
|
25378
|
+
const server = new VirtualHttpServer(this.router, owner, (request, response) => {
|
|
25379
|
+
void this.forward(worker, port, request, response);
|
|
25380
|
+
});
|
|
25381
|
+
try {
|
|
25382
|
+
server.listen(port);
|
|
25383
|
+
this.proxies.set(port, server);
|
|
25384
|
+
} catch {
|
|
25385
|
+
}
|
|
25386
|
+
}
|
|
25387
|
+
async forward(worker, port, request, response) {
|
|
25388
|
+
const chunks = [];
|
|
25389
|
+
for await (const chunk of request) chunks.push(chunk);
|
|
25390
|
+
const body = concat4(chunks);
|
|
25391
|
+
const id = this.nextRequestId++;
|
|
25392
|
+
const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
|
|
25393
|
+
worker.postMessage({
|
|
25394
|
+
type: "http-request",
|
|
25395
|
+
id,
|
|
25396
|
+
port,
|
|
25397
|
+
init: { method: request.method, path: request.url, headers: request.headers, body }
|
|
25398
|
+
});
|
|
25399
|
+
let result;
|
|
25400
|
+
try {
|
|
25401
|
+
result = await answered;
|
|
25402
|
+
} catch (error) {
|
|
25403
|
+
this.waiting.delete(id);
|
|
25404
|
+
response.writeHead(500, "Internal Server Error", {});
|
|
25405
|
+
response.end(new TextEncoder().encode(error instanceof Error ? error.message : String(error)));
|
|
25406
|
+
return;
|
|
25407
|
+
}
|
|
25408
|
+
response.writeHead(Number(result.statusCode ?? 200), String(result.statusMessage ?? ""), result.headers ?? {});
|
|
25409
|
+
response.end(result.body ?? new Uint8Array());
|
|
25410
|
+
}
|
|
25411
|
+
settleProxied(id, response) {
|
|
25412
|
+
const resolve2 = this.waiting.get(id);
|
|
25413
|
+
if (!resolve2) return;
|
|
25414
|
+
this.waiting.delete(id);
|
|
25415
|
+
resolve2(response);
|
|
25416
|
+
}
|
|
25417
|
+
closeProxies(owner) {
|
|
25418
|
+
for (const [port, server] of [...this.proxies]) {
|
|
25419
|
+
if (server.owner !== owner) continue;
|
|
25420
|
+
server.close();
|
|
25421
|
+
this.proxies.delete(port);
|
|
25422
|
+
}
|
|
25423
|
+
}
|
|
25424
|
+
teardown() {
|
|
25425
|
+
for (const { worker, server } of [...this.live]) {
|
|
25426
|
+
server.close();
|
|
25427
|
+
void worker.terminate();
|
|
25428
|
+
}
|
|
25429
|
+
this.live.clear();
|
|
25430
|
+
super.teardown();
|
|
25431
|
+
}
|
|
25432
|
+
/** Does anything under `cwd` need a module only the host can supply? */
|
|
25433
|
+
needsHostModules(cwd) {
|
|
25434
|
+
return packageIsInstalled(this.volume, cwd, "rolldown");
|
|
25435
|
+
}
|
|
25436
|
+
};
|
|
25437
|
+
function isRecord2(value) {
|
|
25438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25439
|
+
}
|
|
25440
|
+
function concat4(parts) {
|
|
25441
|
+
if (parts.length === 0) return new Uint8Array();
|
|
25442
|
+
if (parts.length === 1) return parts[0];
|
|
25443
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
25444
|
+
const joined = new Uint8Array(total);
|
|
25445
|
+
let at = 0;
|
|
25446
|
+
for (const part of parts) {
|
|
25447
|
+
joined.set(part, at);
|
|
25448
|
+
at += part.length;
|
|
25449
|
+
}
|
|
25450
|
+
return joined;
|
|
24603
25451
|
}
|
|
24604
25452
|
|
|
24605
25453
|
// src/container/container.ts
|
|
@@ -24646,11 +25494,13 @@ var Container = class _Container {
|
|
|
24646
25494
|
// ── boot ──────────────────────────────────────────────────────────────────
|
|
24647
25495
|
static async create(opts = {}) {
|
|
24648
25496
|
if (opts.python) configurePython(opts.python);
|
|
24649
|
-
const
|
|
25497
|
+
const podOptions = {
|
|
24650
25498
|
workdir: opts.cwd ?? "/",
|
|
24651
25499
|
env: opts.env ?? {},
|
|
24652
25500
|
...opts.onServerReady ? { onServerReady: opts.onServerReady } : {}
|
|
24653
|
-
}
|
|
25501
|
+
};
|
|
25502
|
+
const workerOptions = { ...podOptions, ...opts.workerUrl ? { workerUrl: opts.workerUrl } : {} };
|
|
25503
|
+
const pod = opts.pod ?? (opts.isolation === "realm" ? null : await WorkerRuntimePod.tryBoot(workerOptions)) ?? await LocalRuntimePod.boot(podOptions);
|
|
24654
25504
|
const kernel = new Kernel({
|
|
24655
25505
|
pod,
|
|
24656
25506
|
hostname: opts.hostname ?? "sandbox",
|
|
@@ -24892,15 +25742,15 @@ var Container = class _Container {
|
|
|
24892
25742
|
}
|
|
24893
25743
|
makeStdio(opts) {
|
|
24894
25744
|
const combined = [];
|
|
24895
|
-
const
|
|
25745
|
+
const decoder8 = new TextDecoder();
|
|
24896
25746
|
const stdout = new BufferSink((chunk) => {
|
|
24897
|
-
const text2 =
|
|
25747
|
+
const text2 = decoder8.decode(chunk, { stream: true });
|
|
24898
25748
|
combined.push(text2);
|
|
24899
25749
|
opts.onStdout?.(text2);
|
|
24900
25750
|
this.hooks.onStdout?.(text2);
|
|
24901
25751
|
});
|
|
24902
25752
|
const stderr = new BufferSink((chunk) => {
|
|
24903
|
-
const text2 =
|
|
25753
|
+
const text2 = decoder8.decode(chunk, { stream: true });
|
|
24904
25754
|
combined.push(text2);
|
|
24905
25755
|
opts.onStderr?.(text2);
|
|
24906
25756
|
this.hooks.onStderr?.(text2);
|
|
@@ -25594,7 +26444,9 @@ exports.parseUmask = parseUmask;
|
|
|
25594
26444
|
exports.posixPath = path_exports;
|
|
25595
26445
|
exports.resetPidCounter = resetPidCounter;
|
|
25596
26446
|
exports.shellQuote = shellQuote;
|
|
26447
|
+
exports.startRuntimeWorker = startRuntimeWorker;
|
|
25597
26448
|
exports.strerror = strerror;
|
|
26449
|
+
exports.syncChannelSupported = syncChannelSupported;
|
|
25598
26450
|
exports.transformEsm = transformEsm;
|
|
25599
26451
|
exports.unameInfo = unameInfo;
|
|
25600
26452
|
//# sourceMappingURL=index.cjs.map
|