just-bash 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/AGENTS.md +1 -1
- package/dist/bin/chunks/python3-JGT65AEB.js +14 -0
- package/dist/bin/chunks/worker.js +1038 -0
- package/dist/bin/just-bash.js +37 -37
- package/dist/bin/shell/chunks/python3-JGT65AEB.js +14 -0
- package/dist/bin/shell/shell.js +37 -37
- package/dist/bundle/browser.js +27 -27
- package/dist/bundle/chunks/python3-3OP7EKER.js +13 -0
- package/dist/bundle/chunks/worker.js +1038 -0
- package/dist/bundle/index.js +31 -31
- package/dist/commands/python3/fs-bridge-handler.d.ts +50 -0
- package/dist/commands/python3/protocol.d.ts +138 -0
- package/dist/commands/python3/python3.d.ts +11 -0
- package/dist/commands/python3/sync-fs-backend.d.ts +59 -0
- package/dist/commands/python3/worker.d.ts +16 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/limits.d.ts +2 -0
- package/package.json +7 -5
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
// src/commands/python3/worker.ts
|
|
2
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
import { loadPyodide } from "pyodide";
|
|
4
|
+
|
|
5
|
+
// src/commands/python3/protocol.ts
|
|
6
|
+
var OpCode = {
|
|
7
|
+
NOOP: 0,
|
|
8
|
+
READ_FILE: 1,
|
|
9
|
+
WRITE_FILE: 2,
|
|
10
|
+
STAT: 3,
|
|
11
|
+
READDIR: 4,
|
|
12
|
+
MKDIR: 5,
|
|
13
|
+
RM: 6,
|
|
14
|
+
EXISTS: 7,
|
|
15
|
+
APPEND_FILE: 8,
|
|
16
|
+
SYMLINK: 9,
|
|
17
|
+
READLINK: 10,
|
|
18
|
+
LSTAT: 11,
|
|
19
|
+
CHMOD: 12,
|
|
20
|
+
REALPATH: 13,
|
|
21
|
+
// Special operations for Python I/O
|
|
22
|
+
WRITE_STDOUT: 100,
|
|
23
|
+
WRITE_STDERR: 101,
|
|
24
|
+
EXIT: 102,
|
|
25
|
+
// HTTP operations
|
|
26
|
+
HTTP_REQUEST: 200
|
|
27
|
+
};
|
|
28
|
+
var Status = {
|
|
29
|
+
PENDING: 0,
|
|
30
|
+
READY: 1,
|
|
31
|
+
SUCCESS: 2,
|
|
32
|
+
ERROR: 3
|
|
33
|
+
};
|
|
34
|
+
var ErrorCode = {
|
|
35
|
+
NONE: 0,
|
|
36
|
+
NOT_FOUND: 1,
|
|
37
|
+
IS_DIRECTORY: 2,
|
|
38
|
+
NOT_DIRECTORY: 3,
|
|
39
|
+
EXISTS: 4,
|
|
40
|
+
PERMISSION_DENIED: 5,
|
|
41
|
+
INVALID_PATH: 6,
|
|
42
|
+
IO_ERROR: 7,
|
|
43
|
+
TIMEOUT: 8,
|
|
44
|
+
NETWORK_ERROR: 9,
|
|
45
|
+
NETWORK_NOT_CONFIGURED: 10
|
|
46
|
+
};
|
|
47
|
+
var Offset = {
|
|
48
|
+
OP_CODE: 0,
|
|
49
|
+
STATUS: 4,
|
|
50
|
+
PATH_LENGTH: 8,
|
|
51
|
+
DATA_LENGTH: 12,
|
|
52
|
+
RESULT_LENGTH: 16,
|
|
53
|
+
ERROR_CODE: 20,
|
|
54
|
+
FLAGS: 24,
|
|
55
|
+
MODE: 28,
|
|
56
|
+
PATH_BUFFER: 32,
|
|
57
|
+
DATA_BUFFER: 4128
|
|
58
|
+
// 32 + 4096
|
|
59
|
+
};
|
|
60
|
+
var Size = {
|
|
61
|
+
CONTROL_REGION: 32,
|
|
62
|
+
PATH_BUFFER: 4096,
|
|
63
|
+
DATA_BUFFER: 1048576,
|
|
64
|
+
// 1MB (reduced from 16MB for faster tests)
|
|
65
|
+
TOTAL: 1052704
|
|
66
|
+
// 32 + 4096 + 1MB
|
|
67
|
+
};
|
|
68
|
+
var Flags = {
|
|
69
|
+
NONE: 0,
|
|
70
|
+
RECURSIVE: 1,
|
|
71
|
+
FORCE: 2,
|
|
72
|
+
MKDIR_RECURSIVE: 1
|
|
73
|
+
};
|
|
74
|
+
var StatLayout = {
|
|
75
|
+
IS_FILE: 0,
|
|
76
|
+
IS_DIRECTORY: 1,
|
|
77
|
+
IS_SYMLINK: 2,
|
|
78
|
+
MODE: 4,
|
|
79
|
+
SIZE: 8,
|
|
80
|
+
MTIME: 16,
|
|
81
|
+
TOTAL: 24
|
|
82
|
+
};
|
|
83
|
+
var ProtocolBuffer = class {
|
|
84
|
+
int32View;
|
|
85
|
+
uint8View;
|
|
86
|
+
dataView;
|
|
87
|
+
constructor(buffer) {
|
|
88
|
+
this.int32View = new Int32Array(buffer);
|
|
89
|
+
this.uint8View = new Uint8Array(buffer);
|
|
90
|
+
this.dataView = new DataView(buffer);
|
|
91
|
+
}
|
|
92
|
+
getOpCode() {
|
|
93
|
+
return Atomics.load(this.int32View, Offset.OP_CODE / 4);
|
|
94
|
+
}
|
|
95
|
+
setOpCode(code) {
|
|
96
|
+
Atomics.store(this.int32View, Offset.OP_CODE / 4, code);
|
|
97
|
+
}
|
|
98
|
+
getStatus() {
|
|
99
|
+
return Atomics.load(this.int32View, Offset.STATUS / 4);
|
|
100
|
+
}
|
|
101
|
+
setStatus(status) {
|
|
102
|
+
Atomics.store(this.int32View, Offset.STATUS / 4, status);
|
|
103
|
+
}
|
|
104
|
+
getPathLength() {
|
|
105
|
+
return Atomics.load(this.int32View, Offset.PATH_LENGTH / 4);
|
|
106
|
+
}
|
|
107
|
+
setPathLength(length) {
|
|
108
|
+
Atomics.store(this.int32View, Offset.PATH_LENGTH / 4, length);
|
|
109
|
+
}
|
|
110
|
+
getDataLength() {
|
|
111
|
+
return Atomics.load(this.int32View, Offset.DATA_LENGTH / 4);
|
|
112
|
+
}
|
|
113
|
+
setDataLength(length) {
|
|
114
|
+
Atomics.store(this.int32View, Offset.DATA_LENGTH / 4, length);
|
|
115
|
+
}
|
|
116
|
+
getResultLength() {
|
|
117
|
+
return Atomics.load(this.int32View, Offset.RESULT_LENGTH / 4);
|
|
118
|
+
}
|
|
119
|
+
setResultLength(length) {
|
|
120
|
+
Atomics.store(this.int32View, Offset.RESULT_LENGTH / 4, length);
|
|
121
|
+
}
|
|
122
|
+
getErrorCode() {
|
|
123
|
+
return Atomics.load(this.int32View, Offset.ERROR_CODE / 4);
|
|
124
|
+
}
|
|
125
|
+
setErrorCode(code) {
|
|
126
|
+
Atomics.store(this.int32View, Offset.ERROR_CODE / 4, code);
|
|
127
|
+
}
|
|
128
|
+
getFlags() {
|
|
129
|
+
return Atomics.load(this.int32View, Offset.FLAGS / 4);
|
|
130
|
+
}
|
|
131
|
+
setFlags(flags) {
|
|
132
|
+
Atomics.store(this.int32View, Offset.FLAGS / 4, flags);
|
|
133
|
+
}
|
|
134
|
+
getMode() {
|
|
135
|
+
return Atomics.load(this.int32View, Offset.MODE / 4);
|
|
136
|
+
}
|
|
137
|
+
setMode(mode) {
|
|
138
|
+
Atomics.store(this.int32View, Offset.MODE / 4, mode);
|
|
139
|
+
}
|
|
140
|
+
getPath() {
|
|
141
|
+
const length = this.getPathLength();
|
|
142
|
+
const bytes = this.uint8View.slice(
|
|
143
|
+
Offset.PATH_BUFFER,
|
|
144
|
+
Offset.PATH_BUFFER + length
|
|
145
|
+
);
|
|
146
|
+
return new TextDecoder().decode(bytes);
|
|
147
|
+
}
|
|
148
|
+
setPath(path) {
|
|
149
|
+
const encoded = new TextEncoder().encode(path);
|
|
150
|
+
if (encoded.length > Size.PATH_BUFFER) {
|
|
151
|
+
throw new Error(`Path too long: ${encoded.length} > ${Size.PATH_BUFFER}`);
|
|
152
|
+
}
|
|
153
|
+
this.uint8View.set(encoded, Offset.PATH_BUFFER);
|
|
154
|
+
this.setPathLength(encoded.length);
|
|
155
|
+
}
|
|
156
|
+
getData() {
|
|
157
|
+
const length = this.getDataLength();
|
|
158
|
+
return this.uint8View.slice(
|
|
159
|
+
Offset.DATA_BUFFER,
|
|
160
|
+
Offset.DATA_BUFFER + length
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
setData(data) {
|
|
164
|
+
if (data.length > Size.DATA_BUFFER) {
|
|
165
|
+
throw new Error(`Data too large: ${data.length} > ${Size.DATA_BUFFER}`);
|
|
166
|
+
}
|
|
167
|
+
this.uint8View.set(data, Offset.DATA_BUFFER);
|
|
168
|
+
this.setDataLength(data.length);
|
|
169
|
+
}
|
|
170
|
+
getDataAsString() {
|
|
171
|
+
const data = this.getData();
|
|
172
|
+
return new TextDecoder().decode(data);
|
|
173
|
+
}
|
|
174
|
+
setDataFromString(str) {
|
|
175
|
+
const encoded = new TextEncoder().encode(str);
|
|
176
|
+
this.setData(encoded);
|
|
177
|
+
}
|
|
178
|
+
getResult() {
|
|
179
|
+
const length = this.getResultLength();
|
|
180
|
+
return this.uint8View.slice(
|
|
181
|
+
Offset.DATA_BUFFER,
|
|
182
|
+
Offset.DATA_BUFFER + length
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
setResult(data) {
|
|
186
|
+
if (data.length > Size.DATA_BUFFER) {
|
|
187
|
+
throw new Error(`Result too large: ${data.length} > ${Size.DATA_BUFFER}`);
|
|
188
|
+
}
|
|
189
|
+
this.uint8View.set(data, Offset.DATA_BUFFER);
|
|
190
|
+
this.setResultLength(data.length);
|
|
191
|
+
}
|
|
192
|
+
getResultAsString() {
|
|
193
|
+
const result = this.getResult();
|
|
194
|
+
return new TextDecoder().decode(result);
|
|
195
|
+
}
|
|
196
|
+
setResultFromString(str) {
|
|
197
|
+
const encoded = new TextEncoder().encode(str);
|
|
198
|
+
this.setResult(encoded);
|
|
199
|
+
}
|
|
200
|
+
encodeStat(stat) {
|
|
201
|
+
this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_FILE] = stat.isFile ? 1 : 0;
|
|
202
|
+
this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_DIRECTORY] = stat.isDirectory ? 1 : 0;
|
|
203
|
+
this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_SYMLINK] = stat.isSymbolicLink ? 1 : 0;
|
|
204
|
+
this.dataView.setInt32(
|
|
205
|
+
Offset.DATA_BUFFER + StatLayout.MODE,
|
|
206
|
+
stat.mode,
|
|
207
|
+
true
|
|
208
|
+
);
|
|
209
|
+
const size = Math.min(stat.size, Number.MAX_SAFE_INTEGER);
|
|
210
|
+
this.dataView.setFloat64(Offset.DATA_BUFFER + StatLayout.SIZE, size, true);
|
|
211
|
+
this.dataView.setFloat64(
|
|
212
|
+
Offset.DATA_BUFFER + StatLayout.MTIME,
|
|
213
|
+
stat.mtime.getTime(),
|
|
214
|
+
true
|
|
215
|
+
);
|
|
216
|
+
this.setResultLength(StatLayout.TOTAL);
|
|
217
|
+
}
|
|
218
|
+
decodeStat() {
|
|
219
|
+
return {
|
|
220
|
+
isFile: this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_FILE] === 1,
|
|
221
|
+
isDirectory: this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_DIRECTORY] === 1,
|
|
222
|
+
isSymbolicLink: this.uint8View[Offset.DATA_BUFFER + StatLayout.IS_SYMLINK] === 1,
|
|
223
|
+
mode: this.dataView.getInt32(Offset.DATA_BUFFER + StatLayout.MODE, true),
|
|
224
|
+
size: this.dataView.getFloat64(
|
|
225
|
+
Offset.DATA_BUFFER + StatLayout.SIZE,
|
|
226
|
+
true
|
|
227
|
+
),
|
|
228
|
+
mtime: new Date(
|
|
229
|
+
this.dataView.getFloat64(Offset.DATA_BUFFER + StatLayout.MTIME, true)
|
|
230
|
+
)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
waitForReady(timeout) {
|
|
234
|
+
return Atomics.wait(
|
|
235
|
+
this.int32View,
|
|
236
|
+
Offset.STATUS / 4,
|
|
237
|
+
Status.PENDING,
|
|
238
|
+
timeout
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
waitForReadyAsync(timeout) {
|
|
242
|
+
return Atomics.waitAsync(
|
|
243
|
+
this.int32View,
|
|
244
|
+
Offset.STATUS / 4,
|
|
245
|
+
Status.PENDING,
|
|
246
|
+
timeout
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Wait for status to become READY.
|
|
251
|
+
* Returns immediately if status is already READY, or waits until it changes.
|
|
252
|
+
*/
|
|
253
|
+
async waitUntilReady(timeout) {
|
|
254
|
+
const startTime = Date.now();
|
|
255
|
+
while (true) {
|
|
256
|
+
const status = this.getStatus();
|
|
257
|
+
if (status === Status.READY) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
const elapsed = Date.now() - startTime;
|
|
261
|
+
if (elapsed >= timeout) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const remainingMs = timeout - elapsed;
|
|
265
|
+
const result = Atomics.waitAsync(
|
|
266
|
+
this.int32View,
|
|
267
|
+
Offset.STATUS / 4,
|
|
268
|
+
status,
|
|
269
|
+
remainingMs
|
|
270
|
+
);
|
|
271
|
+
if (result.async) {
|
|
272
|
+
const waitResult = await result.value;
|
|
273
|
+
if (waitResult === "timed-out") {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
waitForResult(timeout) {
|
|
280
|
+
return Atomics.wait(
|
|
281
|
+
this.int32View,
|
|
282
|
+
Offset.STATUS / 4,
|
|
283
|
+
Status.READY,
|
|
284
|
+
timeout
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
notify() {
|
|
288
|
+
return Atomics.notify(this.int32View, Offset.STATUS / 4);
|
|
289
|
+
}
|
|
290
|
+
reset() {
|
|
291
|
+
this.setOpCode(OpCode.NOOP);
|
|
292
|
+
this.setStatus(Status.PENDING);
|
|
293
|
+
this.setPathLength(0);
|
|
294
|
+
this.setDataLength(0);
|
|
295
|
+
this.setResultLength(0);
|
|
296
|
+
this.setErrorCode(ErrorCode.NONE);
|
|
297
|
+
this.setFlags(Flags.NONE);
|
|
298
|
+
this.setMode(0);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/commands/python3/sync-fs-backend.ts
|
|
303
|
+
var SyncFsBackend = class {
|
|
304
|
+
protocol;
|
|
305
|
+
constructor(sharedBuffer) {
|
|
306
|
+
this.protocol = new ProtocolBuffer(sharedBuffer);
|
|
307
|
+
}
|
|
308
|
+
execSync(opCode, path, data, flags = 0, mode = 0) {
|
|
309
|
+
this.protocol.reset();
|
|
310
|
+
this.protocol.setOpCode(opCode);
|
|
311
|
+
this.protocol.setPath(path);
|
|
312
|
+
this.protocol.setFlags(flags);
|
|
313
|
+
this.protocol.setMode(mode);
|
|
314
|
+
if (data) {
|
|
315
|
+
this.protocol.setData(data);
|
|
316
|
+
}
|
|
317
|
+
this.protocol.setStatus(Status.READY);
|
|
318
|
+
this.protocol.notify();
|
|
319
|
+
const waitResult = this.protocol.waitForResult(5e3);
|
|
320
|
+
if (waitResult === "timed-out") {
|
|
321
|
+
return { success: false, error: "Operation timed out" };
|
|
322
|
+
}
|
|
323
|
+
const status = this.protocol.getStatus();
|
|
324
|
+
if (status === Status.SUCCESS) {
|
|
325
|
+
return { success: true, result: this.protocol.getResult() };
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
error: this.protocol.getResultAsString() || `Error code: ${this.protocol.getErrorCode()}`
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
readFile(path) {
|
|
333
|
+
const result = this.execSync(OpCode.READ_FILE, path);
|
|
334
|
+
if (!result.success) {
|
|
335
|
+
throw new Error(result.error || "Failed to read file");
|
|
336
|
+
}
|
|
337
|
+
return result.result ?? new Uint8Array(0);
|
|
338
|
+
}
|
|
339
|
+
writeFile(path, data) {
|
|
340
|
+
const result = this.execSync(OpCode.WRITE_FILE, path, data);
|
|
341
|
+
if (!result.success) {
|
|
342
|
+
throw new Error(result.error || "Failed to write file");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
stat(path) {
|
|
346
|
+
const result = this.execSync(OpCode.STAT, path);
|
|
347
|
+
if (!result.success) {
|
|
348
|
+
throw new Error(result.error || "Failed to stat");
|
|
349
|
+
}
|
|
350
|
+
return this.protocol.decodeStat();
|
|
351
|
+
}
|
|
352
|
+
lstat(path) {
|
|
353
|
+
const result = this.execSync(OpCode.LSTAT, path);
|
|
354
|
+
if (!result.success) {
|
|
355
|
+
throw new Error(result.error || "Failed to lstat");
|
|
356
|
+
}
|
|
357
|
+
return this.protocol.decodeStat();
|
|
358
|
+
}
|
|
359
|
+
readdir(path) {
|
|
360
|
+
const result = this.execSync(OpCode.READDIR, path);
|
|
361
|
+
if (!result.success) {
|
|
362
|
+
throw new Error(result.error || "Failed to readdir");
|
|
363
|
+
}
|
|
364
|
+
return JSON.parse(this.protocol.getResultAsString());
|
|
365
|
+
}
|
|
366
|
+
mkdir(path, recursive = false) {
|
|
367
|
+
const flags = recursive ? Flags.MKDIR_RECURSIVE : 0;
|
|
368
|
+
const result = this.execSync(OpCode.MKDIR, path, void 0, flags);
|
|
369
|
+
if (!result.success) {
|
|
370
|
+
throw new Error(result.error || "Failed to mkdir");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
rm(path, recursive = false, force = false) {
|
|
374
|
+
let flags = 0;
|
|
375
|
+
if (recursive) flags |= Flags.RECURSIVE;
|
|
376
|
+
if (force) flags |= Flags.FORCE;
|
|
377
|
+
const result = this.execSync(OpCode.RM, path, void 0, flags);
|
|
378
|
+
if (!result.success) {
|
|
379
|
+
throw new Error(result.error || "Failed to rm");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
exists(path) {
|
|
383
|
+
const result = this.execSync(OpCode.EXISTS, path);
|
|
384
|
+
if (!result.success) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
return result.result?.[0] === 1;
|
|
388
|
+
}
|
|
389
|
+
appendFile(path, data) {
|
|
390
|
+
const result = this.execSync(OpCode.APPEND_FILE, path, data);
|
|
391
|
+
if (!result.success) {
|
|
392
|
+
throw new Error(result.error || "Failed to append file");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
symlink(target, linkPath) {
|
|
396
|
+
const targetData = new TextEncoder().encode(target);
|
|
397
|
+
const result = this.execSync(OpCode.SYMLINK, linkPath, targetData);
|
|
398
|
+
if (!result.success) {
|
|
399
|
+
throw new Error(result.error || "Failed to symlink");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
readlink(path) {
|
|
403
|
+
const result = this.execSync(OpCode.READLINK, path);
|
|
404
|
+
if (!result.success) {
|
|
405
|
+
throw new Error(result.error || "Failed to readlink");
|
|
406
|
+
}
|
|
407
|
+
return this.protocol.getResultAsString();
|
|
408
|
+
}
|
|
409
|
+
chmod(path, mode) {
|
|
410
|
+
const result = this.execSync(OpCode.CHMOD, path, void 0, 0, mode);
|
|
411
|
+
if (!result.success) {
|
|
412
|
+
throw new Error(result.error || "Failed to chmod");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
realpath(path) {
|
|
416
|
+
const result = this.execSync(OpCode.REALPATH, path);
|
|
417
|
+
if (!result.success) {
|
|
418
|
+
throw new Error(result.error || "Failed to realpath");
|
|
419
|
+
}
|
|
420
|
+
return this.protocol.getResultAsString();
|
|
421
|
+
}
|
|
422
|
+
writeStdout(data) {
|
|
423
|
+
const encoded = new TextEncoder().encode(data);
|
|
424
|
+
this.execSync(OpCode.WRITE_STDOUT, "", encoded);
|
|
425
|
+
}
|
|
426
|
+
writeStderr(data) {
|
|
427
|
+
const encoded = new TextEncoder().encode(data);
|
|
428
|
+
this.execSync(OpCode.WRITE_STDERR, "", encoded);
|
|
429
|
+
}
|
|
430
|
+
exit(code) {
|
|
431
|
+
this.execSync(OpCode.EXIT, "", void 0, code);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Make an HTTP request through the main thread's secureFetch.
|
|
435
|
+
* Returns the response as a parsed object.
|
|
436
|
+
*/
|
|
437
|
+
httpRequest(url, options) {
|
|
438
|
+
const requestData = options ? new TextEncoder().encode(JSON.stringify(options)) : void 0;
|
|
439
|
+
const result = this.execSync(OpCode.HTTP_REQUEST, url, requestData);
|
|
440
|
+
if (!result.success) {
|
|
441
|
+
throw new Error(result.error || "HTTP request failed");
|
|
442
|
+
}
|
|
443
|
+
const responseJson = new TextDecoder().decode(result.result);
|
|
444
|
+
return JSON.parse(responseJson);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/commands/python3/worker.ts
|
|
449
|
+
var pyodideInstance = null;
|
|
450
|
+
var pyodideLoading = null;
|
|
451
|
+
async function getPyodide() {
|
|
452
|
+
if (pyodideInstance) {
|
|
453
|
+
return pyodideInstance;
|
|
454
|
+
}
|
|
455
|
+
if (pyodideLoading) {
|
|
456
|
+
return pyodideLoading;
|
|
457
|
+
}
|
|
458
|
+
pyodideLoading = loadPyodide();
|
|
459
|
+
pyodideInstance = await pyodideLoading;
|
|
460
|
+
return pyodideInstance;
|
|
461
|
+
}
|
|
462
|
+
function createHOSTFS(backend, FS, PATH) {
|
|
463
|
+
const ERRNO_CODES = {
|
|
464
|
+
EPERM: 63,
|
|
465
|
+
ENOENT: 44,
|
|
466
|
+
EIO: 29,
|
|
467
|
+
EBADF: 8,
|
|
468
|
+
EAGAIN: 6,
|
|
469
|
+
EACCES: 2,
|
|
470
|
+
EBUSY: 10,
|
|
471
|
+
EEXIST: 20,
|
|
472
|
+
ENOTDIR: 54,
|
|
473
|
+
EISDIR: 31,
|
|
474
|
+
EINVAL: 28,
|
|
475
|
+
EMFILE: 33,
|
|
476
|
+
ENOSPC: 51,
|
|
477
|
+
ESPIPE: 70,
|
|
478
|
+
EROFS: 69,
|
|
479
|
+
ENOTEMPTY: 55,
|
|
480
|
+
ENOSYS: 52,
|
|
481
|
+
ENOTSUP: 138,
|
|
482
|
+
ENODATA: 42
|
|
483
|
+
};
|
|
484
|
+
function realPath(node) {
|
|
485
|
+
const parts = [];
|
|
486
|
+
while (node.parent !== node) {
|
|
487
|
+
parts.push(node.name);
|
|
488
|
+
node = node.parent;
|
|
489
|
+
}
|
|
490
|
+
parts.push(node.mount.opts.root);
|
|
491
|
+
parts.reverse();
|
|
492
|
+
return PATH.join(...parts);
|
|
493
|
+
}
|
|
494
|
+
function tryFSOperation(f) {
|
|
495
|
+
try {
|
|
496
|
+
return f();
|
|
497
|
+
} catch (e) {
|
|
498
|
+
const msg = e?.message?.toLowerCase() || (typeof e === "string" ? e.toLowerCase() : "");
|
|
499
|
+
let code = ERRNO_CODES.EIO;
|
|
500
|
+
if (msg.includes("no such file") || msg.includes("not found")) {
|
|
501
|
+
code = ERRNO_CODES.ENOENT;
|
|
502
|
+
} else if (msg.includes("is a directory")) {
|
|
503
|
+
code = ERRNO_CODES.EISDIR;
|
|
504
|
+
} else if (msg.includes("not a directory")) {
|
|
505
|
+
code = ERRNO_CODES.ENOTDIR;
|
|
506
|
+
} else if (msg.includes("already exists")) {
|
|
507
|
+
code = ERRNO_CODES.EEXIST;
|
|
508
|
+
} else if (msg.includes("permission")) {
|
|
509
|
+
code = ERRNO_CODES.EACCES;
|
|
510
|
+
} else if (msg.includes("not empty")) {
|
|
511
|
+
code = ERRNO_CODES.ENOTEMPTY;
|
|
512
|
+
}
|
|
513
|
+
throw new FS.ErrnoError(code);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function getMode(path) {
|
|
517
|
+
return tryFSOperation(() => {
|
|
518
|
+
const stat = backend.stat(path);
|
|
519
|
+
let mode = stat.mode & 511;
|
|
520
|
+
if (stat.isDirectory) {
|
|
521
|
+
mode |= 16384;
|
|
522
|
+
} else if (stat.isSymbolicLink) {
|
|
523
|
+
mode |= 40960;
|
|
524
|
+
} else {
|
|
525
|
+
mode |= 32768;
|
|
526
|
+
}
|
|
527
|
+
return mode;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const HOSTFS = {
|
|
531
|
+
mount(_mount) {
|
|
532
|
+
return HOSTFS.createNode(null, "/", 16877, 0);
|
|
533
|
+
},
|
|
534
|
+
createNode(parent, name, mode, dev) {
|
|
535
|
+
if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) {
|
|
536
|
+
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
|
|
537
|
+
}
|
|
538
|
+
const node = FS.createNode(parent, name, mode, dev);
|
|
539
|
+
node.node_ops = HOSTFS.node_ops;
|
|
540
|
+
node.stream_ops = HOSTFS.stream_ops;
|
|
541
|
+
return node;
|
|
542
|
+
},
|
|
543
|
+
node_ops: {
|
|
544
|
+
getattr(node) {
|
|
545
|
+
const path = realPath(node);
|
|
546
|
+
return tryFSOperation(() => {
|
|
547
|
+
const stat = backend.stat(path);
|
|
548
|
+
let mode = stat.mode & 511;
|
|
549
|
+
if (stat.isDirectory) {
|
|
550
|
+
mode |= 16384;
|
|
551
|
+
} else if (stat.isSymbolicLink) {
|
|
552
|
+
mode |= 40960;
|
|
553
|
+
} else {
|
|
554
|
+
mode |= 32768;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
dev: 1,
|
|
558
|
+
ino: node.id,
|
|
559
|
+
mode,
|
|
560
|
+
nlink: 1,
|
|
561
|
+
uid: 0,
|
|
562
|
+
gid: 0,
|
|
563
|
+
rdev: 0,
|
|
564
|
+
size: stat.size,
|
|
565
|
+
atime: stat.mtime,
|
|
566
|
+
mtime: stat.mtime,
|
|
567
|
+
ctime: stat.mtime,
|
|
568
|
+
blksize: 4096,
|
|
569
|
+
blocks: Math.ceil(stat.size / 512)
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
},
|
|
573
|
+
setattr(node, attr) {
|
|
574
|
+
const path = realPath(node);
|
|
575
|
+
const mode = attr.mode;
|
|
576
|
+
if (mode !== void 0) {
|
|
577
|
+
tryFSOperation(() => backend.chmod(path, mode));
|
|
578
|
+
node.mode = mode;
|
|
579
|
+
}
|
|
580
|
+
if (attr.size !== void 0) {
|
|
581
|
+
tryFSOperation(() => {
|
|
582
|
+
const content = backend.readFile(path);
|
|
583
|
+
const newContent = content.slice(0, attr.size);
|
|
584
|
+
backend.writeFile(path, newContent);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
lookup(parent, name) {
|
|
589
|
+
const path = PATH.join2(realPath(parent), name);
|
|
590
|
+
const mode = getMode(path);
|
|
591
|
+
return HOSTFS.createNode(parent, name, mode);
|
|
592
|
+
},
|
|
593
|
+
mknod(parent, name, mode, _dev) {
|
|
594
|
+
const node = HOSTFS.createNode(parent, name, mode, _dev);
|
|
595
|
+
const path = realPath(node);
|
|
596
|
+
tryFSOperation(() => {
|
|
597
|
+
if (FS.isDir(node.mode)) {
|
|
598
|
+
backend.mkdir(path, false);
|
|
599
|
+
} else {
|
|
600
|
+
backend.writeFile(path, new Uint8Array(0));
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
return node;
|
|
604
|
+
},
|
|
605
|
+
rename(oldNode, newDir, newName) {
|
|
606
|
+
const oldPath = realPath(oldNode);
|
|
607
|
+
const newPath = PATH.join2(realPath(newDir), newName);
|
|
608
|
+
tryFSOperation(() => {
|
|
609
|
+
const content = backend.readFile(oldPath);
|
|
610
|
+
backend.writeFile(newPath, content);
|
|
611
|
+
backend.rm(oldPath, false, false);
|
|
612
|
+
});
|
|
613
|
+
oldNode.name = newName;
|
|
614
|
+
},
|
|
615
|
+
unlink(parent, name) {
|
|
616
|
+
const path = PATH.join2(realPath(parent), name);
|
|
617
|
+
tryFSOperation(() => backend.rm(path, false, false));
|
|
618
|
+
},
|
|
619
|
+
rmdir(parent, name) {
|
|
620
|
+
const path = PATH.join2(realPath(parent), name);
|
|
621
|
+
tryFSOperation(() => backend.rm(path, false, false));
|
|
622
|
+
},
|
|
623
|
+
readdir(node) {
|
|
624
|
+
const path = realPath(node);
|
|
625
|
+
return tryFSOperation(() => backend.readdir(path));
|
|
626
|
+
},
|
|
627
|
+
symlink(parent, newName, oldPath) {
|
|
628
|
+
const newPath = PATH.join2(realPath(parent), newName);
|
|
629
|
+
tryFSOperation(() => backend.symlink(oldPath, newPath));
|
|
630
|
+
},
|
|
631
|
+
readlink(node) {
|
|
632
|
+
const path = realPath(node);
|
|
633
|
+
return tryFSOperation(() => backend.readlink(path));
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
stream_ops: {
|
|
637
|
+
open(stream) {
|
|
638
|
+
const path = realPath(stream.node);
|
|
639
|
+
const flags = stream.flags;
|
|
640
|
+
const O_WRONLY = 1;
|
|
641
|
+
const O_RDWR = 2;
|
|
642
|
+
const O_CREAT = 64;
|
|
643
|
+
const O_TRUNC = 512;
|
|
644
|
+
const O_APPEND = 1024;
|
|
645
|
+
const accessMode = flags & 3;
|
|
646
|
+
const isWrite = accessMode === O_WRONLY || accessMode === O_RDWR;
|
|
647
|
+
const isCreate = (flags & O_CREAT) !== 0;
|
|
648
|
+
const isTruncate = (flags & O_TRUNC) !== 0;
|
|
649
|
+
const isAppend = (flags & O_APPEND) !== 0;
|
|
650
|
+
if (FS.isDir(stream.node.mode)) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
let content;
|
|
654
|
+
try {
|
|
655
|
+
if (isTruncate && isWrite) {
|
|
656
|
+
content = new Uint8Array(0);
|
|
657
|
+
} else {
|
|
658
|
+
content = backend.readFile(path);
|
|
659
|
+
}
|
|
660
|
+
} catch (_e) {
|
|
661
|
+
if (isCreate && isWrite) {
|
|
662
|
+
content = new Uint8Array(0);
|
|
663
|
+
} else {
|
|
664
|
+
throw new FS.ErrnoError(ERRNO_CODES.ENOENT);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
stream.hostContent = content;
|
|
668
|
+
stream.hostModified = isTruncate && isWrite;
|
|
669
|
+
stream.hostPath = path;
|
|
670
|
+
if (isAppend) {
|
|
671
|
+
stream.position = content.length;
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
close(stream) {
|
|
675
|
+
const hostPath = stream.hostPath;
|
|
676
|
+
const hostContent = stream.hostContent;
|
|
677
|
+
if (stream.hostModified && hostContent && hostPath) {
|
|
678
|
+
tryFSOperation(() => backend.writeFile(hostPath, hostContent));
|
|
679
|
+
}
|
|
680
|
+
delete stream.hostContent;
|
|
681
|
+
delete stream.hostModified;
|
|
682
|
+
delete stream.hostPath;
|
|
683
|
+
},
|
|
684
|
+
read(stream, buffer, offset, length, position) {
|
|
685
|
+
const content = stream.hostContent;
|
|
686
|
+
if (!content) return 0;
|
|
687
|
+
const size = content.length;
|
|
688
|
+
if (position >= size) return 0;
|
|
689
|
+
const bytesToRead = Math.min(length, size - position);
|
|
690
|
+
buffer.set(content.subarray(position, position + bytesToRead), offset);
|
|
691
|
+
return bytesToRead;
|
|
692
|
+
},
|
|
693
|
+
write(stream, buffer, offset, length, position) {
|
|
694
|
+
let content = stream.hostContent || new Uint8Array(0);
|
|
695
|
+
const newSize = Math.max(content.length, position + length);
|
|
696
|
+
if (newSize > content.length) {
|
|
697
|
+
const newContent = new Uint8Array(newSize);
|
|
698
|
+
newContent.set(content);
|
|
699
|
+
content = newContent;
|
|
700
|
+
stream.hostContent = content;
|
|
701
|
+
}
|
|
702
|
+
content.set(buffer.subarray(offset, offset + length), position);
|
|
703
|
+
stream.hostModified = true;
|
|
704
|
+
return length;
|
|
705
|
+
},
|
|
706
|
+
llseek(stream, offset, whence) {
|
|
707
|
+
const SEEK_CUR = 1;
|
|
708
|
+
const SEEK_END = 2;
|
|
709
|
+
let position = offset;
|
|
710
|
+
if (whence === SEEK_CUR) {
|
|
711
|
+
position += stream.position;
|
|
712
|
+
} else if (whence === SEEK_END) {
|
|
713
|
+
if (FS.isFile(stream.node.mode)) {
|
|
714
|
+
const content = stream.hostContent;
|
|
715
|
+
position += content ? content.length : 0;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (position < 0) {
|
|
719
|
+
throw new FS.ErrnoError(ERRNO_CODES.EINVAL);
|
|
720
|
+
}
|
|
721
|
+
return position;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
return HOSTFS;
|
|
726
|
+
}
|
|
727
|
+
async function runPython(input) {
|
|
728
|
+
const backend = new SyncFsBackend(input.sharedBuffer);
|
|
729
|
+
let pyodide;
|
|
730
|
+
try {
|
|
731
|
+
pyodide = await getPyodide();
|
|
732
|
+
} catch (e) {
|
|
733
|
+
return {
|
|
734
|
+
success: false,
|
|
735
|
+
error: `Failed to load Pyodide: ${e.message}`
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
pyodide.setStdout({ batched: () => {
|
|
739
|
+
} });
|
|
740
|
+
pyodide.setStderr({ batched: () => {
|
|
741
|
+
} });
|
|
742
|
+
try {
|
|
743
|
+
pyodide.runPython(`
|
|
744
|
+
import sys
|
|
745
|
+
if hasattr(sys.stdout, 'flush'):
|
|
746
|
+
sys.stdout.flush()
|
|
747
|
+
if hasattr(sys.stderr, 'flush'):
|
|
748
|
+
sys.stderr.flush()
|
|
749
|
+
`);
|
|
750
|
+
} catch (_e) {
|
|
751
|
+
}
|
|
752
|
+
pyodide.setStdout({
|
|
753
|
+
batched: (text) => {
|
|
754
|
+
backend.writeStdout(`${text}
|
|
755
|
+
`);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
pyodide.setStderr({
|
|
759
|
+
batched: (text) => {
|
|
760
|
+
backend.writeStderr(`${text}
|
|
761
|
+
`);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
const FS = pyodide.FS;
|
|
765
|
+
const PATH = pyodide.PATH;
|
|
766
|
+
const HOSTFS = createHOSTFS(backend, FS, PATH);
|
|
767
|
+
try {
|
|
768
|
+
try {
|
|
769
|
+
pyodide.runPython(`import os; os.chdir('/')`);
|
|
770
|
+
} catch (_e) {
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
FS.mkdir("/host");
|
|
774
|
+
} catch (_e) {
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
FS.unmount("/host");
|
|
778
|
+
} catch (_e) {
|
|
779
|
+
}
|
|
780
|
+
FS.mount(HOSTFS, { root: "/" }, "/host");
|
|
781
|
+
} catch (e) {
|
|
782
|
+
return {
|
|
783
|
+
success: false,
|
|
784
|
+
error: `Failed to mount HOSTFS: ${e.message}`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
pyodide.runPython(`
|
|
789
|
+
import sys
|
|
790
|
+
if '_jb_http_bridge' in sys.modules:
|
|
791
|
+
del sys.modules['_jb_http_bridge']
|
|
792
|
+
if 'jb_http' in sys.modules:
|
|
793
|
+
del sys.modules['jb_http']
|
|
794
|
+
`);
|
|
795
|
+
} catch (_e) {
|
|
796
|
+
}
|
|
797
|
+
pyodide.registerJsModule("_jb_http_bridge", {
|
|
798
|
+
request: (url, method, headersJson, body) => {
|
|
799
|
+
try {
|
|
800
|
+
const headers = headersJson ? JSON.parse(headersJson) : void 0;
|
|
801
|
+
const result = backend.httpRequest(url, {
|
|
802
|
+
method: method || "GET",
|
|
803
|
+
headers,
|
|
804
|
+
body: body || void 0
|
|
805
|
+
});
|
|
806
|
+
return JSON.stringify(result);
|
|
807
|
+
} catch (e) {
|
|
808
|
+
return JSON.stringify({ error: e.message });
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
const envSetup = Object.entries(input.env).map(([key, value]) => {
|
|
813
|
+
return `os.environ[${JSON.stringify(key)}] = ${JSON.stringify(value)}`;
|
|
814
|
+
}).join("\n");
|
|
815
|
+
const argv0 = input.scriptPath || "python3";
|
|
816
|
+
const argvList = [argv0, ...input.args].map((arg) => JSON.stringify(arg)).join(", ");
|
|
817
|
+
try {
|
|
818
|
+
await pyodide.runPythonAsync(`
|
|
819
|
+
import os
|
|
820
|
+
import sys
|
|
821
|
+
import builtins
|
|
822
|
+
import json
|
|
823
|
+
|
|
824
|
+
${envSetup}
|
|
825
|
+
|
|
826
|
+
sys.argv = [${argvList}]
|
|
827
|
+
|
|
828
|
+
# Create jb_http module for HTTP requests
|
|
829
|
+
class _JbHttpResponse:
|
|
830
|
+
"""HTTP response object similar to requests.Response"""
|
|
831
|
+
def __init__(self, data):
|
|
832
|
+
self.status_code = data.get('status', 0)
|
|
833
|
+
self.reason = data.get('statusText', '')
|
|
834
|
+
self.headers = data.get('headers', {})
|
|
835
|
+
self.text = data.get('body', '')
|
|
836
|
+
self.url = data.get('url', '')
|
|
837
|
+
self._error = data.get('error')
|
|
838
|
+
|
|
839
|
+
@property
|
|
840
|
+
def ok(self):
|
|
841
|
+
return 200 <= self.status_code < 300
|
|
842
|
+
|
|
843
|
+
def json(self):
|
|
844
|
+
return json.loads(self.text)
|
|
845
|
+
|
|
846
|
+
def raise_for_status(self):
|
|
847
|
+
if self._error:
|
|
848
|
+
raise Exception(self._error)
|
|
849
|
+
if not self.ok:
|
|
850
|
+
raise Exception(f"HTTP {self.status_code}: {self.reason}")
|
|
851
|
+
|
|
852
|
+
class _JbHttp:
|
|
853
|
+
"""HTTP client that bridges to just-bash's secureFetch"""
|
|
854
|
+
def request(self, method, url, headers=None, data=None, json_data=None):
|
|
855
|
+
# Import fresh each time to ensure we use the current bridge
|
|
856
|
+
# (important when worker is reused with different SharedArrayBuffer)
|
|
857
|
+
import _jb_http_bridge
|
|
858
|
+
if json_data is not None:
|
|
859
|
+
data = json.dumps(json_data)
|
|
860
|
+
headers = headers or {}
|
|
861
|
+
headers['Content-Type'] = 'application/json'
|
|
862
|
+
# Serialize headers to JSON to avoid PyProxy issues when passing to JS
|
|
863
|
+
headers_json = json.dumps(headers) if headers else None
|
|
864
|
+
result_json = _jb_http_bridge.request(url, method, headers_json, data)
|
|
865
|
+
result = json.loads(result_json)
|
|
866
|
+
# Check for errors from the bridge (network not configured, URL not allowed, etc.)
|
|
867
|
+
if 'error' in result and result.get('status') is None:
|
|
868
|
+
raise Exception(result['error'])
|
|
869
|
+
return _JbHttpResponse(result)
|
|
870
|
+
|
|
871
|
+
def get(self, url, headers=None, **kwargs):
|
|
872
|
+
return self.request('GET', url, headers=headers, **kwargs)
|
|
873
|
+
|
|
874
|
+
def post(self, url, headers=None, data=None, json=None, **kwargs):
|
|
875
|
+
return self.request('POST', url, headers=headers, data=data, json_data=json, **kwargs)
|
|
876
|
+
|
|
877
|
+
def put(self, url, headers=None, data=None, json=None, **kwargs):
|
|
878
|
+
return self.request('PUT', url, headers=headers, data=data, json_data=json, **kwargs)
|
|
879
|
+
|
|
880
|
+
def delete(self, url, headers=None, **kwargs):
|
|
881
|
+
return self.request('DELETE', url, headers=headers, **kwargs)
|
|
882
|
+
|
|
883
|
+
def head(self, url, headers=None, **kwargs):
|
|
884
|
+
return self.request('HEAD', url, headers=headers, **kwargs)
|
|
885
|
+
|
|
886
|
+
def patch(self, url, headers=None, data=None, json=None, **kwargs):
|
|
887
|
+
return self.request('PATCH', url, headers=headers, data=data, json_data=json, **kwargs)
|
|
888
|
+
|
|
889
|
+
# Register jb_http as an importable module
|
|
890
|
+
import types
|
|
891
|
+
jb_http = types.ModuleType('jb_http')
|
|
892
|
+
jb_http._client = _JbHttp()
|
|
893
|
+
jb_http.get = jb_http._client.get
|
|
894
|
+
jb_http.post = jb_http._client.post
|
|
895
|
+
jb_http.put = jb_http._client.put
|
|
896
|
+
jb_http.delete = jb_http._client.delete
|
|
897
|
+
jb_http.head = jb_http._client.head
|
|
898
|
+
jb_http.patch = jb_http._client.patch
|
|
899
|
+
jb_http.request = jb_http._client.request
|
|
900
|
+
jb_http.Response = _JbHttpResponse
|
|
901
|
+
sys.modules['jb_http'] = jb_http
|
|
902
|
+
|
|
903
|
+
# Redirect root paths to /host for file operations
|
|
904
|
+
# Only patch once - check if already patched
|
|
905
|
+
if not hasattr(builtins, '_jb_original_open'):
|
|
906
|
+
builtins._jb_original_open = builtins.open
|
|
907
|
+
|
|
908
|
+
def _redirected_open(path, mode='r', *args, **kwargs):
|
|
909
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
910
|
+
path = '/host' + path
|
|
911
|
+
return builtins._jb_original_open(path, mode, *args, **kwargs)
|
|
912
|
+
builtins.open = _redirected_open
|
|
913
|
+
|
|
914
|
+
os._jb_original_listdir = os.listdir
|
|
915
|
+
def _redirected_listdir(path='.'):
|
|
916
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
917
|
+
path = '/host' + path
|
|
918
|
+
return os._jb_original_listdir(path)
|
|
919
|
+
os.listdir = _redirected_listdir
|
|
920
|
+
|
|
921
|
+
os.path._jb_original_exists = os.path.exists
|
|
922
|
+
def _redirected_exists(path):
|
|
923
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
924
|
+
path = '/host' + path
|
|
925
|
+
return os.path._jb_original_exists(path)
|
|
926
|
+
os.path.exists = _redirected_exists
|
|
927
|
+
|
|
928
|
+
os.path._jb_original_isfile = os.path.isfile
|
|
929
|
+
def _redirected_isfile(path):
|
|
930
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
931
|
+
path = '/host' + path
|
|
932
|
+
return os.path._jb_original_isfile(path)
|
|
933
|
+
os.path.isfile = _redirected_isfile
|
|
934
|
+
|
|
935
|
+
os.path._jb_original_isdir = os.path.isdir
|
|
936
|
+
def _redirected_isdir(path):
|
|
937
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
938
|
+
path = '/host' + path
|
|
939
|
+
return os.path._jb_original_isdir(path)
|
|
940
|
+
os.path.isdir = _redirected_isdir
|
|
941
|
+
|
|
942
|
+
os._jb_original_stat = os.stat
|
|
943
|
+
def _redirected_stat(path, *args, **kwargs):
|
|
944
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
945
|
+
path = '/host' + path
|
|
946
|
+
return os._jb_original_stat(path, *args, **kwargs)
|
|
947
|
+
os.stat = _redirected_stat
|
|
948
|
+
|
|
949
|
+
os._jb_original_mkdir = os.mkdir
|
|
950
|
+
def _redirected_mkdir(path, *args, **kwargs):
|
|
951
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
952
|
+
path = '/host' + path
|
|
953
|
+
return os._jb_original_mkdir(path, *args, **kwargs)
|
|
954
|
+
os.mkdir = _redirected_mkdir
|
|
955
|
+
|
|
956
|
+
os._jb_original_makedirs = os.makedirs
|
|
957
|
+
def _redirected_makedirs(path, *args, **kwargs):
|
|
958
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
959
|
+
path = '/host' + path
|
|
960
|
+
return os._jb_original_makedirs(path, *args, **kwargs)
|
|
961
|
+
os.makedirs = _redirected_makedirs
|
|
962
|
+
|
|
963
|
+
os._jb_original_remove = os.remove
|
|
964
|
+
def _redirected_remove(path, *args, **kwargs):
|
|
965
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
966
|
+
path = '/host' + path
|
|
967
|
+
return os._jb_original_remove(path, *args, **kwargs)
|
|
968
|
+
os.remove = _redirected_remove
|
|
969
|
+
|
|
970
|
+
os._jb_original_rmdir = os.rmdir
|
|
971
|
+
def _redirected_rmdir(path, *args, **kwargs):
|
|
972
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
973
|
+
path = '/host' + path
|
|
974
|
+
return os._jb_original_rmdir(path, *args, **kwargs)
|
|
975
|
+
os.rmdir = _redirected_rmdir
|
|
976
|
+
|
|
977
|
+
# Patch os.getcwd to strip /host prefix
|
|
978
|
+
os._jb_original_getcwd = os.getcwd
|
|
979
|
+
def _redirected_getcwd():
|
|
980
|
+
cwd = os._jb_original_getcwd()
|
|
981
|
+
if cwd.startswith('/host'):
|
|
982
|
+
return cwd[5:] # Strip '/host' prefix
|
|
983
|
+
return cwd
|
|
984
|
+
os.getcwd = _redirected_getcwd
|
|
985
|
+
|
|
986
|
+
# Patch os.chdir to add /host prefix
|
|
987
|
+
os._jb_original_chdir = os.chdir
|
|
988
|
+
def _redirected_chdir(path):
|
|
989
|
+
if isinstance(path, str) and path.startswith('/') and not path.startswith('/lib') and not path.startswith('/proc') and not path.startswith('/host'):
|
|
990
|
+
path = '/host' + path
|
|
991
|
+
return os._jb_original_chdir(path)
|
|
992
|
+
os.chdir = _redirected_chdir
|
|
993
|
+
|
|
994
|
+
# Set cwd to host mount
|
|
995
|
+
os.chdir('/host' + ${JSON.stringify(input.cwd)})
|
|
996
|
+
`);
|
|
997
|
+
} catch (e) {
|
|
998
|
+
return {
|
|
999
|
+
success: false,
|
|
1000
|
+
error: `Failed to set up environment: ${e.message}`
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
const wrappedCode = `
|
|
1005
|
+
import sys
|
|
1006
|
+
_jb_exit_code = 0
|
|
1007
|
+
try:
|
|
1008
|
+
${input.pythonCode.split("\n").map((line) => ` ${line}`).join("\n")}
|
|
1009
|
+
except SystemExit as e:
|
|
1010
|
+
_jb_exit_code = e.code if isinstance(e.code, int) else (1 if e.code else 0)
|
|
1011
|
+
`;
|
|
1012
|
+
await pyodide.runPythonAsync(wrappedCode);
|
|
1013
|
+
const exitCode = pyodide.globals.get("_jb_exit_code");
|
|
1014
|
+
backend.exit(exitCode);
|
|
1015
|
+
return { success: true };
|
|
1016
|
+
} catch (e) {
|
|
1017
|
+
const error = e;
|
|
1018
|
+
backend.writeStderr(`${error.message}
|
|
1019
|
+
`);
|
|
1020
|
+
backend.exit(1);
|
|
1021
|
+
return { success: true };
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (parentPort) {
|
|
1025
|
+
if (workerData) {
|
|
1026
|
+
runPython(workerData).then((result) => {
|
|
1027
|
+
parentPort?.postMessage(result);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
parentPort.on("message", async (input) => {
|
|
1031
|
+
try {
|
|
1032
|
+
const result = await runPython(input);
|
|
1033
|
+
parentPort?.postMessage(result);
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
parentPort?.postMessage({ success: false, error: e.message });
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
}
|