@poncho-ai/harness 0.35.0 → 0.36.1
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/.turbo/turbo-build.log +12 -11
- package/CHANGELOG.md +25 -0
- package/dist/index.d.ts +485 -29
- package/dist/index.js +2839 -2114
- package/dist/isolate-TCWTUVG4.js +1532 -0
- package/package.json +23 -4
- package/scripts/migrate-to-engine.mjs +556 -0
- package/src/config.ts +106 -1
- package/src/harness.ts +226 -91
- package/src/index.ts +5 -0
- package/src/isolate/bindings.ts +206 -0
- package/src/isolate/bundler.ts +179 -0
- package/src/isolate/index.ts +10 -0
- package/src/isolate/polyfills.ts +796 -0
- package/src/isolate/run-code-tool.ts +220 -0
- package/src/isolate/runtime.ts +286 -0
- package/src/isolate/type-stubs.ts +196 -0
- package/src/memory.ts +129 -198
- package/src/reminder-store.ts +3 -237
- package/src/secrets-store.ts +2 -91
- package/src/state.ts +11 -1302
- package/src/storage/engine.ts +106 -0
- package/src/storage/index.ts +59 -0
- package/src/storage/memory-engine.ts +588 -0
- package/src/storage/postgres-engine.ts +139 -0
- package/src/storage/schema.ts +145 -0
- package/src/storage/sql-dialect.ts +963 -0
- package/src/storage/sqlite-engine.ts +99 -0
- package/src/storage/store-adapters.ts +100 -0
- package/src/todo-tools.ts +1 -136
- package/src/upload-store.ts +1 -0
- package/src/vfs/bash-manager.ts +120 -0
- package/src/vfs/bash-tool.ts +59 -0
- package/src/vfs/create-bash-fs.ts +32 -0
- package/src/vfs/edit-file-tool.ts +72 -0
- package/src/vfs/index.ts +5 -0
- package/src/vfs/poncho-fs-adapter.ts +267 -0
- package/src/vfs/protected-fs.ts +177 -0
- package/src/vfs/read-file-tool.ts +103 -0
- package/src/vfs/write-file-tool.ts +49 -0
- package/test/harness.test.ts +30 -36
- package/test/isolate-vfs.test.ts +453 -0
- package/test/isolate.test.ts +252 -0
- package/test/state.test.ts +4 -27
- package/test/storage-engine.test.ts +250 -0
- package/test/vfs.test.ts +242 -0
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -11931
- package/src/kv-store.ts +0 -216
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Isolate Polyfills – standard Web/Node.js API layer injected into V8 isolates.
|
|
3
|
+
//
|
|
4
|
+
// Wraps the __poncho_* internal bindings into standard APIs so agent code
|
|
5
|
+
// can use fetch(), fs.readFileSync(), Buffer, etc. naturally.
|
|
6
|
+
//
|
|
7
|
+
// Execution order in the isolate:
|
|
8
|
+
// 1. Runtime preamble (console capture)
|
|
9
|
+
// 2. Binding wrappers (__poncho_fs_read, __poncho_fetch, etc.)
|
|
10
|
+
// 3. This polyfill layer (standard APIs)
|
|
11
|
+
// 4. Library preamble (bundled npm packages)
|
|
12
|
+
// 5. User code
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns JavaScript source for the polyfill layer.
|
|
17
|
+
* `hasNetwork` controls whether the fetch() polyfill is included.
|
|
18
|
+
*/
|
|
19
|
+
export function buildPolyfillPreamble(hasNetwork: boolean): string {
|
|
20
|
+
return [
|
|
21
|
+
POLYFILL_TEXT_ENCODING,
|
|
22
|
+
POLYFILL_ATOB_BTOA,
|
|
23
|
+
POLYFILL_BUFFER,
|
|
24
|
+
POLYFILL_PATH,
|
|
25
|
+
POLYFILL_FS,
|
|
26
|
+
hasNetwork ? POLYFILL_FETCH : POLYFILL_FETCH_STUB,
|
|
27
|
+
POLYFILL_TIMERS,
|
|
28
|
+
POLYFILL_CRYPTO,
|
|
29
|
+
POLYFILL_CONSOLE_EXTRAS,
|
|
30
|
+
POLYFILL_BLOB,
|
|
31
|
+
POLYFILL_STRUCTUREDCLONE,
|
|
32
|
+
].join("\n\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// TextEncoder / TextDecoder (V8 isolates don't have these)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
const POLYFILL_TEXT_ENCODING = `
|
|
40
|
+
// --- TextEncoder / TextDecoder polyfill ---
|
|
41
|
+
(function() {
|
|
42
|
+
if (typeof globalThis.TextEncoder === "undefined") {
|
|
43
|
+
globalThis.TextEncoder = class TextEncoder {
|
|
44
|
+
encode(str) {
|
|
45
|
+
str = String(str);
|
|
46
|
+
const buf = [];
|
|
47
|
+
for (let i = 0; i < str.length; i++) {
|
|
48
|
+
let code = str.charCodeAt(i);
|
|
49
|
+
if (code < 0x80) {
|
|
50
|
+
buf.push(code);
|
|
51
|
+
} else if (code < 0x800) {
|
|
52
|
+
buf.push(0xC0 | (code >> 6), 0x80 | (code & 0x3F));
|
|
53
|
+
} else if (code >= 0xD800 && code <= 0xDBFF && i + 1 < str.length) {
|
|
54
|
+
const next = str.charCodeAt(i + 1);
|
|
55
|
+
if (next >= 0xDC00 && next <= 0xDFFF) {
|
|
56
|
+
code = ((code - 0xD800) << 10) + (next - 0xDC00) + 0x10000;
|
|
57
|
+
i++;
|
|
58
|
+
buf.push(0xF0 | (code >> 18), 0x80 | ((code >> 12) & 0x3F), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
buf.push(0xE0 | (code >> 12), 0x80 | ((code >> 6) & 0x3F), 0x80 | (code & 0x3F));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return new Uint8Array(buf);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof globalThis.TextDecoder === "undefined") {
|
|
70
|
+
globalThis.TextDecoder = class TextDecoder {
|
|
71
|
+
decode(input) {
|
|
72
|
+
if (!input || input.length === 0) return "";
|
|
73
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
74
|
+
let result = "";
|
|
75
|
+
for (let i = 0; i < bytes.length;) {
|
|
76
|
+
const b = bytes[i];
|
|
77
|
+
if (b < 0x80) {
|
|
78
|
+
result += String.fromCharCode(b);
|
|
79
|
+
i++;
|
|
80
|
+
} else if ((b & 0xE0) === 0xC0) {
|
|
81
|
+
result += String.fromCharCode(((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F));
|
|
82
|
+
i += 2;
|
|
83
|
+
} else if ((b & 0xF0) === 0xE0) {
|
|
84
|
+
result += String.fromCharCode(((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F));
|
|
85
|
+
i += 3;
|
|
86
|
+
} else if ((b & 0xF8) === 0xF0) {
|
|
87
|
+
const code = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F);
|
|
88
|
+
const adjusted = code - 0x10000;
|
|
89
|
+
result += String.fromCharCode(0xD800 + (adjusted >> 10), 0xDC00 + (adjusted & 0x3FF));
|
|
90
|
+
i += 4;
|
|
91
|
+
} else {
|
|
92
|
+
result += "\\uFFFD";
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Buffer
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
const POLYFILL_BUFFER = `
|
|
108
|
+
// --- Buffer polyfill ---
|
|
109
|
+
(function() {
|
|
110
|
+
if (typeof globalThis.Buffer !== "undefined") return;
|
|
111
|
+
|
|
112
|
+
class Buffer extends Uint8Array {
|
|
113
|
+
static from(input, encodingOrOffset, length) {
|
|
114
|
+
if (typeof input === "string") {
|
|
115
|
+
const encoding = (encodingOrOffset || "utf-8").toLowerCase();
|
|
116
|
+
if (encoding === "base64") {
|
|
117
|
+
const bin = atob(input);
|
|
118
|
+
const arr = new Uint8Array(bin.length);
|
|
119
|
+
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
|
120
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
121
|
+
}
|
|
122
|
+
if (encoding === "hex") {
|
|
123
|
+
const arr = new Uint8Array(input.length / 2);
|
|
124
|
+
for (let i = 0; i < input.length; i += 2)
|
|
125
|
+
arr[i / 2] = parseInt(input.slice(i, i + 2), 16);
|
|
126
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
127
|
+
}
|
|
128
|
+
// utf-8 default
|
|
129
|
+
const encoded = new TextEncoder().encode(input);
|
|
130
|
+
return Object.setPrototypeOf(encoded, Buffer.prototype);
|
|
131
|
+
}
|
|
132
|
+
if (input instanceof ArrayBuffer) {
|
|
133
|
+
const arr = new Uint8Array(input, encodingOrOffset, length);
|
|
134
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
135
|
+
}
|
|
136
|
+
if (ArrayBuffer.isView(input)) {
|
|
137
|
+
const arr = new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
138
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(input)) {
|
|
141
|
+
const arr = new Uint8Array(input);
|
|
142
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
143
|
+
}
|
|
144
|
+
throw new TypeError("Buffer.from: unsupported input type");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
static alloc(size, fill) {
|
|
148
|
+
const arr = new Uint8Array(size);
|
|
149
|
+
if (fill !== undefined) {
|
|
150
|
+
const fillByte = typeof fill === "number" ? fill : (typeof fill === "string" ? fill.charCodeAt(0) : 0);
|
|
151
|
+
arr.fill(fillByte);
|
|
152
|
+
}
|
|
153
|
+
return Object.setPrototypeOf(arr, Buffer.prototype);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static allocUnsafe(size) { return Buffer.alloc(size); }
|
|
157
|
+
|
|
158
|
+
static concat(list, totalLength) {
|
|
159
|
+
if (!totalLength) totalLength = list.reduce((s, b) => s + b.length, 0);
|
|
160
|
+
const result = Buffer.alloc(totalLength);
|
|
161
|
+
let offset = 0;
|
|
162
|
+
for (const buf of list) {
|
|
163
|
+
result.set(buf, offset);
|
|
164
|
+
offset += buf.length;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static isBuffer(obj) { return obj instanceof Buffer || obj instanceof Uint8Array; }
|
|
170
|
+
|
|
171
|
+
toString(encoding) {
|
|
172
|
+
encoding = (encoding || "utf-8").toLowerCase();
|
|
173
|
+
if (encoding === "base64") {
|
|
174
|
+
let bin = "";
|
|
175
|
+
for (let i = 0; i < this.length; i++) bin += String.fromCharCode(this[i]);
|
|
176
|
+
return btoa(bin);
|
|
177
|
+
}
|
|
178
|
+
if (encoding === "hex") {
|
|
179
|
+
return Array.from(this).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
180
|
+
}
|
|
181
|
+
return new TextDecoder().decode(this);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
toJSON() {
|
|
185
|
+
return { type: "Buffer", data: Array.from(this) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
write(string, offset, length, encoding) {
|
|
189
|
+
offset = offset || 0;
|
|
190
|
+
const encoded = Buffer.from(string, encoding || "utf-8");
|
|
191
|
+
const bytesToCopy = Math.min(encoded.length, length || this.length - offset);
|
|
192
|
+
this.set(encoded.subarray(0, bytesToCopy), offset);
|
|
193
|
+
return bytesToCopy;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
copy(target, targetStart, sourceStart, sourceEnd) {
|
|
197
|
+
targetStart = targetStart || 0;
|
|
198
|
+
sourceStart = sourceStart || 0;
|
|
199
|
+
sourceEnd = sourceEnd || this.length;
|
|
200
|
+
const slice = this.subarray(sourceStart, sourceEnd);
|
|
201
|
+
target.set(slice, targetStart);
|
|
202
|
+
return slice.length;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
equals(other) {
|
|
206
|
+
if (this.length !== other.length) return false;
|
|
207
|
+
for (let i = 0; i < this.length; i++) {
|
|
208
|
+
if (this[i] !== other[i]) return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
slice(start, end) {
|
|
214
|
+
const sliced = Uint8Array.prototype.slice.call(this, start, end);
|
|
215
|
+
return Object.setPrototypeOf(sliced, Buffer.prototype);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
globalThis.Buffer = Buffer;
|
|
220
|
+
})();
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// atob / btoa
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
const POLYFILL_ATOB_BTOA = `
|
|
228
|
+
// --- atob / btoa polyfill ---
|
|
229
|
+
(function() {
|
|
230
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
231
|
+
const B64_LOOKUP = new Uint8Array(128);
|
|
232
|
+
for (let i = 0; i < B64.length; i++) B64_LOOKUP[B64.charCodeAt(i)] = i;
|
|
233
|
+
|
|
234
|
+
if (typeof globalThis.atob === "undefined") {
|
|
235
|
+
globalThis.atob = function(input) {
|
|
236
|
+
input = String(input).replace(/[\\s=]+/g, "");
|
|
237
|
+
let output = "";
|
|
238
|
+
let bits = 0, collected = 0;
|
|
239
|
+
for (let i = 0; i < input.length; i++) {
|
|
240
|
+
bits = (bits << 6) | B64_LOOKUP[input.charCodeAt(i)];
|
|
241
|
+
collected += 6;
|
|
242
|
+
if (collected >= 8) {
|
|
243
|
+
collected -= 8;
|
|
244
|
+
output += String.fromCharCode((bits >> collected) & 0xFF);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return output;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (typeof globalThis.btoa === "undefined") {
|
|
252
|
+
globalThis.btoa = function(input) {
|
|
253
|
+
input = String(input);
|
|
254
|
+
let output = "";
|
|
255
|
+
for (let i = 0; i < input.length; i += 3) {
|
|
256
|
+
const a = input.charCodeAt(i);
|
|
257
|
+
const b = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
|
|
258
|
+
const c = i + 2 < input.length ? input.charCodeAt(i + 2) : 0;
|
|
259
|
+
output += B64[a >> 2];
|
|
260
|
+
output += B64[((a & 3) << 4) | (b >> 4)];
|
|
261
|
+
output += i + 1 < input.length ? B64[((b & 15) << 2) | (c >> 6)] : "=";
|
|
262
|
+
output += i + 2 < input.length ? B64[c & 63] : "=";
|
|
263
|
+
}
|
|
264
|
+
return output;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// path module
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
const POLYFILL_PATH = `
|
|
275
|
+
// --- path module polyfill ---
|
|
276
|
+
(function() {
|
|
277
|
+
const path = {
|
|
278
|
+
sep: "/",
|
|
279
|
+
join: function() {
|
|
280
|
+
const parts = Array.from(arguments).filter(Boolean);
|
|
281
|
+
return path.normalize(parts.join("/"));
|
|
282
|
+
},
|
|
283
|
+
resolve: function() {
|
|
284
|
+
let resolved = "";
|
|
285
|
+
for (let i = arguments.length - 1; i >= 0; i--) {
|
|
286
|
+
const part = arguments[i];
|
|
287
|
+
if (!part) continue;
|
|
288
|
+
resolved = part + (resolved ? "/" + resolved : "");
|
|
289
|
+
if (part.startsWith("/")) break;
|
|
290
|
+
}
|
|
291
|
+
return path.normalize(resolved.startsWith("/") ? resolved : "/" + resolved);
|
|
292
|
+
},
|
|
293
|
+
normalize: function(p) {
|
|
294
|
+
const parts = p.split("/");
|
|
295
|
+
const result = [];
|
|
296
|
+
for (const part of parts) {
|
|
297
|
+
if (part === "." || part === "") continue;
|
|
298
|
+
if (part === ".." && result.length > 0 && result[result.length - 1] !== "..") {
|
|
299
|
+
result.pop();
|
|
300
|
+
} else {
|
|
301
|
+
result.push(part);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return (p.startsWith("/") ? "/" : "") + result.join("/");
|
|
305
|
+
},
|
|
306
|
+
basename: function(p, ext) {
|
|
307
|
+
const base = p.split("/").filter(Boolean).pop() || "";
|
|
308
|
+
if (ext && base.endsWith(ext)) return base.slice(0, -ext.length);
|
|
309
|
+
return base;
|
|
310
|
+
},
|
|
311
|
+
dirname: function(p) {
|
|
312
|
+
const parts = p.split("/").filter(Boolean);
|
|
313
|
+
parts.pop();
|
|
314
|
+
return (p.startsWith("/") ? "/" : "") + parts.join("/") || ".";
|
|
315
|
+
},
|
|
316
|
+
extname: function(p) {
|
|
317
|
+
const base = path.basename(p);
|
|
318
|
+
const dot = base.lastIndexOf(".");
|
|
319
|
+
return dot > 0 ? base.slice(dot) : "";
|
|
320
|
+
},
|
|
321
|
+
isAbsolute: function(p) { return p.startsWith("/"); },
|
|
322
|
+
parse: function(p) {
|
|
323
|
+
return {
|
|
324
|
+
root: p.startsWith("/") ? "/" : "",
|
|
325
|
+
dir: path.dirname(p),
|
|
326
|
+
base: path.basename(p),
|
|
327
|
+
ext: path.extname(p),
|
|
328
|
+
name: path.basename(p, path.extname(p)),
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
globalThis.__modules = globalThis.__modules || {};
|
|
333
|
+
globalThis.__modules.path = path;
|
|
334
|
+
globalThis.path = path;
|
|
335
|
+
})();
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// fs module (wraps __poncho_fs_* bindings)
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
const POLYFILL_FS = `
|
|
343
|
+
// --- fs module polyfill ---
|
|
344
|
+
(function() {
|
|
345
|
+
function _b64ToUint8(b64) {
|
|
346
|
+
const bin = atob(b64);
|
|
347
|
+
const arr = new Uint8Array(bin.length);
|
|
348
|
+
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
|
349
|
+
return arr;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function _uint8ToB64(u8) {
|
|
353
|
+
let bin = "";
|
|
354
|
+
for (let i = 0; i < u8.length; i++) bin += String.fromCharCode(u8[i]);
|
|
355
|
+
return btoa(bin);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function _toBuffer(data, encoding) {
|
|
359
|
+
if (typeof data === "string") return Buffer.from(data, encoding || "utf-8");
|
|
360
|
+
if (data instanceof Uint8Array) return data;
|
|
361
|
+
return Buffer.from(String(data), "utf-8");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const fs = {
|
|
365
|
+
// --- Async API ---
|
|
366
|
+
readFile: async function(path, options) {
|
|
367
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
368
|
+
if (!encoding || encoding === "buffer") {
|
|
369
|
+
const b64 = await __poncho_fs_read_binary({ path });
|
|
370
|
+
return Buffer.from(_b64ToUint8(b64));
|
|
371
|
+
}
|
|
372
|
+
return await __poncho_fs_read({ path });
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
writeFile: async function(path, data, options) {
|
|
376
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
377
|
+
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
378
|
+
const u8 = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
379
|
+
await __poncho_fs_write_binary({ path, content: _uint8ToB64(u8) });
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (encoding === "base64") {
|
|
383
|
+
await __poncho_fs_write_binary({ path, content: data });
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
await __poncho_fs_write({ path, content: String(data) });
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
readdir: async function(path) {
|
|
390
|
+
return await __poncho_fs_list({ path: path || "/" });
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
mkdir: async function(path, options) {
|
|
394
|
+
await __poncho_fs_mkdir({ path });
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
stat: async function(path) {
|
|
398
|
+
const s = await __poncho_fs_stat({ path });
|
|
399
|
+
return {
|
|
400
|
+
isFile: function() { return s.isFile; },
|
|
401
|
+
isDirectory: function() { return s.isDirectory; },
|
|
402
|
+
size: s.size,
|
|
403
|
+
mtime: new Date(s.mtime),
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
unlink: async function(path) {
|
|
408
|
+
await __poncho_fs_delete({ path });
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
rm: async function(path) {
|
|
412
|
+
await __poncho_fs_delete({ path });
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
exists: async function(path) {
|
|
416
|
+
return await __poncho_fs_exists({ path });
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// --- Sync-style API (actually async under the hood, but works with await) ---
|
|
420
|
+
readFileSync: function(path, options) {
|
|
421
|
+
return fs.readFile(path, options);
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
writeFileSync: function(path, data, options) {
|
|
425
|
+
return fs.writeFile(path, data, options);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
readdirSync: function(path) { return fs.readdir(path); },
|
|
429
|
+
mkdirSync: function(path, options) { return fs.mkdir(path, options); },
|
|
430
|
+
statSync: function(path) { return fs.stat(path); },
|
|
431
|
+
unlinkSync: function(path) { return fs.unlink(path); },
|
|
432
|
+
existsSync: function(path) { return fs.exists(path); },
|
|
433
|
+
rmSync: function(path) { return fs.rm(path); },
|
|
434
|
+
|
|
435
|
+
promises: {},
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// fs.promises mirrors the async API
|
|
439
|
+
for (const key of Object.keys(fs)) {
|
|
440
|
+
if (typeof fs[key] === "function" && key !== "promises") {
|
|
441
|
+
fs.promises[key] = fs[key];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
globalThis.__modules = globalThis.__modules || {};
|
|
446
|
+
globalThis.__modules.fs = fs;
|
|
447
|
+
globalThis.__modules["fs/promises"] = fs.promises;
|
|
448
|
+
globalThis.__modules["node:fs"] = fs;
|
|
449
|
+
globalThis.__modules["node:fs/promises"] = fs.promises;
|
|
450
|
+
globalThis.fs = fs;
|
|
451
|
+
})();
|
|
452
|
+
`;
|
|
453
|
+
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
// fetch() polyfill (standard Web API wrapping __poncho_fetch)
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
|
|
458
|
+
const POLYFILL_FETCH = `
|
|
459
|
+
// --- fetch polyfill ---
|
|
460
|
+
(function() {
|
|
461
|
+
class Headers {
|
|
462
|
+
constructor(init) {
|
|
463
|
+
this._map = {};
|
|
464
|
+
if (init) {
|
|
465
|
+
const entries = typeof init.entries === "function"
|
|
466
|
+
? Array.from(init.entries())
|
|
467
|
+
: Object.entries(init);
|
|
468
|
+
for (const [k, v] of entries) this._map[k.toLowerCase()] = String(v);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
get(name) { return this._map[name.toLowerCase()] ?? null; }
|
|
472
|
+
set(name, value) { this._map[name.toLowerCase()] = String(value); }
|
|
473
|
+
has(name) { return name.toLowerCase() in this._map; }
|
|
474
|
+
delete(name) { delete this._map[name.toLowerCase()]; }
|
|
475
|
+
forEach(cb) { for (const [k, v] of Object.entries(this._map)) cb(v, k, this); }
|
|
476
|
+
entries() { return Object.entries(this._map)[Symbol.iterator](); }
|
|
477
|
+
keys() { return Object.keys(this._map)[Symbol.iterator](); }
|
|
478
|
+
values() { return Object.values(this._map)[Symbol.iterator](); }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
class Response {
|
|
482
|
+
constructor(result, binary) {
|
|
483
|
+
this.status = result.status;
|
|
484
|
+
this.statusText = result.statusText || "";
|
|
485
|
+
this.ok = result.status >= 200 && result.status < 300;
|
|
486
|
+
this.headers = new Headers(result.headers);
|
|
487
|
+
this._body = result.body;
|
|
488
|
+
this._binary = binary;
|
|
489
|
+
this._consumed = false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
_checkConsumed() {
|
|
493
|
+
if (this._consumed) throw new TypeError("Body already consumed");
|
|
494
|
+
this._consumed = true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async text() {
|
|
498
|
+
this._checkConsumed();
|
|
499
|
+
if (this._binary) {
|
|
500
|
+
// Decode base64 → binary string → UTF-8
|
|
501
|
+
const bytes = _fetchB64ToUint8(this._body);
|
|
502
|
+
return new TextDecoder().decode(bytes);
|
|
503
|
+
}
|
|
504
|
+
return this._body;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async json() {
|
|
508
|
+
const text = await this.text();
|
|
509
|
+
return JSON.parse(text);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async arrayBuffer() {
|
|
513
|
+
this._checkConsumed();
|
|
514
|
+
if (this._binary) {
|
|
515
|
+
const bytes = _fetchB64ToUint8(this._body);
|
|
516
|
+
return bytes.buffer;
|
|
517
|
+
}
|
|
518
|
+
return new TextEncoder().encode(this._body).buffer;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async blob() {
|
|
522
|
+
const ab = await this.arrayBuffer();
|
|
523
|
+
const type = this.headers.get("content-type") || "";
|
|
524
|
+
return new Blob([ab], { type });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function _fetchB64ToUint8(b64) {
|
|
529
|
+
const bin = atob(b64);
|
|
530
|
+
const arr = new Uint8Array(bin.length);
|
|
531
|
+
for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
|
|
532
|
+
return arr;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
globalThis.fetch = async function(input, init) {
|
|
536
|
+
const url = typeof input === "string" ? input : (input?.url || String(input));
|
|
537
|
+
const method = init?.method || "GET";
|
|
538
|
+
const headers = {};
|
|
539
|
+
if (init?.headers) {
|
|
540
|
+
const entries = typeof init.headers.entries === "function"
|
|
541
|
+
? Array.from(init.headers.entries())
|
|
542
|
+
: Object.entries(init.headers);
|
|
543
|
+
for (const [k, v] of entries) headers[k] = String(v);
|
|
544
|
+
}
|
|
545
|
+
const body = init?.body ? String(init.body) : undefined;
|
|
546
|
+
|
|
547
|
+
// Always fetch as binary to preserve data integrity
|
|
548
|
+
const result = await __poncho_fetch({ url, method, headers, body, binary: true });
|
|
549
|
+
return new Response(result, true);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
globalThis.Headers = Headers;
|
|
553
|
+
globalThis.Response = Response;
|
|
554
|
+
})();
|
|
555
|
+
`;
|
|
556
|
+
|
|
557
|
+
const POLYFILL_FETCH_STUB = `
|
|
558
|
+
// --- fetch stub (network not configured) ---
|
|
559
|
+
(function() {
|
|
560
|
+
globalThis.fetch = async function() {
|
|
561
|
+
throw new Error(
|
|
562
|
+
"fetch() is not available. Enable network access in poncho.config.js:\\n" +
|
|
563
|
+
" network: { allowedUrls: [\\"https://...\\"]}\\n" +
|
|
564
|
+
" // or: network: { dangerouslyAllowAll: true }"
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
})();
|
|
568
|
+
`;
|
|
569
|
+
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Timers (setTimeout / clearTimeout / setInterval / clearInterval)
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
const POLYFILL_TIMERS = `
|
|
575
|
+
// --- Timers polyfill ---
|
|
576
|
+
(function() {
|
|
577
|
+
let __timerId = 0;
|
|
578
|
+
const __timers = new Map();
|
|
579
|
+
|
|
580
|
+
globalThis.setTimeout = function(fn, delay) {
|
|
581
|
+
const id = ++__timerId;
|
|
582
|
+
const ms = Math.max(0, Number(delay) || 0);
|
|
583
|
+
const start = Date.now();
|
|
584
|
+
__timers.set(id, { fn, ms, start, type: "timeout" });
|
|
585
|
+
// In the isolate, setTimeout returns the id but the callback is
|
|
586
|
+
// executed via a polling mechanism in the async wrapper.
|
|
587
|
+
// For simple cases (delay=0), we can use a microtask.
|
|
588
|
+
if (ms === 0) {
|
|
589
|
+
Promise.resolve().then(() => {
|
|
590
|
+
if (__timers.has(id)) {
|
|
591
|
+
__timers.delete(id);
|
|
592
|
+
fn();
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return id;
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
globalThis.clearTimeout = function(id) {
|
|
600
|
+
__timers.delete(id);
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
globalThis.setInterval = function(fn, delay) {
|
|
604
|
+
const id = ++__timerId;
|
|
605
|
+
const ms = Math.max(1, Number(delay) || 1);
|
|
606
|
+
const wrapper = () => {
|
|
607
|
+
if (!__timers.has(id)) return;
|
|
608
|
+
fn();
|
|
609
|
+
if (__timers.has(id)) {
|
|
610
|
+
globalThis.setTimeout(wrapper, ms);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
__timers.set(id, { fn: wrapper, ms, type: "interval" });
|
|
614
|
+
globalThis.setTimeout(wrapper, ms);
|
|
615
|
+
return id;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
globalThis.clearInterval = function(id) {
|
|
619
|
+
__timers.delete(id);
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// queueMicrotask if not available
|
|
623
|
+
if (typeof globalThis.queueMicrotask === "undefined") {
|
|
624
|
+
globalThis.queueMicrotask = function(fn) { Promise.resolve().then(fn); };
|
|
625
|
+
}
|
|
626
|
+
})();
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
// crypto (randomUUID, getRandomValues)
|
|
631
|
+
// ---------------------------------------------------------------------------
|
|
632
|
+
|
|
633
|
+
const POLYFILL_CRYPTO = `
|
|
634
|
+
// --- crypto polyfill ---
|
|
635
|
+
(function() {
|
|
636
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) return;
|
|
637
|
+
|
|
638
|
+
function getRandomValues(arr) {
|
|
639
|
+
for (let i = 0; i < arr.length; i++) {
|
|
640
|
+
arr[i] = Math.floor(Math.random() * 256);
|
|
641
|
+
}
|
|
642
|
+
return arr;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function randomUUID() {
|
|
646
|
+
const bytes = new Uint8Array(16);
|
|
647
|
+
getRandomValues(bytes);
|
|
648
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
649
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
650
|
+
const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
651
|
+
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
globalThis.crypto = {
|
|
655
|
+
getRandomValues,
|
|
656
|
+
randomUUID,
|
|
657
|
+
subtle: {
|
|
658
|
+
async digest(algorithm, data) {
|
|
659
|
+
// Simple SHA-256 not feasible in pure JS without a library.
|
|
660
|
+
// Provide a helpful error for now.
|
|
661
|
+
throw new Error("crypto.subtle.digest is not available in the sandbox. Use a bundled library instead.");
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
})();
|
|
666
|
+
`;
|
|
667
|
+
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// console extras (table, time, timeEnd)
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
|
|
672
|
+
const POLYFILL_CONSOLE_EXTRAS = `
|
|
673
|
+
// --- console extras ---
|
|
674
|
+
(function() {
|
|
675
|
+
const __timeLabels = new Map();
|
|
676
|
+
|
|
677
|
+
console.table = function(data) {
|
|
678
|
+
try {
|
|
679
|
+
if (Array.isArray(data)) {
|
|
680
|
+
const keys = data.length > 0 && typeof data[0] === "object" ? Object.keys(data[0]) : null;
|
|
681
|
+
if (keys) {
|
|
682
|
+
const header = ["(index)", ...keys].join("\\t");
|
|
683
|
+
const rows = data.map((row, i) => [i, ...keys.map(k => row[k] ?? "")].join("\\t"));
|
|
684
|
+
console.log(header + "\\n" + rows.join("\\n"));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
console.log(JSON.stringify(data, null, 2));
|
|
689
|
+
} catch { console.log(String(data)); }
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
console.time = function(label) {
|
|
693
|
+
__timeLabels.set(label || "default", Date.now());
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
console.timeEnd = function(label) {
|
|
697
|
+
label = label || "default";
|
|
698
|
+
const start = __timeLabels.get(label);
|
|
699
|
+
if (start !== undefined) {
|
|
700
|
+
__timeLabels.delete(label);
|
|
701
|
+
console.log(label + ": " + (Date.now() - start) + "ms");
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
console.timeLog = function(label) {
|
|
706
|
+
label = label || "default";
|
|
707
|
+
const start = __timeLabels.get(label);
|
|
708
|
+
if (start !== undefined) {
|
|
709
|
+
console.log(label + ": " + (Date.now() - start) + "ms");
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
console.assert = function(condition) {
|
|
714
|
+
if (!condition) {
|
|
715
|
+
const args = Array.from(arguments).slice(1);
|
|
716
|
+
console.error("Assertion failed:", ...args);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
console.dir = function(obj) { console.log(obj); };
|
|
721
|
+
console.count = (function() {
|
|
722
|
+
const counts = {};
|
|
723
|
+
return function(label) {
|
|
724
|
+
label = label || "default";
|
|
725
|
+
counts[label] = (counts[label] || 0) + 1;
|
|
726
|
+
console.log(label + ": " + counts[label]);
|
|
727
|
+
};
|
|
728
|
+
})();
|
|
729
|
+
})();
|
|
730
|
+
`;
|
|
731
|
+
|
|
732
|
+
// ---------------------------------------------------------------------------
|
|
733
|
+
// Blob
|
|
734
|
+
// ---------------------------------------------------------------------------
|
|
735
|
+
|
|
736
|
+
const POLYFILL_BLOB = `
|
|
737
|
+
// --- Blob polyfill ---
|
|
738
|
+
(function() {
|
|
739
|
+
if (typeof globalThis.Blob !== "undefined") return;
|
|
740
|
+
|
|
741
|
+
class Blob {
|
|
742
|
+
constructor(parts, options) {
|
|
743
|
+
this.type = (options && options.type) || "";
|
|
744
|
+
const chunks = [];
|
|
745
|
+
if (parts) {
|
|
746
|
+
for (const part of parts) {
|
|
747
|
+
if (typeof part === "string") {
|
|
748
|
+
chunks.push(new TextEncoder().encode(part));
|
|
749
|
+
} else if (part instanceof ArrayBuffer) {
|
|
750
|
+
chunks.push(new Uint8Array(part));
|
|
751
|
+
} else if (ArrayBuffer.isView(part)) {
|
|
752
|
+
chunks.push(new Uint8Array(part.buffer, part.byteOffset, part.byteLength));
|
|
753
|
+
} else if (part instanceof Blob) {
|
|
754
|
+
chunks.push(part._data);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
let totalLen = 0;
|
|
759
|
+
for (const c of chunks) totalLen += c.length;
|
|
760
|
+
this._data = new Uint8Array(totalLen);
|
|
761
|
+
let offset = 0;
|
|
762
|
+
for (const c of chunks) {
|
|
763
|
+
this._data.set(c, offset);
|
|
764
|
+
offset += c.length;
|
|
765
|
+
}
|
|
766
|
+
this.size = this._data.length;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async arrayBuffer() { return this._data.buffer.slice(this._data.byteOffset, this._data.byteOffset + this._data.byteLength); }
|
|
770
|
+
async text() { return new TextDecoder().decode(this._data); }
|
|
771
|
+
slice(start, end, type) {
|
|
772
|
+
const sliced = this._data.slice(start || 0, end || this._data.length);
|
|
773
|
+
const blob = new Blob([], { type: type || this.type });
|
|
774
|
+
blob._data = sliced;
|
|
775
|
+
blob.size = sliced.length;
|
|
776
|
+
return blob;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
globalThis.Blob = Blob;
|
|
781
|
+
})();
|
|
782
|
+
`;
|
|
783
|
+
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
// structuredClone
|
|
786
|
+
// ---------------------------------------------------------------------------
|
|
787
|
+
|
|
788
|
+
const POLYFILL_STRUCTUREDCLONE = `
|
|
789
|
+
// --- structuredClone polyfill ---
|
|
790
|
+
(function() {
|
|
791
|
+
if (typeof globalThis.structuredClone !== "undefined") return;
|
|
792
|
+
globalThis.structuredClone = function(value) {
|
|
793
|
+
return JSON.parse(JSON.stringify(value));
|
|
794
|
+
};
|
|
795
|
+
})();
|
|
796
|
+
`;
|