@capsule-run/sdk 0.7.2 → 0.8.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 +5 -1
- package/dist/app.d.ts +7 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +7 -0
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/polyfills/fs.d.ts +94 -11
- package/dist/polyfills/fs.d.ts.map +1 -1
- package/dist/polyfills/fs.js +289 -10
- package/dist/polyfills/fs.js.map +1 -1
- package/dist/polyfills/process.d.ts +7 -2
- package/dist/polyfills/process.d.ts.map +1 -1
- package/dist/polyfills/process.js +78 -6
- package/dist/polyfills/process.js.map +1 -1
- package/dist/run.d.ts +3 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js.map +1 -1
- package/dist/task.d.ts +11 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +6 -3
- package/dist/task.js.map +1 -1
- package/package.json +4 -10
- package/src/app.ts +8 -0
- package/src/index.ts +1 -2
- package/src/polyfills/fs.ts +317 -11
- package/src/polyfills/process.ts +80 -6
- package/src/run.ts +3 -0
- package/src/task.ts +32 -14
- package/dist/polyfills/buffer.d.ts +0 -8
- package/dist/polyfills/buffer.d.ts.map +0 -1
- package/dist/polyfills/buffer.js +0 -9
- package/dist/polyfills/buffer.js.map +0 -1
- package/dist/polyfills/events.d.ts +0 -8
- package/dist/polyfills/events.d.ts.map +0 -1
- package/dist/polyfills/events.js +0 -9
- package/dist/polyfills/events.js.map +0 -1
- package/dist/polyfills/path.d.ts +0 -8
- package/dist/polyfills/path.d.ts.map +0 -1
- package/dist/polyfills/path.js +0 -8
- package/dist/polyfills/path.js.map +0 -1
- package/dist/polyfills/stream-web.d.ts +0 -74
- package/dist/polyfills/stream-web.d.ts.map +0 -1
- package/dist/polyfills/stream-web.js +0 -23
- package/dist/polyfills/stream-web.js.map +0 -1
- package/dist/polyfills/stream.d.ts +0 -16
- package/dist/polyfills/stream.d.ts.map +0 -1
- package/dist/polyfills/stream.js +0 -16
- package/dist/polyfills/stream.js.map +0 -1
- package/dist/polyfills/url.d.ts +0 -47
- package/dist/polyfills/url.d.ts.map +0 -1
- package/dist/polyfills/url.js +0 -68
- package/dist/polyfills/url.js.map +0 -1
- package/src/polyfills/buffer.ts +0 -11
- package/src/polyfills/events.ts +0 -11
- package/src/polyfills/path.ts +0 -21
- package/src/polyfills/stream-web.ts +0 -24
- package/src/polyfills/stream.ts +0 -28
- package/src/polyfills/url.ts +0 -73
package/src/polyfills/fs.ts
CHANGED
|
@@ -296,12 +296,303 @@ export function readdir(
|
|
|
296
296
|
.catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Sync implementations
|
|
301
|
+
// wasi:filesystem/types@0.2.0 descriptor methods (openAt, read, write, stat,
|
|
302
|
+
// readDirectory, etc.) are direct component-model imports — they are
|
|
303
|
+
// synchronous from JS's perspective. No asyncify, no event-loop blocking.
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
function enoent(path: string): Error {
|
|
307
|
+
return Object.assign(
|
|
308
|
+
new Error(`ENOENT: no such file or directory, open '${path}'`),
|
|
309
|
+
{ code: 'ENOENT' }
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
299
313
|
/**
|
|
300
|
-
*
|
|
314
|
+
* Read file contents synchronously.
|
|
315
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
301
316
|
*/
|
|
302
|
-
export function
|
|
303
|
-
|
|
304
|
-
|
|
317
|
+
export function readFileSync(path: string, options?: ReadFileOptions | Encoding): string | Uint8Array {
|
|
318
|
+
const resolved = resolvePath(path);
|
|
319
|
+
if (!resolved) throw enoent(path);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
|
|
323
|
+
const stat = fd.stat();
|
|
324
|
+
const [data] = fd.read(stat.size, BigInt(0));
|
|
325
|
+
const encoding = typeof options === 'string' ? options : options?.encoding;
|
|
326
|
+
return (encoding === 'utf8' || encoding === 'utf-8') ? new TextDecoder().decode(data) : data;
|
|
327
|
+
} catch (e) {
|
|
328
|
+
if (e instanceof Error && (e as any).code) throw e;
|
|
329
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, open '${path}'`), { code: 'ENOENT' });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Write data to a file synchronously.
|
|
335
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
336
|
+
*/
|
|
337
|
+
export function writeFileSync(path: string, data: string | Uint8Array, _options?: WriteFileOptions | Encoding): void {
|
|
338
|
+
const resolved = resolvePath(path);
|
|
339
|
+
if (!resolved) throw enoent(path);
|
|
340
|
+
|
|
341
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
342
|
+
try {
|
|
343
|
+
const fd = resolved.dir.openAt(
|
|
344
|
+
{ symlinkFollow: false },
|
|
345
|
+
resolved.relativePath,
|
|
346
|
+
{ create: true, truncate: true },
|
|
347
|
+
{ write: true, mutateDirectory: true }
|
|
348
|
+
);
|
|
349
|
+
fd.write(bytes, BigInt(0));
|
|
350
|
+
} catch (e) {
|
|
351
|
+
throw new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Append data to a file synchronously, creating it if it doesn't exist.
|
|
357
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
358
|
+
*/
|
|
359
|
+
export function appendFileSync(path: string, data: string | Uint8Array, _options?: WriteFileOptions | Encoding): void {
|
|
360
|
+
const resolved = resolvePath(path);
|
|
361
|
+
if (!resolved) throw enoent(path);
|
|
362
|
+
|
|
363
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
364
|
+
try {
|
|
365
|
+
const fd = resolved.dir.openAt(
|
|
366
|
+
{ symlinkFollow: false },
|
|
367
|
+
resolved.relativePath,
|
|
368
|
+
{ create: true },
|
|
369
|
+
{ write: true, mutateDirectory: true }
|
|
370
|
+
);
|
|
371
|
+
const stat = fd.stat();
|
|
372
|
+
fd.write(bytes, stat.size);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
throw new Error(`Failed to append to file '${path}': ${e}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Read directory contents synchronously.
|
|
380
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
381
|
+
*/
|
|
382
|
+
export function readdirSync(path: string, _options?: any): string[] {
|
|
383
|
+
const resolved = resolvePath(path);
|
|
384
|
+
if (!resolved) throw enoent(path);
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
let targetDir = resolved.dir;
|
|
388
|
+
if (resolved.relativePath !== '.') {
|
|
389
|
+
targetDir = resolved.dir.openAt(
|
|
390
|
+
{ symlinkFollow: false },
|
|
391
|
+
resolved.relativePath,
|
|
392
|
+
{ directory: true },
|
|
393
|
+
{ read: true }
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const stream = targetDir.readDirectory();
|
|
397
|
+
const entries: string[] = [];
|
|
398
|
+
let entry;
|
|
399
|
+
while ((entry = stream.readDirectoryEntry()) && entry) {
|
|
400
|
+
if (entry.name) entries.push(entry.name);
|
|
401
|
+
}
|
|
402
|
+
return entries;
|
|
403
|
+
} catch (e) {
|
|
404
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, scandir '${path}'`), { code: 'ENOENT' });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export interface StatResult {
|
|
409
|
+
isFile: () => boolean;
|
|
410
|
+
isDirectory: () => boolean;
|
|
411
|
+
isSymbolicLink: () => boolean;
|
|
412
|
+
size: number;
|
|
413
|
+
mtimeMs: number;
|
|
414
|
+
atimeMs: number;
|
|
415
|
+
ctimeMs: number;
|
|
416
|
+
mode: number;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function makeStatResult(type: 'file' | 'directory' | 'notfound', size: bigint = BigInt(0)): StatResult {
|
|
420
|
+
return {
|
|
421
|
+
isFile: () => type === 'file',
|
|
422
|
+
isDirectory: () => type === 'directory',
|
|
423
|
+
isSymbolicLink: () => false,
|
|
424
|
+
size: Number(size),
|
|
425
|
+
mtimeMs: 0,
|
|
426
|
+
atimeMs: 0,
|
|
427
|
+
ctimeMs: 0,
|
|
428
|
+
mode: type === 'directory' ? 0o40755 : 0o100644,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get file stats synchronously.
|
|
434
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
435
|
+
*/
|
|
436
|
+
export function statSync(path: string): StatResult {
|
|
437
|
+
const resolved = resolvePath(path);
|
|
438
|
+
if (!resolved) throw enoent(path);
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
|
|
442
|
+
const s = fd.stat();
|
|
443
|
+
const type = s.type === 'directory' ? 'directory' : 'file';
|
|
444
|
+
return makeStatResult(type, s.size);
|
|
445
|
+
} catch (e) {
|
|
446
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, stat '${path}'`), { code: 'ENOENT' });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get file stats synchronously (no symlink follow — same as stat in WASI 0.2).
|
|
452
|
+
*/
|
|
453
|
+
export function lstatSync(path: string): StatResult {
|
|
454
|
+
return statSync(path);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Create a directory synchronously.
|
|
459
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
460
|
+
*/
|
|
461
|
+
export function mkdirSync(path: string, options?: MkdirOptions): void {
|
|
462
|
+
if (options?.recursive) {
|
|
463
|
+
const normalized = normalizePath(path);
|
|
464
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
465
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
466
|
+
const partial = parts.slice(0, i).join('/');
|
|
467
|
+
const resolved = resolvePath(partial);
|
|
468
|
+
if (!resolved) continue;
|
|
469
|
+
try { resolved.dir.createDirectoryAt(resolved.relativePath); } catch { /* already exists */ }
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
const resolved = resolvePath(path);
|
|
473
|
+
if (!resolved) throw enoent(path);
|
|
474
|
+
try {
|
|
475
|
+
resolved.dir.createDirectoryAt(resolved.relativePath);
|
|
476
|
+
} catch (e) {
|
|
477
|
+
throw Object.assign(new Error(`EEXIST: file already exists, mkdir '${path}'`), { code: 'EEXIST' });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Remove a directory synchronously.
|
|
484
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
485
|
+
*/
|
|
486
|
+
export function rmdirSync(path: string, _options?: RmdirOptions): void {
|
|
487
|
+
const resolved = resolvePath(path);
|
|
488
|
+
if (!resolved) throw enoent(path);
|
|
489
|
+
try {
|
|
490
|
+
resolved.dir.removeDirectoryAt(resolved.relativePath);
|
|
491
|
+
} catch (e) {
|
|
492
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, rmdir '${path}'`), { code: 'ENOENT' });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Remove a file or directory synchronously.
|
|
498
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
499
|
+
*/
|
|
500
|
+
export function rmSync(path: string, options?: RmOptions): void {
|
|
501
|
+
const resolved = resolvePath(path);
|
|
502
|
+
if (!resolved) {
|
|
503
|
+
if (options?.force) return;
|
|
504
|
+
throw enoent(path);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
|
|
509
|
+
const s = fd.stat();
|
|
510
|
+
if (s.type === 'directory') {
|
|
511
|
+
if (!options?.recursive) {
|
|
512
|
+
throw Object.assign(
|
|
513
|
+
new Error(`EISDIR: illegal operation on a directory, rm '${path}'`),
|
|
514
|
+
{ code: 'EISDIR' }
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
resolved.dir.removeDirectoryAt(resolved.relativePath);
|
|
518
|
+
} else {
|
|
519
|
+
resolved.dir.unlinkFileAt(resolved.relativePath);
|
|
520
|
+
}
|
|
521
|
+
} catch (e) {
|
|
522
|
+
if (options?.force) return;
|
|
523
|
+
if (e instanceof Error && (e as any).code) throw e;
|
|
524
|
+
throw enoent(path);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Remove a file synchronously.
|
|
530
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
531
|
+
*/
|
|
532
|
+
export function unlinkSync(path: string): void {
|
|
533
|
+
const resolved = resolvePath(path);
|
|
534
|
+
if (!resolved) throw enoent(path);
|
|
535
|
+
try {
|
|
536
|
+
resolved.dir.unlinkFileAt(resolved.relativePath);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, unlink '${path}'`), { code: 'ENOENT' });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Copy a file synchronously.
|
|
544
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
545
|
+
*/
|
|
546
|
+
export function copyFileSync(src: string, dest: string): void {
|
|
547
|
+
const data = readFileSync(src) as Uint8Array;
|
|
548
|
+
writeFileSync(dest, data);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Rename a file or directory synchronously.
|
|
553
|
+
* Falls back to copy+delete since wasi:filesystem/types@0.2.0
|
|
554
|
+
* does not expose a rename/link-at in the current WIT binding.
|
|
555
|
+
*/
|
|
556
|
+
export function renameSync(oldPath: string, newPath: string): void {
|
|
557
|
+
try {
|
|
558
|
+
const data = readFileSync(oldPath);
|
|
559
|
+
writeFileSync(newPath, data as Uint8Array);
|
|
560
|
+
unlinkSync(oldPath);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
throw Object.assign(
|
|
563
|
+
new Error(`ENOENT: no such file or directory, rename '${oldPath}' -> '${newPath}'`),
|
|
564
|
+
{ code: 'ENOENT' }
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Check file accessibility synchronously.
|
|
571
|
+
* Throws if the path does not exist.
|
|
572
|
+
*/
|
|
573
|
+
export function accessSync(path: string, _mode?: number): void {
|
|
574
|
+
const resolved = resolvePath(path);
|
|
575
|
+
if (!resolved) throw enoent(path);
|
|
576
|
+
try {
|
|
577
|
+
resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
|
|
578
|
+
} catch (e) {
|
|
579
|
+
throw Object.assign(new Error(`ENOENT: no such file or directory, access '${path}'`), { code: 'ENOENT' });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Check if a path exists synchronously.
|
|
585
|
+
* Backed by wasi:filesystem/types@0.2.0 — fully synchronous.
|
|
586
|
+
*/
|
|
587
|
+
export function existsSync(path: string): boolean {
|
|
588
|
+
const resolved = resolvePath(path);
|
|
589
|
+
if (!resolved) return false;
|
|
590
|
+
try {
|
|
591
|
+
resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
|
|
592
|
+
return true;
|
|
593
|
+
} catch {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
305
596
|
}
|
|
306
597
|
|
|
307
598
|
/**
|
|
@@ -533,15 +824,15 @@ export const promises = {
|
|
|
533
824
|
await unlink(path);
|
|
534
825
|
},
|
|
535
826
|
|
|
536
|
-
async stat(path: string): Promise<
|
|
827
|
+
async stat(path: string): Promise<StatResult> {
|
|
537
828
|
const kind = await statPath(path);
|
|
538
829
|
if (kind === 'notfound') {
|
|
539
|
-
throw
|
|
830
|
+
throw Object.assign(
|
|
831
|
+
new Error(`ENOENT: no such file or directory, stat '${path}'`),
|
|
832
|
+
{ code: 'ENOENT' }
|
|
833
|
+
);
|
|
540
834
|
}
|
|
541
|
-
return
|
|
542
|
-
isFile: () => kind === 'file',
|
|
543
|
-
isDirectory: () => kind === 'directory',
|
|
544
|
-
};
|
|
835
|
+
return makeStatResult(kind);
|
|
545
836
|
},
|
|
546
837
|
|
|
547
838
|
async rmdir(path: string, options?: RmdirOptions): Promise<void> {
|
|
@@ -566,16 +857,31 @@ export const promises = {
|
|
|
566
857
|
};
|
|
567
858
|
|
|
568
859
|
const fs = {
|
|
860
|
+
// Async / callback
|
|
569
861
|
readFile,
|
|
570
862
|
writeFile,
|
|
571
863
|
readdir,
|
|
572
|
-
existsSync,
|
|
573
864
|
unlink,
|
|
574
865
|
rmdir,
|
|
575
866
|
rm,
|
|
576
867
|
mkdir,
|
|
577
868
|
copyFile,
|
|
578
869
|
cp,
|
|
870
|
+
readFileSync,
|
|
871
|
+
writeFileSync,
|
|
872
|
+
appendFileSync,
|
|
873
|
+
readdirSync,
|
|
874
|
+
statSync,
|
|
875
|
+
lstatSync,
|
|
876
|
+
mkdirSync,
|
|
877
|
+
rmdirSync,
|
|
878
|
+
rmSync,
|
|
879
|
+
unlinkSync,
|
|
880
|
+
copyFileSync,
|
|
881
|
+
renameSync,
|
|
882
|
+
accessSync,
|
|
883
|
+
existsSync,
|
|
884
|
+
// Promises API
|
|
579
885
|
promises,
|
|
580
886
|
};
|
|
581
887
|
|
package/src/polyfills/process.ts
CHANGED
|
@@ -9,6 +9,16 @@ declare const globalThis: {
|
|
|
9
9
|
getArguments(): string[];
|
|
10
10
|
initialCwd(): string | null;
|
|
11
11
|
};
|
|
12
|
+
'wasi:cli/stdin': {
|
|
13
|
+
getStdin(): {
|
|
14
|
+
blockingRead(len: bigint): [Uint8Array, boolean];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
'wasi:cli/stdout': {
|
|
18
|
+
getStdout(): {
|
|
19
|
+
blockingWriteAndFlush(buf: Uint8Array): void;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
12
22
|
};
|
|
13
23
|
|
|
14
24
|
/**
|
|
@@ -178,24 +188,88 @@ const process = {
|
|
|
178
188
|
},
|
|
179
189
|
|
|
180
190
|
/**
|
|
181
|
-
* Standard
|
|
191
|
+
* Standard input — backed by wasi:cli/stdin@0.2.0.
|
|
192
|
+
* Exposes a minimal EventEmitter-compatible Readable so that
|
|
193
|
+
* MCP's StdioServerTransport can call stdin.on('data', ...).
|
|
182
194
|
*/
|
|
183
|
-
get
|
|
195
|
+
get stdin(): any {
|
|
196
|
+
const listeners: Array<(chunk: Uint8Array | string) => void> = [];
|
|
197
|
+
let reading = false;
|
|
198
|
+
|
|
199
|
+
function startReading() {
|
|
200
|
+
if (reading) return;
|
|
201
|
+
reading = true;
|
|
202
|
+
(async () => {
|
|
203
|
+
try {
|
|
204
|
+
const api = globalThis['wasi:cli/stdin'];
|
|
205
|
+
if (!api) return;
|
|
206
|
+
const stream = api.getStdin();
|
|
207
|
+
while (true) {
|
|
208
|
+
const [chunk, done] = stream.blockingRead(BigInt(4096));
|
|
209
|
+
if (chunk && chunk.length > 0) {
|
|
210
|
+
const buf = chunk;
|
|
211
|
+
listeners.forEach(fn => fn(buf));
|
|
212
|
+
}
|
|
213
|
+
if (done) {
|
|
214
|
+
endListeners.forEach(fn => fn());
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch { /* stdin not available */ }
|
|
219
|
+
})();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const endListeners: Array<() => void> = [];
|
|
223
|
+
|
|
184
224
|
return {
|
|
185
|
-
write: (data: string) => console.log(data),
|
|
186
225
|
isTTY: false,
|
|
226
|
+
readable: true,
|
|
227
|
+
on(event: string, fn: (...args: any[]) => void) {
|
|
228
|
+
if (event === 'data') { listeners.push(fn); startReading(); }
|
|
229
|
+
if (event === 'end') { endListeners.push(fn); }
|
|
230
|
+
return this;
|
|
231
|
+
},
|
|
232
|
+
once(event: string, fn: (...args: any[]) => void) {
|
|
233
|
+
return this.on(event, fn);
|
|
234
|
+
},
|
|
235
|
+
pause() { return this; },
|
|
236
|
+
resume() { startReading(); return this; },
|
|
237
|
+
pipe(dest: any) { return dest; },
|
|
238
|
+
setEncoding(_enc: string) { return this; },
|
|
187
239
|
};
|
|
188
240
|
},
|
|
189
241
|
|
|
190
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Standard output — backed by wasi:cli/stdout@0.2.0.
|
|
244
|
+
*/
|
|
245
|
+
get stdout(): any {
|
|
191
246
|
return {
|
|
192
|
-
write: (data: string) => console.error(data),
|
|
193
247
|
isTTY: false,
|
|
248
|
+
write(data: string | Uint8Array, _enc?: string, cb?: () => void): boolean {
|
|
249
|
+
try {
|
|
250
|
+
const api = globalThis['wasi:cli/stdout'];
|
|
251
|
+
if (api) {
|
|
252
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
253
|
+
api.getStdout().blockingWriteAndFlush(bytes);
|
|
254
|
+
} else {
|
|
255
|
+
console.log(typeof data === 'string' ? data : new TextDecoder().decode(data));
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
console.log(typeof data === 'string' ? data : new TextDecoder().decode(data));
|
|
259
|
+
}
|
|
260
|
+
cb?.();
|
|
261
|
+
return true;
|
|
262
|
+
},
|
|
263
|
+
end(data?: string | Uint8Array, cb?: () => void): void {
|
|
264
|
+
if (data) this.write(data);
|
|
265
|
+
cb?.();
|
|
266
|
+
},
|
|
194
267
|
};
|
|
195
268
|
},
|
|
196
269
|
|
|
197
|
-
get
|
|
270
|
+
get stderr(): any {
|
|
198
271
|
return {
|
|
272
|
+
write: (data: string) => console.error(data),
|
|
199
273
|
isTTY: false,
|
|
200
274
|
};
|
|
201
275
|
},
|
package/src/run.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { execFile } from 'child_process';
|
|
9
9
|
import { resolve, extname } from 'path';
|
|
10
10
|
import { existsSync } from 'fs';
|
|
11
|
+
import { HostRequest } from './task';
|
|
11
12
|
|
|
12
13
|
export interface RunnerOptions {
|
|
13
14
|
file: string;
|
|
@@ -26,6 +27,8 @@ export interface RunnerResult {
|
|
|
26
27
|
duration_ms: number;
|
|
27
28
|
retries: number;
|
|
28
29
|
fuel_consumed: number;
|
|
30
|
+
ram_used: number;
|
|
31
|
+
host_requests: HostRequest[];
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
|
package/src/task.ts
CHANGED
|
@@ -50,13 +50,18 @@ interface TaskExecution {
|
|
|
50
50
|
duration_ms: number;
|
|
51
51
|
retries: number;
|
|
52
52
|
fuel_consumed: number;
|
|
53
|
+
ram_used: number;
|
|
54
|
+
host_requests: HostRequest[];
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
export interface HostRequest {
|
|
58
|
+
method: string;
|
|
59
|
+
url: string;
|
|
60
|
+
headers?: string[] | null;
|
|
61
|
+
body?: string | null;
|
|
62
|
+
status: number;
|
|
63
|
+
}
|
|
56
64
|
|
|
57
|
-
type TaskReturnType<T> = T extends Promise<infer U>
|
|
58
|
-
? Promise<TaskResult<U>>
|
|
59
|
-
: TaskResult<T>;
|
|
60
65
|
|
|
61
66
|
/**
|
|
62
67
|
* Define a Capsule task with configuration.
|
|
@@ -121,10 +126,20 @@ function normalizeAllowedFile(entry: string | AllowedFile): string {
|
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
export function task<TArgs extends
|
|
129
|
+
export function task<TArgs extends unknown[], TReturn>(
|
|
130
|
+
options: TaskOptions,
|
|
131
|
+
fn: (...args: TArgs) => Promise<TReturn>
|
|
132
|
+
): (...args: TArgs) => Promise<TaskResult<TReturn>>;
|
|
133
|
+
|
|
134
|
+
export function task<TArgs extends unknown[], TReturn>(
|
|
125
135
|
options: TaskOptions,
|
|
126
136
|
fn: (...args: TArgs) => TReturn
|
|
127
|
-
): (...args: TArgs) =>
|
|
137
|
+
): (...args: TArgs) => TaskResult<TReturn>;
|
|
138
|
+
|
|
139
|
+
export function task<TArgs extends unknown[], TReturn>(
|
|
140
|
+
options: TaskOptions,
|
|
141
|
+
fn: (...args: TArgs) => TReturn | Promise<TReturn>
|
|
142
|
+
): (...args: TArgs) => TaskResult<TReturn> | Promise<TaskResult<TReturn>> {
|
|
128
143
|
const taskName = options.name;
|
|
129
144
|
let compute = options.compute?.toString().toUpperCase() ?? "MEDIUM";
|
|
130
145
|
let allowedHosts = options.allowedHosts ?? [];
|
|
@@ -140,45 +155,48 @@ export function task<TArgs extends any[], TReturn>(
|
|
|
140
155
|
envVariables: options.envVariables,
|
|
141
156
|
};
|
|
142
157
|
|
|
143
|
-
const wrapper = (...args: TArgs):
|
|
158
|
+
const wrapper = (...args: TArgs): TaskResult<TReturn> | Promise<TaskResult<TReturn>> => {
|
|
144
159
|
if (!isWasmMode()) {
|
|
145
160
|
const result = fn(...args);
|
|
146
161
|
|
|
147
162
|
if (result instanceof Promise) {
|
|
148
163
|
return result.then((value) => ({
|
|
149
164
|
success: true,
|
|
150
|
-
result: value,
|
|
165
|
+
result: value as TReturn,
|
|
151
166
|
error: null,
|
|
152
167
|
execution: {
|
|
153
168
|
task_name: taskName,
|
|
154
169
|
duration_ms: 0,
|
|
155
170
|
retries: 0,
|
|
156
171
|
fuel_consumed: 0,
|
|
172
|
+
ram_used: 0,
|
|
173
|
+
host_requests: [],
|
|
157
174
|
},
|
|
158
|
-
}))
|
|
175
|
+
}));
|
|
159
176
|
}
|
|
160
177
|
|
|
161
178
|
return {
|
|
162
179
|
success: true,
|
|
163
|
-
result,
|
|
180
|
+
result: result as TReturn,
|
|
164
181
|
error: null,
|
|
165
182
|
execution: {
|
|
166
183
|
task_name: taskName,
|
|
167
184
|
duration_ms: 0,
|
|
168
185
|
retries: 0,
|
|
169
186
|
fuel_consumed: 0,
|
|
187
|
+
ram_used: 0,
|
|
188
|
+
host_requests: [],
|
|
170
189
|
},
|
|
171
|
-
}
|
|
190
|
+
};
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
const resultJson = callHost(taskName, args, taskConfig);
|
|
175
194
|
|
|
176
195
|
try {
|
|
177
|
-
|
|
178
|
-
return result as TaskReturnType<TReturn>;
|
|
196
|
+
return JSON.parse(resultJson) as TaskResult<TReturn>;
|
|
179
197
|
} catch (e) {
|
|
180
198
|
if (e instanceof SyntaxError) {
|
|
181
|
-
return resultJson as unknown as
|
|
199
|
+
return resultJson as unknown as TaskResult<TReturn>;
|
|
182
200
|
}
|
|
183
201
|
throw e;
|
|
184
202
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Buffer polyfill for WASI environment
|
|
3
|
-
* Provides Node.js-compatible Buffer class using the buffer package
|
|
4
|
-
*/
|
|
5
|
-
import { Buffer as BufferPolyfill } from 'buffer';
|
|
6
|
-
export declare const Buffer: BufferConstructor;
|
|
7
|
-
export default BufferPolyfill;
|
|
8
|
-
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/polyfills/buffer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAIlD,eAAO,MAAM,MAAM,mBAAiB,CAAC;AACrC,eAAe,cAAc,CAAC"}
|
package/dist/polyfills/buffer.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Buffer polyfill for WASI environment
|
|
3
|
-
* Provides Node.js-compatible Buffer class using the buffer package
|
|
4
|
-
*/
|
|
5
|
-
import { Buffer as BufferPolyfill } from 'buffer';
|
|
6
|
-
globalThis.Buffer = BufferPolyfill;
|
|
7
|
-
export const Buffer = BufferPolyfill;
|
|
8
|
-
export default BufferPolyfill;
|
|
9
|
-
//# sourceMappingURL=buffer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../src/polyfills/buffer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,QAAQ,CAAC;AAEjD,UAAkB,CAAC,MAAM,GAAG,cAAc,CAAC;AAE5C,MAAM,CAAC,MAAM,MAAM,GAAG,cAAc,CAAC;AACrC,eAAe,cAAc,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/polyfills/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,YAAY,MAAM,QAAQ,CAAC;AAIlC,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,CAAC"}
|
package/dist/polyfills/events.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Events polyfill for WASI environment
|
|
3
|
-
* Provides Node.js-compatible EventEmitter using the events package
|
|
4
|
-
*/
|
|
5
|
-
import EventEmitter from 'events';
|
|
6
|
-
globalThis.EventEmitter = EventEmitter;
|
|
7
|
-
export { EventEmitter };
|
|
8
|
-
export default EventEmitter;
|
|
9
|
-
//# sourceMappingURL=events.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/polyfills/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,YAAY,MAAM,QAAQ,CAAC;AAEjC,UAAkB,CAAC,YAAY,GAAG,YAAY,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,CAAC"}
|
package/dist/polyfills/path.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path polyfill
|
|
3
|
-
* Provides Node.js-compatible path object
|
|
4
|
-
*/
|
|
5
|
-
import path from 'path-browserify';
|
|
6
|
-
export default path;
|
|
7
|
-
export declare const resolve: (this: void, ...pathSegments: string[]) => string, join: (this: void, ...paths: string[]) => string, dirname: (this: void, path: string) => string, basename: (this: void, path: string, ext?: string) => string, extname: (this: void, path: string) => string, format: (this: void, pathObject: Partial<path.PathObject>) => string, parse: (this: void, path: string) => path.PathObject, sep: string, delimiter: string, posix: path.Path, win32: null;
|
|
8
|
-
//# sourceMappingURL=path.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/polyfills/path.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,IAAI,MAAM,iBAAiB,CAAC;AAEnC,eAAe,IAAI,CAAC;AACpB,eAAO,MACH,OAAO,qDACP,IAAI,8CACJ,OAAO,wCACP,QAAQ,sDACR,OAAO,wCACP,MAAM,gEACN,KAAK,iDACL,GAAG,UACH,SAAS,UACT,KAAK,aACL,KAAK,MACD,CAAC"}
|
package/dist/polyfills/path.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path polyfill
|
|
3
|
-
* Provides Node.js-compatible path object
|
|
4
|
-
*/
|
|
5
|
-
import path from 'path-browserify';
|
|
6
|
-
export default path;
|
|
7
|
-
export const { resolve, join, dirname, basename, extname, format, parse, sep, delimiter, posix, win32 } = path;
|
|
8
|
-
//# sourceMappingURL=path.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/polyfills/path.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,IAAI,MAAM,iBAAiB,CAAC;AAEnC,eAAe,IAAI,CAAC;AACpB,MAAM,CAAC,MAAM,EACT,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,KAAK,EACL,GAAG,EACH,SAAS,EACT,KAAK,EACL,KAAK,EACR,GAAG,IAAI,CAAC"}
|