sandlot 0.1.4 → 0.2.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/dist/browser/bundler.d.ts +68 -0
- package/dist/browser/bundler.d.ts.map +1 -0
- package/dist/browser/executor.d.ts +46 -0
- package/dist/browser/executor.d.ts.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2692 -0
- package/dist/browser/preset.d.ts +63 -0
- package/dist/browser/preset.d.ts.map +1 -0
- package/dist/commands/index.d.ts +20 -11
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/types.d.ts +31 -132
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/core/bundler-utils.d.ts +142 -0
- package/dist/core/bundler-utils.d.ts.map +1 -0
- package/dist/core/esm-types-resolver.d.ts +125 -0
- package/dist/core/esm-types-resolver.d.ts.map +1 -0
- package/dist/core/executor.d.ts +35 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/sandbox.d.ts +30 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandlot.d.ts +30 -0
- package/dist/core/sandlot.d.ts.map +1 -0
- package/dist/core/shared-module-registry.d.ts +46 -0
- package/dist/core/shared-module-registry.d.ts.map +1 -0
- package/dist/core/typechecker.d.ts +60 -0
- package/dist/core/typechecker.d.ts.map +1 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1399 -2010
- package/dist/node/bundler.d.ts +48 -0
- package/dist/node/bundler.d.ts.map +1 -0
- package/dist/node/executor.d.ts +48 -0
- package/dist/node/executor.d.ts.map +1 -0
- package/dist/node/index.d.ts +9 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2646 -0
- package/dist/node/preset.d.ts +62 -0
- package/dist/node/preset.d.ts.map +1 -0
- package/dist/types.d.ts +525 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +16 -6
- package/src/browser/bundler.ts +294 -0
- package/src/browser/executor.ts +71 -0
- package/src/browser/index.ts +57 -0
- package/src/browser/preset.ts +179 -0
- package/src/commands/index.ts +526 -43
- package/src/commands/types.ts +82 -146
- package/src/core/bundler-utils.ts +630 -0
- package/src/core/esm-types-resolver.ts +432 -0
- package/src/core/executor.ts +161 -0
- package/src/{fs.ts → core/fs.ts} +59 -37
- package/src/core/sandbox.ts +621 -0
- package/src/core/sandlot.ts +77 -0
- package/src/core/shared-module-registry.ts +138 -0
- package/src/core/typechecker.ts +607 -0
- package/src/index.ts +104 -139
- package/src/node/bundler.ts +194 -0
- package/src/node/executor.ts +87 -0
- package/src/node/index.ts +39 -0
- package/src/node/preset.ts +178 -0
- package/src/types.ts +668 -0
- package/README.md +0 -243
- package/dist/build-emitter.d.ts +0 -47
- package/dist/build-emitter.d.ts.map +0 -1
- package/dist/builder.d.ts +0 -370
- package/dist/builder.d.ts.map +0 -1
- package/dist/bundler.d.ts +0 -152
- package/dist/bundler.d.ts.map +0 -1
- package/dist/commands/compile.d.ts +0 -13
- package/dist/commands/compile.d.ts.map +0 -1
- package/dist/commands/packages.d.ts +0 -17
- package/dist/commands/packages.d.ts.map +0 -1
- package/dist/commands/run.d.ts +0 -40
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands.d.ts +0 -179
- package/dist/commands.d.ts.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/internal.d.ts +0 -79
- package/dist/internal.d.ts.map +0 -1
- package/dist/internal.js +0 -1942
- package/dist/loader.d.ts +0 -164
- package/dist/loader.d.ts.map +0 -1
- package/dist/packages.d.ts +0 -199
- package/dist/packages.d.ts.map +0 -1
- package/dist/runner.d.ts +0 -314
- package/dist/runner.d.ts.map +0 -1
- package/dist/sandbox-manager.d.ts +0 -261
- package/dist/sandbox-manager.d.ts.map +0 -1
- package/dist/sandbox.d.ts +0 -267
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/shared-modules.d.ts +0 -148
- package/dist/shared-modules.d.ts.map +0 -1
- package/dist/shared-resources.d.ts +0 -102
- package/dist/shared-resources.d.ts.map +0 -1
- package/dist/ts-libs.d.ts +0 -85
- package/dist/ts-libs.d.ts.map +0 -1
- package/dist/typechecker.d.ts +0 -127
- package/dist/typechecker.d.ts.map +0 -1
- package/src/build-emitter.ts +0 -64
- package/src/builder.ts +0 -498
- package/src/bundler.ts +0 -575
- package/src/commands/compile.ts +0 -236
- package/src/commands/packages.ts +0 -154
- package/src/commands/run.ts +0 -245
- package/src/internal.ts +0 -119
- package/src/loader.ts +0 -229
- package/src/packages.ts +0 -936
- package/src/sandbox.ts +0 -398
- package/src/shared-modules.ts +0 -280
- package/src/shared-resources.ts +0 -166
- package/src/ts-libs.ts +0 -218
- package/src/typechecker.ts +0 -635
package/dist/index.js
CHANGED
|
@@ -6,10 +6,101 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
// src/
|
|
9
|
+
// src/core/shared-module-registry.ts
|
|
10
|
+
var instanceCounter = 0;
|
|
11
|
+
function generateInstanceId() {
|
|
12
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
13
|
+
return crypto.randomUUID().slice(0, 8);
|
|
14
|
+
}
|
|
15
|
+
return `${Date.now().toString(36)}_${(++instanceCounter).toString(36)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class SharedModuleRegistry {
|
|
19
|
+
modules;
|
|
20
|
+
exportNamesMap;
|
|
21
|
+
_registryKey;
|
|
22
|
+
constructor(modules) {
|
|
23
|
+
this._registryKey = `__sandlot_${generateInstanceId()}__`;
|
|
24
|
+
this.modules = new Map(Object.entries(modules));
|
|
25
|
+
this.exportNamesMap = new Map;
|
|
26
|
+
for (const [id, mod] of this.modules) {
|
|
27
|
+
this.exportNamesMap.set(id, this.introspectExports(mod));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
get registryKey() {
|
|
31
|
+
return this._registryKey;
|
|
32
|
+
}
|
|
33
|
+
exposeGlobally() {
|
|
34
|
+
globalThis[this._registryKey] = this;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
removeFromGlobal() {
|
|
38
|
+
if (globalThis[this._registryKey] === this) {
|
|
39
|
+
delete globalThis[this._registryKey];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
get(moduleId) {
|
|
43
|
+
const mod = this.modules.get(moduleId);
|
|
44
|
+
if (mod === undefined && !this.modules.has(moduleId)) {
|
|
45
|
+
throw new Error(`Shared module "${moduleId}" not registered.`);
|
|
46
|
+
}
|
|
47
|
+
return mod;
|
|
48
|
+
}
|
|
49
|
+
has(moduleId) {
|
|
50
|
+
return this.modules.has(moduleId);
|
|
51
|
+
}
|
|
52
|
+
getExportNames(moduleId) {
|
|
53
|
+
return this.exportNamesMap.get(moduleId) ?? [];
|
|
54
|
+
}
|
|
55
|
+
list() {
|
|
56
|
+
return [...this.modules.keys()];
|
|
57
|
+
}
|
|
58
|
+
introspectExports(module) {
|
|
59
|
+
if (module === null || module === undefined) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
if (typeof module !== "object" && typeof module !== "function") {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const exports = [];
|
|
66
|
+
for (const key of Object.keys(module)) {
|
|
67
|
+
if (this.isValidIdentifier(key)) {
|
|
68
|
+
exports.push(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return exports;
|
|
72
|
+
}
|
|
73
|
+
isValidIdentifier(name) {
|
|
74
|
+
if (name.length === 0)
|
|
75
|
+
return false;
|
|
76
|
+
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name))
|
|
77
|
+
return false;
|
|
78
|
+
const reserved = [
|
|
79
|
+
"default",
|
|
80
|
+
"class",
|
|
81
|
+
"function",
|
|
82
|
+
"var",
|
|
83
|
+
"let",
|
|
84
|
+
"const",
|
|
85
|
+
"import",
|
|
86
|
+
"export"
|
|
87
|
+
];
|
|
88
|
+
return !reserved.includes(name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function createSharedModuleRegistry(modules) {
|
|
92
|
+
if (!modules || Object.keys(modules).length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const registry = new SharedModuleRegistry(modules);
|
|
96
|
+
registry.exposeGlobally();
|
|
97
|
+
return registry;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/core/sandbox.ts
|
|
10
101
|
import { Bash } from "just-bash/browser";
|
|
11
102
|
|
|
12
|
-
// src/fs.ts
|
|
103
|
+
// src/core/fs.ts
|
|
13
104
|
var DEFAULT_FILE_MODE = 420;
|
|
14
105
|
var DEFAULT_DIR_MODE = 493;
|
|
15
106
|
var DEFAULT_SYMLINK_MODE = 511;
|
|
@@ -73,7 +164,7 @@ class Filesystem {
|
|
|
73
164
|
}
|
|
74
165
|
return size;
|
|
75
166
|
}
|
|
76
|
-
|
|
167
|
+
readFile(path, options) {
|
|
77
168
|
const normalizedPath = this.normalizePath(path);
|
|
78
169
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
79
170
|
if (!entry) {
|
|
@@ -89,7 +180,7 @@ class Filesystem {
|
|
|
89
180
|
const encoding = this.getEncoding(options) ?? "utf8";
|
|
90
181
|
return this.decodeBuffer(content, encoding);
|
|
91
182
|
}
|
|
92
|
-
|
|
183
|
+
readFileBuffer(path) {
|
|
93
184
|
const normalizedPath = this.normalizePath(path);
|
|
94
185
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
95
186
|
if (!entry) {
|
|
@@ -104,7 +195,7 @@ class Filesystem {
|
|
|
104
195
|
}
|
|
105
196
|
return new TextEncoder().encode(content);
|
|
106
197
|
}
|
|
107
|
-
|
|
198
|
+
writeFile(path, content, _options) {
|
|
108
199
|
const normalizedPath = this.normalizePath(path);
|
|
109
200
|
this.checkSizeLimit(content);
|
|
110
201
|
this.ensureParentDirs(normalizedPath);
|
|
@@ -119,20 +210,20 @@ class Filesystem {
|
|
|
119
210
|
mtime: new Date
|
|
120
211
|
});
|
|
121
212
|
}
|
|
122
|
-
|
|
213
|
+
appendFile(path, content, options) {
|
|
123
214
|
const normalizedPath = this.normalizePath(path);
|
|
124
215
|
let existing = "";
|
|
125
216
|
try {
|
|
126
|
-
existing =
|
|
217
|
+
existing = this.readFile(normalizedPath);
|
|
127
218
|
} catch {}
|
|
128
219
|
const newContent = typeof existing === "string" && typeof content === "string" ? existing + content : this.concatBuffers(typeof existing === "string" ? new TextEncoder().encode(existing) : existing, typeof content === "string" ? new TextEncoder().encode(content) : content);
|
|
129
|
-
|
|
220
|
+
this.writeFile(normalizedPath, newContent, options);
|
|
130
221
|
}
|
|
131
|
-
|
|
222
|
+
exists(path) {
|
|
132
223
|
const normalizedPath = this.normalizePath(path);
|
|
133
224
|
return this.entries.has(normalizedPath);
|
|
134
225
|
}
|
|
135
|
-
|
|
226
|
+
stat(path) {
|
|
136
227
|
const normalizedPath = this.normalizePath(path);
|
|
137
228
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
138
229
|
if (!entry) {
|
|
@@ -140,7 +231,7 @@ class Filesystem {
|
|
|
140
231
|
}
|
|
141
232
|
return this.entryToStat(entry);
|
|
142
233
|
}
|
|
143
|
-
|
|
234
|
+
lstat(path) {
|
|
144
235
|
const normalizedPath = this.normalizePath(path);
|
|
145
236
|
const entry = this.entries.get(normalizedPath);
|
|
146
237
|
if (!entry) {
|
|
@@ -148,7 +239,7 @@ class Filesystem {
|
|
|
148
239
|
}
|
|
149
240
|
return this.entryToStat(entry);
|
|
150
241
|
}
|
|
151
|
-
|
|
242
|
+
mkdir(path, options) {
|
|
152
243
|
const normalizedPath = this.normalizePath(path);
|
|
153
244
|
if (this.entries.has(normalizedPath)) {
|
|
154
245
|
if (options?.recursive) {
|
|
@@ -170,7 +261,7 @@ class Filesystem {
|
|
|
170
261
|
mtime: new Date
|
|
171
262
|
});
|
|
172
263
|
}
|
|
173
|
-
|
|
264
|
+
readdir(path) {
|
|
174
265
|
const normalizedPath = this.normalizePath(path);
|
|
175
266
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
176
267
|
if (!entry) {
|
|
@@ -193,7 +284,7 @@ class Filesystem {
|
|
|
193
284
|
}
|
|
194
285
|
return names.sort();
|
|
195
286
|
}
|
|
196
|
-
|
|
287
|
+
readdirWithFileTypes(path) {
|
|
197
288
|
const normalizedPath = this.normalizePath(path);
|
|
198
289
|
const entry = this.resolveSymlinks(normalizedPath);
|
|
199
290
|
if (!entry) {
|
|
@@ -221,7 +312,7 @@ class Filesystem {
|
|
|
221
312
|
}
|
|
222
313
|
return dirents.sort((a, b) => a.name.localeCompare(b.name));
|
|
223
314
|
}
|
|
224
|
-
|
|
315
|
+
rm(path, options) {
|
|
225
316
|
const normalizedPath = this.normalizePath(path);
|
|
226
317
|
const entry = this.entries.get(normalizedPath);
|
|
227
318
|
if (!entry) {
|
|
@@ -230,7 +321,7 @@ class Filesystem {
|
|
|
230
321
|
throw new Error(`ENOENT: no such file or directory, rm '${path}'`);
|
|
231
322
|
}
|
|
232
323
|
if (entry.type === "directory") {
|
|
233
|
-
const children =
|
|
324
|
+
const children = this.readdir(normalizedPath);
|
|
234
325
|
if (children.length > 0 && !options?.recursive) {
|
|
235
326
|
throw new Error(`ENOTEMPTY: directory not empty, rm '${path}'`);
|
|
236
327
|
}
|
|
@@ -245,7 +336,7 @@ class Filesystem {
|
|
|
245
336
|
}
|
|
246
337
|
this.entries.delete(normalizedPath);
|
|
247
338
|
}
|
|
248
|
-
|
|
339
|
+
cp(src, dest, options) {
|
|
249
340
|
const srcPath = this.normalizePath(src);
|
|
250
341
|
const destPath = this.normalizePath(dest);
|
|
251
342
|
const entry = this.entries.get(srcPath);
|
|
@@ -271,7 +362,7 @@ class Filesystem {
|
|
|
271
362
|
this.entries.set(destPath, this.cloneEntry(entry));
|
|
272
363
|
}
|
|
273
364
|
}
|
|
274
|
-
|
|
365
|
+
mv(src, dest) {
|
|
275
366
|
const srcPath = this.normalizePath(src);
|
|
276
367
|
const destPath = this.normalizePath(dest);
|
|
277
368
|
const entry = this.entries.get(srcPath);
|
|
@@ -317,7 +408,7 @@ class Filesystem {
|
|
|
317
408
|
getAllPaths() {
|
|
318
409
|
return [...this.entries.keys()].sort();
|
|
319
410
|
}
|
|
320
|
-
|
|
411
|
+
chmod(path, mode) {
|
|
321
412
|
const normalizedPath = this.normalizePath(path);
|
|
322
413
|
const entry = this.entries.get(normalizedPath);
|
|
323
414
|
if (!entry) {
|
|
@@ -326,7 +417,7 @@ class Filesystem {
|
|
|
326
417
|
entry.mode = mode;
|
|
327
418
|
entry.mtime = new Date;
|
|
328
419
|
}
|
|
329
|
-
|
|
420
|
+
symlink(target, linkPath) {
|
|
330
421
|
const normalizedLinkPath = this.normalizePath(linkPath);
|
|
331
422
|
if (this.entries.has(normalizedLinkPath)) {
|
|
332
423
|
throw new Error(`EEXIST: file already exists, symlink '${linkPath}'`);
|
|
@@ -339,7 +430,7 @@ class Filesystem {
|
|
|
339
430
|
mtime: new Date
|
|
340
431
|
});
|
|
341
432
|
}
|
|
342
|
-
|
|
433
|
+
link(existingPath, newPath) {
|
|
343
434
|
const srcPath = this.normalizePath(existingPath);
|
|
344
435
|
const destPath = this.normalizePath(newPath);
|
|
345
436
|
const entry = this.entries.get(srcPath);
|
|
@@ -360,7 +451,7 @@ class Filesystem {
|
|
|
360
451
|
mtime: new Date
|
|
361
452
|
});
|
|
362
453
|
}
|
|
363
|
-
|
|
454
|
+
readlink(path) {
|
|
364
455
|
const normalizedPath = this.normalizePath(path);
|
|
365
456
|
const entry = this.entries.get(normalizedPath);
|
|
366
457
|
if (!entry) {
|
|
@@ -371,7 +462,7 @@ class Filesystem {
|
|
|
371
462
|
}
|
|
372
463
|
return entry.target;
|
|
373
464
|
}
|
|
374
|
-
|
|
465
|
+
realpath(path) {
|
|
375
466
|
const normalizedPath = this.normalizePath(path);
|
|
376
467
|
const parts = normalizedPath.split("/").filter(Boolean);
|
|
377
468
|
let resolved = "/";
|
|
@@ -397,7 +488,7 @@ class Filesystem {
|
|
|
397
488
|
}
|
|
398
489
|
return resolved;
|
|
399
490
|
}
|
|
400
|
-
|
|
491
|
+
utimes(path, atime, mtime) {
|
|
401
492
|
const normalizedPath = this.normalizePath(path);
|
|
402
493
|
const entry = this.entries.get(normalizedPath);
|
|
403
494
|
if (!entry) {
|
|
@@ -541,39 +632,559 @@ class Filesystem {
|
|
|
541
632
|
function createFilesystem(options) {
|
|
542
633
|
return Filesystem.create(options);
|
|
543
634
|
}
|
|
635
|
+
function wrapFilesystemForJustBash(fs) {
|
|
636
|
+
return {
|
|
637
|
+
readFile: async (path, options) => fs.readFile(path, options),
|
|
638
|
+
writeFile: async (path, content, options) => fs.writeFile(path, content, options),
|
|
639
|
+
appendFile: async (path, content, options) => fs.appendFile(path, content, options),
|
|
640
|
+
exists: async (path) => fs.exists(path),
|
|
641
|
+
stat: async (path) => fs.stat(path),
|
|
642
|
+
lstat: async (path) => fs.lstat(path),
|
|
643
|
+
mkdir: async (path, options) => fs.mkdir(path, options),
|
|
644
|
+
readdir: async (path) => fs.readdir(path),
|
|
645
|
+
readdirWithFileTypes: async (path) => fs.readdirWithFileTypes(path),
|
|
646
|
+
rm: async (path, options) => fs.rm(path, options),
|
|
647
|
+
cp: async (src, dest, options) => fs.cp(src, dest, options),
|
|
648
|
+
mv: async (src, dest) => fs.mv(src, dest),
|
|
649
|
+
chmod: async (path, mode) => fs.chmod(path, mode),
|
|
650
|
+
symlink: async (target, linkPath) => fs.symlink(target, linkPath),
|
|
651
|
+
link: async (existingPath, newPath) => fs.link(existingPath, newPath),
|
|
652
|
+
readlink: async (path) => fs.readlink(path),
|
|
653
|
+
realpath: async (path) => fs.realpath(path),
|
|
654
|
+
utimes: async (path, atime, mtime) => fs.utimes(path, atime, mtime),
|
|
655
|
+
readFileBuffer: async (path) => fs.readFileBuffer(path),
|
|
656
|
+
resolvePath: (base, path) => fs.resolvePath(base, path),
|
|
657
|
+
getAllPaths: () => fs.getAllPaths()
|
|
658
|
+
};
|
|
659
|
+
}
|
|
544
660
|
|
|
545
|
-
// src/
|
|
546
|
-
|
|
547
|
-
var KNOWN_SUBPATHS = {
|
|
548
|
-
react: ["jsx-runtime", "jsx-dev-runtime"],
|
|
549
|
-
"react-dom": ["client", "server"]
|
|
550
|
-
};
|
|
661
|
+
// src/commands/index.ts
|
|
662
|
+
import { defineCommand } from "just-bash/browser";
|
|
551
663
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return `${
|
|
664
|
+
// src/commands/types.ts
|
|
665
|
+
function formatSize(bytes) {
|
|
666
|
+
if (bytes < 1024)
|
|
667
|
+
return `${bytes} B`;
|
|
668
|
+
if (bytes < 1024 * 1024)
|
|
669
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
670
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
671
|
+
}
|
|
672
|
+
function formatDiagnostics(diagnostics) {
|
|
673
|
+
if (diagnostics.length === 0)
|
|
674
|
+
return "";
|
|
675
|
+
return diagnostics.map((d) => {
|
|
676
|
+
const severity = d.severity.toUpperCase();
|
|
677
|
+
if (d.file) {
|
|
678
|
+
const loc = `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}`;
|
|
679
|
+
return `${severity}: ${loc}: ${d.message}`;
|
|
680
|
+
}
|
|
681
|
+
return `${severity}: ${d.message}`;
|
|
682
|
+
}).join(`
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
function formatBundleErrors(errors) {
|
|
686
|
+
if (errors.length === 0)
|
|
687
|
+
return "";
|
|
688
|
+
return errors.map((e) => {
|
|
689
|
+
let output = "";
|
|
690
|
+
if (e.location) {
|
|
691
|
+
const loc = `${e.location.file}:${e.location.line}${e.location.column ? `:${e.location.column}` : ""}`;
|
|
692
|
+
output += `ERROR: ${loc}: ${e.text}`;
|
|
693
|
+
if (e.location.lineText) {
|
|
694
|
+
output += `
|
|
695
|
+
${e.location.line} | ${e.location.lineText}`;
|
|
696
|
+
if (e.location.column) {
|
|
697
|
+
const padding = " ".repeat(String(e.location.line).length + 3 + e.location.column - 1);
|
|
698
|
+
output += `
|
|
699
|
+
${padding}^`;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
output += `ERROR: ${e.text}`;
|
|
704
|
+
}
|
|
705
|
+
return output;
|
|
706
|
+
}).join(`
|
|
707
|
+
|
|
708
|
+
`);
|
|
709
|
+
}
|
|
710
|
+
// src/commands/index.ts
|
|
711
|
+
function createSandlotCommand(sandboxRef) {
|
|
712
|
+
return defineCommand("sandlot", async (args, ctx) => {
|
|
713
|
+
const subcommand = args[0];
|
|
714
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
715
|
+
return showHelp();
|
|
716
|
+
}
|
|
717
|
+
switch (subcommand) {
|
|
718
|
+
case "build":
|
|
719
|
+
return handleBuild(sandboxRef, args.slice(1));
|
|
720
|
+
case "typecheck":
|
|
721
|
+
case "tsc":
|
|
722
|
+
return handleTypecheck(sandboxRef, args.slice(1));
|
|
723
|
+
case "install":
|
|
724
|
+
case "add":
|
|
725
|
+
case "i":
|
|
726
|
+
return handleInstall(sandboxRef, args.slice(1));
|
|
727
|
+
case "uninstall":
|
|
728
|
+
case "remove":
|
|
729
|
+
case "rm":
|
|
730
|
+
return handleUninstall(sandboxRef, args.slice(1));
|
|
731
|
+
case "run":
|
|
732
|
+
return handleRun(sandboxRef, args.slice(1));
|
|
733
|
+
default:
|
|
734
|
+
return {
|
|
735
|
+
stdout: "",
|
|
736
|
+
stderr: `Unknown command: sandlot ${subcommand}
|
|
737
|
+
|
|
738
|
+
Run 'sandlot help' for available commands.
|
|
739
|
+
`,
|
|
740
|
+
exitCode: 1
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function showHelp() {
|
|
746
|
+
return {
|
|
747
|
+
stdout: `sandlot - In-browser TypeScript sandbox
|
|
748
|
+
|
|
749
|
+
Usage: sandlot <command> [options]
|
|
750
|
+
|
|
751
|
+
Commands:
|
|
752
|
+
build Build the project (typecheck, bundle)
|
|
753
|
+
run Build and execute code
|
|
754
|
+
typecheck Type check without building (alias: tsc)
|
|
755
|
+
install Install packages (aliases: add, i)
|
|
756
|
+
uninstall Remove packages (aliases: remove, rm)
|
|
757
|
+
help Show this help message
|
|
758
|
+
|
|
759
|
+
Run 'sandlot <command> --help' for command-specific options.
|
|
760
|
+
|
|
761
|
+
Examples:
|
|
762
|
+
sandlot build
|
|
763
|
+
sandlot run
|
|
764
|
+
sandlot run --skip-typecheck --timeout 5000
|
|
765
|
+
sandlot install react react-dom
|
|
766
|
+
sandlot typecheck
|
|
767
|
+
`,
|
|
768
|
+
stderr: "",
|
|
769
|
+
exitCode: 0
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
async function handleBuild(sandboxRef, args) {
|
|
773
|
+
let entryPoint;
|
|
774
|
+
let skipTypecheck = false;
|
|
775
|
+
let minify = false;
|
|
776
|
+
let format = "esm";
|
|
777
|
+
for (let i = 0;i < args.length; i++) {
|
|
778
|
+
const arg = args[i];
|
|
779
|
+
if (arg === "--skip-typecheck" || arg === "-s") {
|
|
780
|
+
skipTypecheck = true;
|
|
781
|
+
} else if (arg === "--minify" || arg === "-m") {
|
|
782
|
+
minify = true;
|
|
783
|
+
} else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
|
|
784
|
+
const f = args[++i].toLowerCase();
|
|
785
|
+
if (f === "esm" || f === "iife" || f === "cjs") {
|
|
786
|
+
format = f;
|
|
787
|
+
}
|
|
788
|
+
} else if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
|
|
789
|
+
entryPoint = args[++i];
|
|
790
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
791
|
+
return {
|
|
792
|
+
stdout: `Usage: sandlot build [options]
|
|
793
|
+
|
|
794
|
+
Options:
|
|
795
|
+
--entry, -e <path> Entry point (default: from package.json main)
|
|
796
|
+
--skip-typecheck, -s Skip type checking
|
|
797
|
+
--minify, -m Minify output
|
|
798
|
+
--format, -f <fmt> Output format (esm|iife|cjs)
|
|
799
|
+
--help, -h Show this help message
|
|
800
|
+
|
|
801
|
+
Examples:
|
|
802
|
+
sandlot build
|
|
803
|
+
sandlot build --entry /src/main.ts
|
|
804
|
+
sandlot build --skip-typecheck --minify
|
|
805
|
+
`,
|
|
806
|
+
stderr: "",
|
|
807
|
+
exitCode: 0
|
|
808
|
+
};
|
|
809
|
+
} else if (arg && !arg.startsWith("-") && !entryPoint) {
|
|
810
|
+
entryPoint = arg;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const result = await sandboxRef.build({
|
|
814
|
+
entryPoint,
|
|
815
|
+
skipTypecheck,
|
|
816
|
+
minify,
|
|
817
|
+
format
|
|
818
|
+
});
|
|
819
|
+
if (!result.success) {
|
|
820
|
+
let stderr = `Build failed`;
|
|
821
|
+
switch (result.phase) {
|
|
822
|
+
case "entry":
|
|
823
|
+
stderr = `Build failed: ${result.message}
|
|
824
|
+
`;
|
|
825
|
+
break;
|
|
826
|
+
case "typecheck":
|
|
827
|
+
if (result.diagnostics) {
|
|
828
|
+
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
829
|
+
stderr = `Build failed: Type check errors
|
|
830
|
+
|
|
831
|
+
${formatDiagnostics(errors)}
|
|
832
|
+
`;
|
|
833
|
+
} else {
|
|
834
|
+
stderr = `Build failed: Type check errors
|
|
835
|
+
`;
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
case "bundle":
|
|
839
|
+
if (result.bundleErrors && result.bundleErrors.length > 0) {
|
|
840
|
+
stderr = `Build failed: Bundle errors
|
|
841
|
+
|
|
842
|
+
${formatBundleErrors(result.bundleErrors)}
|
|
843
|
+
`;
|
|
844
|
+
} else {
|
|
845
|
+
stderr = `Build failed: Bundle error
|
|
846
|
+
`;
|
|
847
|
+
}
|
|
848
|
+
break;
|
|
849
|
+
default:
|
|
850
|
+
stderr = `Build failed: Unknown error
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
stdout: "",
|
|
855
|
+
stderr,
|
|
856
|
+
exitCode: 1
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
let output = `Build successful!
|
|
860
|
+
`;
|
|
861
|
+
output += `Size: ${formatSize(result.code.length)}
|
|
862
|
+
`;
|
|
863
|
+
output += `Files: ${result.includedFiles.length}
|
|
864
|
+
`;
|
|
865
|
+
if (result.warnings.length > 0) {
|
|
866
|
+
output += `
|
|
867
|
+
Warnings:
|
|
868
|
+
`;
|
|
869
|
+
for (const warning of result.warnings) {
|
|
870
|
+
if (warning.location) {
|
|
871
|
+
output += ` ${warning.location.file}:${warning.location.line}: ${warning.text}
|
|
872
|
+
`;
|
|
873
|
+
} else {
|
|
874
|
+
output += ` ${warning.text}
|
|
875
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
stdout: output,
|
|
881
|
+
stderr: "",
|
|
882
|
+
exitCode: 0
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
async function handleTypecheck(sandboxRef, args) {
|
|
886
|
+
let entryPoint;
|
|
887
|
+
for (let i = 0;i < args.length; i++) {
|
|
888
|
+
const arg = args[i];
|
|
889
|
+
if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
|
|
890
|
+
entryPoint = args[++i];
|
|
891
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
892
|
+
return {
|
|
893
|
+
stdout: `Usage: sandlot typecheck [options]
|
|
894
|
+
|
|
895
|
+
Options:
|
|
896
|
+
--entry, -e <path> Entry point (default: from package.json main)
|
|
897
|
+
--help, -h Show this help message
|
|
898
|
+
|
|
899
|
+
Aliases: sandlot tsc
|
|
900
|
+
|
|
901
|
+
Examples:
|
|
902
|
+
sandlot typecheck
|
|
903
|
+
sandlot typecheck --entry /src/main.ts
|
|
904
|
+
`,
|
|
905
|
+
stderr: "",
|
|
906
|
+
exitCode: 0
|
|
907
|
+
};
|
|
908
|
+
} else if (arg && !arg.startsWith("-") && !entryPoint) {
|
|
909
|
+
entryPoint = arg;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const result = await sandboxRef.typecheck({ entryPoint });
|
|
914
|
+
if (!result.success) {
|
|
915
|
+
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
916
|
+
const formatted = formatDiagnostics(errors);
|
|
917
|
+
return {
|
|
918
|
+
stdout: "",
|
|
919
|
+
stderr: `Type check failed:
|
|
920
|
+
${formatted}
|
|
921
|
+
`,
|
|
922
|
+
exitCode: 1
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
const warnings = result.diagnostics.filter((d) => d.severity === "warning");
|
|
926
|
+
let output = `Type check passed.
|
|
927
|
+
`;
|
|
928
|
+
if (warnings.length > 0) {
|
|
929
|
+
output += `
|
|
930
|
+
Warnings:
|
|
931
|
+
${formatDiagnostics(warnings)}
|
|
932
|
+
`;
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
stdout: output,
|
|
936
|
+
stderr: "",
|
|
937
|
+
exitCode: 0
|
|
938
|
+
};
|
|
939
|
+
} catch (err) {
|
|
940
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
941
|
+
return {
|
|
942
|
+
stdout: "",
|
|
943
|
+
stderr: `Type check error: ${message}
|
|
944
|
+
`,
|
|
945
|
+
exitCode: 1
|
|
946
|
+
};
|
|
556
947
|
}
|
|
557
|
-
|
|
558
|
-
|
|
948
|
+
}
|
|
949
|
+
async function handleInstall(sandboxRef, args) {
|
|
950
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
951
|
+
return {
|
|
952
|
+
stdout: `Usage: sandlot install <package>[@version] [...packages]
|
|
953
|
+
|
|
954
|
+
Examples:
|
|
955
|
+
sandlot install react
|
|
956
|
+
sandlot install lodash@4.17.21
|
|
957
|
+
sandlot install @tanstack/react-query@5
|
|
958
|
+
sandlot install react react-dom
|
|
959
|
+
|
|
960
|
+
Aliases: sandlot add, sandlot i
|
|
961
|
+
`,
|
|
962
|
+
stderr: "",
|
|
963
|
+
exitCode: 0
|
|
964
|
+
};
|
|
559
965
|
}
|
|
560
|
-
|
|
561
|
-
|
|
966
|
+
const packages = args.filter((a) => !!a && !a.startsWith("-"));
|
|
967
|
+
if (packages.length === 0) {
|
|
968
|
+
return {
|
|
969
|
+
stdout: "",
|
|
970
|
+
stderr: `Usage: sandlot install <package>[@version] [...packages]
|
|
971
|
+
|
|
972
|
+
Run 'sandlot install --help' for more information.
|
|
973
|
+
`,
|
|
974
|
+
exitCode: 1
|
|
975
|
+
};
|
|
562
976
|
}
|
|
563
|
-
|
|
564
|
-
|
|
977
|
+
const results = [];
|
|
978
|
+
let hasError = false;
|
|
979
|
+
for (const packageSpec of packages) {
|
|
980
|
+
try {
|
|
981
|
+
const result = await sandboxRef.install(packageSpec);
|
|
982
|
+
let status = `+ ${result.name}@${result.version}`;
|
|
983
|
+
if (result.typesInstalled) {
|
|
984
|
+
status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
|
|
985
|
+
if (result.fromCache) {
|
|
986
|
+
status += " [cached]";
|
|
987
|
+
}
|
|
988
|
+
} else if (result.typesError) {
|
|
989
|
+
status += ` (no types: ${result.typesError})`;
|
|
990
|
+
}
|
|
991
|
+
results.push(status);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
hasError = true;
|
|
994
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
995
|
+
results.push(`x ${packageSpec}: ${message}`);
|
|
996
|
+
}
|
|
565
997
|
}
|
|
566
|
-
|
|
567
|
-
|
|
998
|
+
const output = results.join(`
|
|
999
|
+
`) + `
|
|
1000
|
+
`;
|
|
1001
|
+
return hasError ? { stdout: "", stderr: output, exitCode: 1 } : { stdout: output, stderr: "", exitCode: 0 };
|
|
1002
|
+
}
|
|
1003
|
+
async function handleUninstall(sandboxRef, args) {
|
|
1004
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1005
|
+
return {
|
|
1006
|
+
stdout: `Usage: sandlot uninstall <package> [...packages]
|
|
1007
|
+
|
|
1008
|
+
Examples:
|
|
1009
|
+
sandlot uninstall lodash
|
|
1010
|
+
sandlot uninstall react react-dom
|
|
1011
|
+
|
|
1012
|
+
Aliases: sandlot remove, sandlot rm
|
|
1013
|
+
`,
|
|
1014
|
+
stderr: "",
|
|
1015
|
+
exitCode: 0
|
|
1016
|
+
};
|
|
568
1017
|
}
|
|
569
|
-
|
|
570
|
-
|
|
1018
|
+
const packages = args.filter((a) => !!a && !a.startsWith("-"));
|
|
1019
|
+
if (packages.length === 0) {
|
|
1020
|
+
return {
|
|
1021
|
+
stdout: "",
|
|
1022
|
+
stderr: `Usage: sandlot uninstall <package> [...packages]
|
|
1023
|
+
|
|
1024
|
+
Run 'sandlot uninstall --help' for more information.
|
|
1025
|
+
`,
|
|
1026
|
+
exitCode: 1
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
const results = [];
|
|
1030
|
+
let hasError = false;
|
|
1031
|
+
for (const packageName of packages) {
|
|
1032
|
+
try {
|
|
1033
|
+
const result = await sandboxRef.uninstall(packageName);
|
|
1034
|
+
if (result.removed) {
|
|
1035
|
+
results.push(`- ${result.name}`);
|
|
1036
|
+
} else {
|
|
1037
|
+
results.push(`x ${packageName}: not installed`);
|
|
1038
|
+
hasError = true;
|
|
1039
|
+
}
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
hasError = true;
|
|
1042
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1043
|
+
results.push(`x ${packageName}: ${message}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const output = results.join(`
|
|
1047
|
+
`) + `
|
|
1048
|
+
`;
|
|
1049
|
+
return hasError ? { stdout: "", stderr: output, exitCode: 1 } : { stdout: output, stderr: "", exitCode: 0 };
|
|
1050
|
+
}
|
|
1051
|
+
async function handleRun(sandboxRef, args) {
|
|
1052
|
+
let entryPoint;
|
|
1053
|
+
let skipTypecheck = false;
|
|
1054
|
+
let timeout = 30000;
|
|
1055
|
+
let entryExport = "main";
|
|
1056
|
+
for (let i = 0;i < args.length; i++) {
|
|
1057
|
+
const arg = args[i];
|
|
1058
|
+
if (arg === "--skip-typecheck" || arg === "-s") {
|
|
1059
|
+
skipTypecheck = true;
|
|
1060
|
+
} else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
|
|
1061
|
+
const t = parseInt(args[++i], 10);
|
|
1062
|
+
if (!isNaN(t))
|
|
1063
|
+
timeout = t;
|
|
1064
|
+
} else if ((arg === "--entry" || arg === "-e") && args[i + 1]) {
|
|
1065
|
+
entryPoint = args[++i];
|
|
1066
|
+
} else if ((arg === "--export" || arg === "-x") && args[i + 1]) {
|
|
1067
|
+
const e = args[++i].toLowerCase();
|
|
1068
|
+
if (e === "main" || e === "default") {
|
|
1069
|
+
entryExport = e;
|
|
1070
|
+
}
|
|
1071
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
1072
|
+
return {
|
|
1073
|
+
stdout: `Usage: sandlot run [options]
|
|
1074
|
+
|
|
1075
|
+
Options:
|
|
1076
|
+
--entry, -e <path> Entry point (default: from package.json main)
|
|
1077
|
+
--skip-typecheck, -s Skip type checking
|
|
1078
|
+
--timeout, -t <ms> Execution timeout (default: 30000, 0 = none)
|
|
1079
|
+
--export, -x <name> Export to call: main or default (default: main)
|
|
1080
|
+
--help, -h Show this help message
|
|
1081
|
+
|
|
1082
|
+
Examples:
|
|
1083
|
+
sandlot run
|
|
1084
|
+
sandlot run --entry /src/main.ts
|
|
1085
|
+
sandlot run --skip-typecheck --timeout 5000
|
|
1086
|
+
sandlot run --export default
|
|
1087
|
+
`,
|
|
1088
|
+
stderr: "",
|
|
1089
|
+
exitCode: 0
|
|
1090
|
+
};
|
|
1091
|
+
} else if (arg && !arg.startsWith("-") && !entryPoint) {
|
|
1092
|
+
entryPoint = arg;
|
|
1093
|
+
}
|
|
571
1094
|
}
|
|
572
|
-
|
|
573
|
-
|
|
1095
|
+
try {
|
|
1096
|
+
const result = await sandboxRef.run({
|
|
1097
|
+
entryPoint,
|
|
1098
|
+
skipTypecheck,
|
|
1099
|
+
timeout,
|
|
1100
|
+
entryExport
|
|
1101
|
+
});
|
|
1102
|
+
if (!result.success) {
|
|
1103
|
+
let stderr = "";
|
|
1104
|
+
if (result.buildFailure) {
|
|
1105
|
+
stderr = `Run failed: Build error in ${result.buildFailure.phase} phase`;
|
|
1106
|
+
if (result.buildFailure.message) {
|
|
1107
|
+
stderr += `
|
|
1108
|
+
${result.buildFailure.message}`;
|
|
1109
|
+
}
|
|
1110
|
+
stderr += `
|
|
1111
|
+
`;
|
|
1112
|
+
} else {
|
|
1113
|
+
stderr = `Run failed: ${result.error ?? "Unknown error"}
|
|
1114
|
+
`;
|
|
1115
|
+
}
|
|
1116
|
+
let stdout = "";
|
|
1117
|
+
if (result.logs.length > 0) {
|
|
1118
|
+
stdout = result.logs.join(`
|
|
1119
|
+
`) + `
|
|
1120
|
+
`;
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
stdout,
|
|
1124
|
+
stderr,
|
|
1125
|
+
exitCode: 1
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
let output = "";
|
|
1129
|
+
if (result.logs.length > 0) {
|
|
1130
|
+
output = result.logs.join(`
|
|
1131
|
+
`) + `
|
|
1132
|
+
`;
|
|
1133
|
+
}
|
|
1134
|
+
if (result.returnValue !== undefined) {
|
|
1135
|
+
const returnStr = typeof result.returnValue === "object" ? JSON.stringify(result.returnValue, null, 2) : String(result.returnValue);
|
|
1136
|
+
output += `[return] ${returnStr}
|
|
1137
|
+
`;
|
|
1138
|
+
}
|
|
1139
|
+
if (result.executionTimeMs !== undefined) {
|
|
1140
|
+
output += `
|
|
1141
|
+
Completed in ${result.executionTimeMs.toFixed(2)}ms
|
|
1142
|
+
`;
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
stdout: output,
|
|
1146
|
+
stderr: "",
|
|
1147
|
+
exitCode: 0
|
|
1148
|
+
};
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1151
|
+
return {
|
|
1152
|
+
stdout: "",
|
|
1153
|
+
stderr: `Run error: ${message}
|
|
1154
|
+
`,
|
|
1155
|
+
exitCode: 1
|
|
1156
|
+
};
|
|
574
1157
|
}
|
|
575
1158
|
}
|
|
1159
|
+
function createDefaultCommands(sandboxRef) {
|
|
1160
|
+
return [createSandlotCommand(sandboxRef)];
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/core/sandbox.ts
|
|
1164
|
+
var DEFAULT_ENTRY_POINT = "./index.ts";
|
|
1165
|
+
var TSCONFIG_PATH = "/tsconfig.json";
|
|
576
1166
|
var PACKAGE_JSON_PATH = "/package.json";
|
|
1167
|
+
var DEFAULT_PACKAGE_JSON = {
|
|
1168
|
+
main: DEFAULT_ENTRY_POINT,
|
|
1169
|
+
dependencies: {}
|
|
1170
|
+
};
|
|
1171
|
+
var DEFAULT_TSCONFIG = {
|
|
1172
|
+
compilerOptions: {
|
|
1173
|
+
target: "ES2020",
|
|
1174
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
1175
|
+
module: "ESNext",
|
|
1176
|
+
moduleResolution: "bundler",
|
|
1177
|
+
jsx: "react-jsx",
|
|
1178
|
+
strict: true,
|
|
1179
|
+
noEmit: true,
|
|
1180
|
+
esModuleInterop: true,
|
|
1181
|
+
skipLibCheck: true,
|
|
1182
|
+
resolveJsonModule: true,
|
|
1183
|
+
isolatedModules: true
|
|
1184
|
+
},
|
|
1185
|
+
include: ["**/*.ts", "**/*.tsx"],
|
|
1186
|
+
exclude: ["node_modules"]
|
|
1187
|
+
};
|
|
577
1188
|
function parsePackageSpec(spec) {
|
|
578
1189
|
if (spec.startsWith("@")) {
|
|
579
1190
|
const slashIndex = spec.indexOf("/");
|
|
@@ -599,1755 +1210,358 @@ function parsePackageSpec(spec) {
|
|
|
599
1210
|
version: spec.slice(atIndex + 1)
|
|
600
1211
|
};
|
|
601
1212
|
}
|
|
602
|
-
function
|
|
603
|
-
return name.startsWith("@types/");
|
|
604
|
-
}
|
|
605
|
-
function extractVersionFromUrl(url, packageName) {
|
|
606
|
-
const exactRegex = new RegExp(`${escapeRegExp(packageName)}@([^/]+)`);
|
|
607
|
-
const exactMatch = url.match(exactRegex);
|
|
608
|
-
if (exactMatch?.[1]) {
|
|
609
|
-
return exactMatch[1];
|
|
610
|
-
}
|
|
611
|
-
if (packageName.startsWith("@")) {
|
|
612
|
-
const scopedParts = packageName.split("/");
|
|
613
|
-
if (scopedParts.length === 2 && scopedParts[1]) {
|
|
614
|
-
const partialRegex = new RegExp(`${escapeRegExp(scopedParts[1])}@([^/]+)`);
|
|
615
|
-
const partialMatch = url.match(partialRegex);
|
|
616
|
-
if (partialMatch?.[1]) {
|
|
617
|
-
return partialMatch[1];
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
const genericMatch = url.match(/@(\d+\.\d+\.\d+[^/]*)/);
|
|
622
|
-
if (genericMatch?.[1]) {
|
|
623
|
-
return genericMatch[1];
|
|
624
|
-
}
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
async function fetchVersionFromNpm(name) {
|
|
628
|
-
const registryUrl = `https://registry.npmjs.org/${name}/latest`;
|
|
629
|
-
const response = await fetch(registryUrl);
|
|
630
|
-
if (!response.ok) {
|
|
631
|
-
throw new Error(`Failed to fetch version from npm: ${response.status}`);
|
|
632
|
-
}
|
|
633
|
-
const data = await response.json();
|
|
634
|
-
return data.version;
|
|
635
|
-
}
|
|
636
|
-
function extractVersionFromHeaders(headers, packageName) {
|
|
637
|
-
const esmId = headers.get("x-esm-id");
|
|
638
|
-
if (esmId) {
|
|
639
|
-
const version = extractVersionFromUrl(esmId, packageName);
|
|
640
|
-
if (version)
|
|
641
|
-
return version;
|
|
642
|
-
}
|
|
643
|
-
const typesHeader = headers.get("X-TypeScript-Types");
|
|
644
|
-
if (typesHeader) {
|
|
645
|
-
const versionMatch = typesHeader.match(/@(\d+\.\d+\.\d+[^/]*)/);
|
|
646
|
-
if (versionMatch?.[1]) {
|
|
647
|
-
return versionMatch[1];
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
async function fetchPackageInfo(name, version, subpath) {
|
|
653
|
-
let url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
|
|
654
|
-
if (subpath) {
|
|
655
|
-
url += `/${subpath}`;
|
|
656
|
-
}
|
|
657
|
-
const response = await fetch(url, { method: "HEAD" });
|
|
658
|
-
if (!response.ok) {
|
|
659
|
-
throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}${subpath ? `/${subpath}` : ""}`);
|
|
660
|
-
}
|
|
661
|
-
const resolvedUrl = response.url;
|
|
662
|
-
let resolvedVersion = extractVersionFromUrl(resolvedUrl, name);
|
|
663
|
-
if (!resolvedVersion) {
|
|
664
|
-
resolvedVersion = extractVersionFromHeaders(response.headers, name);
|
|
665
|
-
}
|
|
666
|
-
if (!resolvedVersion && version && version !== "latest") {
|
|
667
|
-
resolvedVersion = version;
|
|
668
|
-
}
|
|
669
|
-
if (!resolvedVersion) {
|
|
670
|
-
try {
|
|
671
|
-
resolvedVersion = await fetchVersionFromNpm(name);
|
|
672
|
-
} catch (err) {
|
|
673
|
-
console.warn(`Could not resolve version for ${name}:`, err);
|
|
674
|
-
resolvedVersion = "latest";
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
const typesUrl = response.headers.get("X-TypeScript-Types") ?? undefined;
|
|
678
|
-
return {
|
|
679
|
-
version: resolvedVersion,
|
|
680
|
-
typesUrl: typesUrl ? new URL(typesUrl, resolvedUrl).href : undefined
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
function escapeRegExp(string) {
|
|
684
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
685
|
-
}
|
|
686
|
-
async function fetchTypeDefinitions(typesUrl, packageName) {
|
|
687
|
-
const types = new Map;
|
|
1213
|
+
function readPackageJson(fs) {
|
|
688
1214
|
try {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1215
|
+
if (fs.exists(PACKAGE_JSON_PATH)) {
|
|
1216
|
+
const content = fs.readFile(PACKAGE_JSON_PATH);
|
|
1217
|
+
return JSON.parse(content);
|
|
692
1218
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
types.set(typePath, content);
|
|
696
|
-
const refs = parseTypeReferences(content);
|
|
697
|
-
await fetchReferencedTypes(refs, typesUrl, packageName, types);
|
|
698
|
-
} catch (err) {
|
|
699
|
-
console.warn(`Failed to fetch types for ${packageName}:`, err);
|
|
700
|
-
throw err;
|
|
701
|
-
}
|
|
702
|
-
return types;
|
|
703
|
-
}
|
|
704
|
-
function parseTypeReferences(content) {
|
|
705
|
-
const paths = [];
|
|
706
|
-
const types = [];
|
|
707
|
-
const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
|
|
708
|
-
let match;
|
|
709
|
-
while ((match = pathRegex.exec(content)) !== null) {
|
|
710
|
-
if (match[1])
|
|
711
|
-
paths.push(match[1]);
|
|
712
|
-
}
|
|
713
|
-
const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
|
|
714
|
-
while ((match = typesRegex.exec(content)) !== null) {
|
|
715
|
-
if (match[1])
|
|
716
|
-
types.push(match[1]);
|
|
717
|
-
}
|
|
718
|
-
return { paths, types };
|
|
719
|
-
}
|
|
720
|
-
async function fetchReferencedTypes(refs, baseUrl, packageName, collected, visited = new Set) {
|
|
721
|
-
for (const pathRef of refs.paths) {
|
|
722
|
-
const refUrl = new URL(pathRef, baseUrl).href;
|
|
723
|
-
if (visited.has(refUrl))
|
|
724
|
-
continue;
|
|
725
|
-
visited.add(refUrl);
|
|
726
|
-
try {
|
|
727
|
-
const response = await fetch(refUrl);
|
|
728
|
-
if (!response.ok)
|
|
729
|
-
continue;
|
|
730
|
-
const content = await response.text();
|
|
731
|
-
const fileName = pathRef.split("/").pop() ?? "types.d.ts";
|
|
732
|
-
const typePath = `/node_modules/${packageName}/${fileName}`;
|
|
733
|
-
collected.set(typePath, content);
|
|
734
|
-
const nestedRefs = parseTypeReferences(content);
|
|
735
|
-
await fetchReferencedTypes(nestedRefs, refUrl, packageName, collected, visited);
|
|
736
|
-
} catch {}
|
|
737
|
-
}
|
|
1219
|
+
} catch {}
|
|
1220
|
+
return {};
|
|
738
1221
|
}
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
return types;
|
|
745
|
-
}
|
|
746
|
-
const response = await fetch(info.typesUrl);
|
|
747
|
-
if (!response.ok) {
|
|
748
|
-
return types;
|
|
749
|
-
}
|
|
750
|
-
const content = await response.text();
|
|
751
|
-
const typePath = `/node_modules/${packageName}/${subpath}.d.ts`;
|
|
752
|
-
types.set(typePath, content);
|
|
753
|
-
const indexTypePath = `/node_modules/${packageName}/${subpath}/index.d.ts`;
|
|
754
|
-
types.set(indexTypePath, content);
|
|
755
|
-
const refs = parseTypeReferences(content);
|
|
756
|
-
await fetchReferencedTypes(refs, info.typesUrl, packageName, types);
|
|
757
|
-
} catch (err) {
|
|
758
|
-
console.warn(`Failed to fetch types for ${packageName}/${subpath}:`, err);
|
|
1222
|
+
function getEntryPoint(fs) {
|
|
1223
|
+
const pkg = readPackageJson(fs);
|
|
1224
|
+
const main = pkg.main ?? DEFAULT_ENTRY_POINT;
|
|
1225
|
+
if (main.startsWith("/")) {
|
|
1226
|
+
return main;
|
|
759
1227
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
async function fetchTypesPackageContent(name, version) {
|
|
763
|
-
const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
|
|
764
|
-
const headResponse = await fetch(url, { method: "HEAD" });
|
|
765
|
-
if (!headResponse.ok) {
|
|
766
|
-
throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
|
|
767
|
-
}
|
|
768
|
-
const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
|
|
769
|
-
const indexUrl = `${ESM_CDN_BASE}/${name}@${resolvedVersion}/index.d.ts`;
|
|
770
|
-
const response = await fetch(indexUrl);
|
|
771
|
-
if (!response.ok) {
|
|
772
|
-
throw new Error(`Failed to fetch types from ${name}: ${response.status}`);
|
|
773
|
-
}
|
|
774
|
-
const content = await response.text();
|
|
775
|
-
const types = new Map;
|
|
776
|
-
const typePath = `/node_modules/${name}/index.d.ts`;
|
|
777
|
-
types.set(typePath, content);
|
|
778
|
-
const refs = parseTypeReferences(content);
|
|
779
|
-
for (const pathRef of refs.paths) {
|
|
780
|
-
try {
|
|
781
|
-
const refUrl = new URL(pathRef, indexUrl).href;
|
|
782
|
-
const refResponse = await fetch(refUrl);
|
|
783
|
-
if (refResponse.ok) {
|
|
784
|
-
const refContent = await refResponse.text();
|
|
785
|
-
const fileName = pathRef.startsWith("./") ? pathRef.slice(2) : pathRef;
|
|
786
|
-
const refTypePath = `/node_modules/${name}/${fileName}`;
|
|
787
|
-
types.set(refTypePath, refContent);
|
|
788
|
-
}
|
|
789
|
-
} catch {}
|
|
1228
|
+
if (main.startsWith("./")) {
|
|
1229
|
+
return "/" + main.slice(2);
|
|
790
1230
|
}
|
|
791
|
-
return
|
|
1231
|
+
return "/" + main;
|
|
792
1232
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const content = await fs.readFile(PACKAGE_JSON_PATH);
|
|
797
|
-
const parsed = JSON.parse(content);
|
|
798
|
-
return {
|
|
799
|
-
dependencies: parsed.dependencies ?? {}
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
} catch {}
|
|
803
|
-
return { dependencies: {} };
|
|
1233
|
+
function getInstalledPackages(fs) {
|
|
1234
|
+
const pkg = readPackageJson(fs);
|
|
1235
|
+
return pkg.dependencies ?? {};
|
|
804
1236
|
}
|
|
805
|
-
|
|
1237
|
+
function saveInstalledPackages(fs, dependencies) {
|
|
806
1238
|
let existing = {};
|
|
807
1239
|
try {
|
|
808
|
-
if (
|
|
809
|
-
const content =
|
|
1240
|
+
if (fs.exists(PACKAGE_JSON_PATH)) {
|
|
1241
|
+
const content = fs.readFile(PACKAGE_JSON_PATH);
|
|
810
1242
|
existing = JSON.parse(content);
|
|
811
1243
|
}
|
|
812
1244
|
} catch {}
|
|
813
1245
|
const updated = {
|
|
814
1246
|
...existing,
|
|
815
|
-
dependencies
|
|
816
|
-
};
|
|
817
|
-
await fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
|
|
818
|
-
}
|
|
819
|
-
async function installPackage(fs, packageSpec, options) {
|
|
820
|
-
const { name, version } = parsePackageSpec(packageSpec);
|
|
821
|
-
const { cache } = options ?? {};
|
|
822
|
-
if (isTypesPackage(name)) {
|
|
823
|
-
return installTypesPackage(fs, name, version, cache);
|
|
824
|
-
}
|
|
825
|
-
const info = await fetchPackageInfo(name, version);
|
|
826
|
-
const packageDir = `/node_modules/${name}`;
|
|
827
|
-
await ensureDir(fs, packageDir);
|
|
828
|
-
const packageJsonPath = `${packageDir}/package.json`;
|
|
829
|
-
const packageJson = {
|
|
830
|
-
name,
|
|
831
|
-
version: info.version,
|
|
832
|
-
types: "./index.d.ts",
|
|
833
|
-
main: "./index.js"
|
|
834
|
-
};
|
|
835
|
-
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
836
|
-
let typeFiles = null;
|
|
837
|
-
let fromCache = false;
|
|
838
|
-
if (cache) {
|
|
839
|
-
typeFiles = cache.get(name, info.version);
|
|
840
|
-
if (typeFiles) {
|
|
841
|
-
fromCache = true;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
let typesError;
|
|
845
|
-
if (!typeFiles) {
|
|
846
|
-
typeFiles = new Map;
|
|
847
|
-
if (info.typesUrl) {
|
|
848
|
-
try {
|
|
849
|
-
const mainTypes = await fetchTypeDefinitions(info.typesUrl, name);
|
|
850
|
-
for (const [path, content] of mainTypes) {
|
|
851
|
-
typeFiles.set(path, content);
|
|
852
|
-
}
|
|
853
|
-
} catch (err) {
|
|
854
|
-
typesError = err instanceof Error ? err.message : String(err);
|
|
855
|
-
}
|
|
856
|
-
} else {
|
|
857
|
-
typesError = "No TypeScript types available from esm.sh";
|
|
858
|
-
}
|
|
859
|
-
const knownSubpaths = KNOWN_SUBPATHS[name];
|
|
860
|
-
if (knownSubpaths && knownSubpaths.length > 0) {
|
|
861
|
-
const subpathResults = await Promise.allSettled(knownSubpaths.map((subpath) => fetchSubpathTypes(name, subpath, info.version)));
|
|
862
|
-
for (const result of subpathResults) {
|
|
863
|
-
if (result.status === "fulfilled") {
|
|
864
|
-
for (const [path, content] of result.value) {
|
|
865
|
-
typeFiles.set(path, content);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
if (cache && typeFiles.size > 0) {
|
|
871
|
-
cache.set(name, info.version, typeFiles);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
for (const [path, content] of typeFiles) {
|
|
875
|
-
const dir = path.substring(0, path.lastIndexOf("/"));
|
|
876
|
-
await ensureDir(fs, dir);
|
|
877
|
-
await fs.writeFile(path, content);
|
|
878
|
-
}
|
|
879
|
-
const manifest = await getPackageManifest(fs);
|
|
880
|
-
manifest.dependencies[name] = info.version;
|
|
881
|
-
await savePackageManifest(fs, manifest);
|
|
882
|
-
return {
|
|
883
|
-
name,
|
|
884
|
-
version: info.version,
|
|
885
|
-
typesInstalled: typeFiles.size > 0,
|
|
886
|
-
typeFilesCount: typeFiles.size,
|
|
887
|
-
typesError,
|
|
888
|
-
fromCache
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
async function installTypesPackage(fs, name, version, cache) {
|
|
892
|
-
const url = version ? `${ESM_CDN_BASE}/${name}@${version}` : `${ESM_CDN_BASE}/${name}`;
|
|
893
|
-
const headResponse = await fetch(url, { method: "HEAD" });
|
|
894
|
-
if (!headResponse.ok) {
|
|
895
|
-
throw new Error(`Package not found: ${name}${version ? `@${version}` : ""}`);
|
|
896
|
-
}
|
|
897
|
-
const resolvedVersion = extractVersionFromUrl(headResponse.url, name) ?? version ?? "latest";
|
|
898
|
-
let types = null;
|
|
899
|
-
let fromCache = false;
|
|
900
|
-
if (cache) {
|
|
901
|
-
types = cache.get(name, resolvedVersion);
|
|
902
|
-
if (types) {
|
|
903
|
-
fromCache = true;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
if (!types) {
|
|
907
|
-
const result = await fetchTypesPackageContent(name, version);
|
|
908
|
-
types = result.types;
|
|
909
|
-
if (cache && types.size > 0) {
|
|
910
|
-
cache.set(name, resolvedVersion, types);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const packageDir = `/node_modules/${name}`;
|
|
914
|
-
await ensureDir(fs, packageDir);
|
|
915
|
-
const packageJsonPath = `${packageDir}/package.json`;
|
|
916
|
-
const packageJson = {
|
|
917
|
-
name,
|
|
918
|
-
version: resolvedVersion,
|
|
919
|
-
types: "./index.d.ts"
|
|
920
|
-
};
|
|
921
|
-
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
922
|
-
for (const [path, content] of types) {
|
|
923
|
-
const dir = path.substring(0, path.lastIndexOf("/"));
|
|
924
|
-
await ensureDir(fs, dir);
|
|
925
|
-
await fs.writeFile(path, content);
|
|
926
|
-
}
|
|
927
|
-
const manifest = await getPackageManifest(fs);
|
|
928
|
-
manifest.dependencies[name] = resolvedVersion;
|
|
929
|
-
await savePackageManifest(fs, manifest);
|
|
930
|
-
return {
|
|
931
|
-
name,
|
|
932
|
-
version: resolvedVersion,
|
|
933
|
-
typesInstalled: types.size > 0,
|
|
934
|
-
typeFilesCount: types.size,
|
|
935
|
-
fromCache
|
|
1247
|
+
dependencies
|
|
936
1248
|
};
|
|
1249
|
+
fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(updated, null, 2));
|
|
937
1250
|
}
|
|
938
|
-
|
|
1251
|
+
function ensureDir(fs, path) {
|
|
939
1252
|
if (path === "/" || path === "")
|
|
940
1253
|
return;
|
|
941
|
-
if (
|
|
942
|
-
const stat =
|
|
1254
|
+
if (fs.exists(path)) {
|
|
1255
|
+
const stat = fs.stat(path);
|
|
943
1256
|
if (stat.isDirectory)
|
|
944
1257
|
return;
|
|
945
1258
|
}
|
|
946
1259
|
const parent = path.substring(0, path.lastIndexOf("/")) || "/";
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
async function uninstallPackage(fs, packageName) {
|
|
951
|
-
const manifest = await getPackageManifest(fs);
|
|
952
|
-
if (!(packageName in manifest.dependencies)) {
|
|
953
|
-
return false;
|
|
954
|
-
}
|
|
955
|
-
delete manifest.dependencies[packageName];
|
|
956
|
-
await savePackageManifest(fs, manifest);
|
|
957
|
-
const typesPath = `/node_modules/${packageName}`;
|
|
958
|
-
if (await fs.exists(typesPath)) {
|
|
959
|
-
await removePath(fs, typesPath);
|
|
960
|
-
}
|
|
961
|
-
return true;
|
|
962
|
-
}
|
|
963
|
-
async function removePath(fs, path) {
|
|
964
|
-
if (!await fs.exists(path))
|
|
965
|
-
return;
|
|
966
|
-
await fs.rm(path, { recursive: true, force: true });
|
|
1260
|
+
ensureDir(fs, parent);
|
|
1261
|
+
fs.mkdir(path);
|
|
967
1262
|
}
|
|
968
|
-
function
|
|
969
|
-
const {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
if (
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1263
|
+
async function createSandboxImpl(fs, options, context) {
|
|
1264
|
+
const {
|
|
1265
|
+
bundler,
|
|
1266
|
+
typechecker,
|
|
1267
|
+
typesResolver,
|
|
1268
|
+
sharedModuleRegistry,
|
|
1269
|
+
executor
|
|
1270
|
+
} = context;
|
|
1271
|
+
let lastBuild = null;
|
|
1272
|
+
const onBuildCallbacks = new Set;
|
|
1273
|
+
if (options.onBuild) {
|
|
1274
|
+
onBuildCallbacks.add(options.onBuild);
|
|
1275
|
+
}
|
|
1276
|
+
if (options.initialFiles) {
|
|
1277
|
+
for (const [path, content] of Object.entries(options.initialFiles)) {
|
|
1278
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1279
|
+
const dir = normalizedPath.substring(0, normalizedPath.lastIndexOf("/"));
|
|
1280
|
+
if (dir && dir !== "/") {
|
|
1281
|
+
ensureDir(fs, dir);
|
|
1282
|
+
}
|
|
1283
|
+
fs.writeFile(normalizedPath, content);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (!fs.exists(PACKAGE_JSON_PATH)) {
|
|
1287
|
+
fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(DEFAULT_PACKAGE_JSON, null, 2));
|
|
1288
|
+
}
|
|
1289
|
+
if (!fs.exists(TSCONFIG_PATH)) {
|
|
1290
|
+
fs.writeFile(TSCONFIG_PATH, JSON.stringify(DEFAULT_TSCONFIG, null, 2));
|
|
1291
|
+
}
|
|
1292
|
+
async function install(packageSpec) {
|
|
1293
|
+
const { name, version } = parsePackageSpec(packageSpec);
|
|
1294
|
+
let resolvedVersion = version ?? "latest";
|
|
1295
|
+
let typesInstalled = false;
|
|
1296
|
+
let typeFilesCount = 0;
|
|
1297
|
+
let typesError;
|
|
1298
|
+
const fromCache = false;
|
|
1299
|
+
if (typesResolver) {
|
|
1300
|
+
try {
|
|
1301
|
+
const typeFiles = await typesResolver.resolveTypes(name, version);
|
|
1302
|
+
const packageDir = `/node_modules/${name}`;
|
|
1303
|
+
ensureDir(fs, packageDir);
|
|
1304
|
+
let typesEntry = "index.d.ts";
|
|
1305
|
+
for (const [filePath, content] of Object.entries(typeFiles)) {
|
|
1306
|
+
const fullPath = filePath.startsWith("/") ? filePath : `${packageDir}/${filePath}`;
|
|
1307
|
+
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
1308
|
+
ensureDir(fs, dir);
|
|
1309
|
+
fs.writeFile(fullPath, content);
|
|
1310
|
+
typeFilesCount++;
|
|
1311
|
+
const relativePath = fullPath.replace(`${packageDir}/`, "");
|
|
1312
|
+
if (relativePath === "index.d.ts") {
|
|
1313
|
+
typesEntry = "index.d.ts";
|
|
1314
|
+
} else if (typesEntry === "index.d.ts" && relativePath.endsWith(".d.ts") && !relativePath.includes("/")) {
|
|
1315
|
+
typesEntry = relativePath;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
typesInstalled = typeFilesCount > 0;
|
|
1319
|
+
if (typesInstalled) {
|
|
1320
|
+
const pkgJsonPath = `${packageDir}/package.json`;
|
|
1321
|
+
const pkgJson = {
|
|
1322
|
+
name,
|
|
1323
|
+
version: resolvedVersion,
|
|
1324
|
+
types: typesEntry,
|
|
1325
|
+
main: typesEntry.replace(/\.d\.ts$/, ".js")
|
|
1326
|
+
};
|
|
1327
|
+
fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
1328
|
+
}
|
|
1329
|
+
if (!version) {
|
|
1330
|
+
resolvedVersion = "latest";
|
|
1331
|
+
}
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
typesError = err instanceof Error ? err.message : String(err);
|
|
1334
|
+
}
|
|
984
1335
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
async function listPackages(fs) {
|
|
997
|
-
const manifest = await getPackageManifest(fs);
|
|
998
|
-
return Object.entries(manifest.dependencies).map(([name, version]) => ({
|
|
999
|
-
name,
|
|
1000
|
-
version
|
|
1001
|
-
}));
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// src/shared-modules.ts
|
|
1005
|
-
var GLOBAL_KEY = "__sandlot_shared_modules__";
|
|
1006
|
-
|
|
1007
|
-
class SharedModuleRegistry {
|
|
1008
|
-
modules = new Map;
|
|
1009
|
-
exportNames = new Map;
|
|
1010
|
-
constructor() {
|
|
1011
|
-
globalThis[GLOBAL_KEY] = this;
|
|
1012
|
-
}
|
|
1013
|
-
register(moduleId, module) {
|
|
1014
|
-
this.modules.set(moduleId, module);
|
|
1015
|
-
this.exportNames.set(moduleId, introspectExports(module));
|
|
1016
|
-
return this;
|
|
1336
|
+
const dependencies = getInstalledPackages(fs);
|
|
1337
|
+
dependencies[name] = resolvedVersion;
|
|
1338
|
+
saveInstalledPackages(fs, dependencies);
|
|
1339
|
+
return {
|
|
1340
|
+
name,
|
|
1341
|
+
version: resolvedVersion,
|
|
1342
|
+
typesInstalled,
|
|
1343
|
+
typeFilesCount,
|
|
1344
|
+
typesError,
|
|
1345
|
+
fromCache
|
|
1346
|
+
};
|
|
1017
1347
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1348
|
+
async function uninstall(packageName) {
|
|
1349
|
+
const dependencies = getInstalledPackages(fs);
|
|
1350
|
+
if (!(packageName in dependencies)) {
|
|
1351
|
+
return { name: packageName, removed: false };
|
|
1352
|
+
}
|
|
1353
|
+
delete dependencies[packageName];
|
|
1354
|
+
saveInstalledPackages(fs, dependencies);
|
|
1355
|
+
const typesPath = `/node_modules/${packageName}`;
|
|
1356
|
+
if (fs.exists(typesPath)) {
|
|
1357
|
+
fs.rm(typesPath, { recursive: true, force: true });
|
|
1358
|
+
}
|
|
1359
|
+
return { name: packageName, removed: true };
|
|
1360
|
+
}
|
|
1361
|
+
async function build(buildOptions) {
|
|
1362
|
+
const buildEntryPoint = buildOptions?.entryPoint ?? getEntryPoint(fs);
|
|
1363
|
+
const skipTypecheck = buildOptions?.skipTypecheck ?? false;
|
|
1364
|
+
const minify = buildOptions?.minify ?? false;
|
|
1365
|
+
const format = buildOptions?.format ?? "esm";
|
|
1366
|
+
if (!fs.exists(buildEntryPoint)) {
|
|
1367
|
+
return {
|
|
1368
|
+
success: false,
|
|
1369
|
+
phase: "entry",
|
|
1370
|
+
message: `Entry point not found: ${buildEntryPoint}`
|
|
1371
|
+
};
|
|
1021
1372
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1373
|
+
if (!skipTypecheck && typechecker) {
|
|
1374
|
+
const typecheckResult = await typechecker.typecheck({
|
|
1375
|
+
fs,
|
|
1376
|
+
entryPoint: buildEntryPoint,
|
|
1377
|
+
tsconfigPath: TSCONFIG_PATH
|
|
1378
|
+
});
|
|
1379
|
+
if (!typecheckResult.success) {
|
|
1380
|
+
return {
|
|
1381
|
+
success: false,
|
|
1382
|
+
phase: "typecheck",
|
|
1383
|
+
diagnostics: typecheckResult.diagnostics
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1032
1386
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
function introspectExports(module) {
|
|
1053
|
-
if (module === null || module === undefined) {
|
|
1054
|
-
return [];
|
|
1055
|
-
}
|
|
1056
|
-
if (typeof module !== "object" && typeof module !== "function") {
|
|
1057
|
-
return [];
|
|
1058
|
-
}
|
|
1059
|
-
const exports = [];
|
|
1060
|
-
for (const key of Object.keys(module)) {
|
|
1061
|
-
if (isValidIdentifier(key)) {
|
|
1062
|
-
exports.push(key);
|
|
1387
|
+
const installedPackages = getInstalledPackages(fs);
|
|
1388
|
+
const bundleResult = await bundler.bundle({
|
|
1389
|
+
fs,
|
|
1390
|
+
entryPoint: buildEntryPoint,
|
|
1391
|
+
installedPackages,
|
|
1392
|
+
sharedModules: sharedModuleRegistry?.list() ?? [],
|
|
1393
|
+
sharedModuleRegistry: sharedModuleRegistry ?? undefined,
|
|
1394
|
+
format,
|
|
1395
|
+
minify
|
|
1396
|
+
});
|
|
1397
|
+
if (!bundleResult.success) {
|
|
1398
|
+
return {
|
|
1399
|
+
success: false,
|
|
1400
|
+
phase: "bundle",
|
|
1401
|
+
bundleErrors: bundleResult.errors,
|
|
1402
|
+
bundleWarnings: bundleResult.warnings
|
|
1403
|
+
};
|
|
1063
1404
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
return true;
|
|
1078
|
-
}
|
|
1079
|
-
var defaultRegistry = null;
|
|
1080
|
-
function getSharedModuleRegistry() {
|
|
1081
|
-
if (!defaultRegistry) {
|
|
1082
|
-
defaultRegistry = new SharedModuleRegistry;
|
|
1083
|
-
}
|
|
1084
|
-
return defaultRegistry;
|
|
1085
|
-
}
|
|
1086
|
-
function hasSharedModuleRegistry() {
|
|
1087
|
-
return GLOBAL_KEY in globalThis;
|
|
1088
|
-
}
|
|
1089
|
-
function registerSharedModules(modules) {
|
|
1090
|
-
getSharedModuleRegistry().registerAll(modules);
|
|
1091
|
-
}
|
|
1092
|
-
function unregisterSharedModule(moduleId) {
|
|
1093
|
-
return getSharedModuleRegistry().unregister(moduleId);
|
|
1094
|
-
}
|
|
1095
|
-
function clearSharedModules() {
|
|
1096
|
-
getSharedModuleRegistry().clear();
|
|
1097
|
-
}
|
|
1098
|
-
function getSharedModuleExports(moduleId) {
|
|
1099
|
-
return getSharedModuleRegistry().getExportNames(moduleId);
|
|
1100
|
-
}
|
|
1101
|
-
function getSharedModuleRuntimeCode(moduleId) {
|
|
1102
|
-
return `
|
|
1103
|
-
(function() {
|
|
1104
|
-
var registry = globalThis["${GLOBAL_KEY}"];
|
|
1105
|
-
if (!registry) {
|
|
1106
|
-
throw new Error(
|
|
1107
|
-
'Sandlot SharedModuleRegistry not found. ' +
|
|
1108
|
-
'Call registerSharedModules() in your host application before loading dynamic modules.'
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
return registry.get(${JSON.stringify(moduleId)});
|
|
1112
|
-
})()
|
|
1113
|
-
`.trim();
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// src/bundler.ts
|
|
1117
|
-
var esbuild = null;
|
|
1118
|
-
function isServerEnvironment() {
|
|
1119
|
-
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
1120
|
-
return true;
|
|
1121
|
-
}
|
|
1122
|
-
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
1123
|
-
return true;
|
|
1124
|
-
}
|
|
1125
|
-
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1126
|
-
return true;
|
|
1127
|
-
}
|
|
1128
|
-
return false;
|
|
1129
|
-
}
|
|
1130
|
-
async function getEsbuild() {
|
|
1131
|
-
if (esbuild)
|
|
1132
|
-
return esbuild;
|
|
1133
|
-
if (isServerEnvironment()) {
|
|
1134
|
-
const mod = await import("esbuild");
|
|
1135
|
-
esbuild = mod.default ?? mod;
|
|
1136
|
-
} else {
|
|
1137
|
-
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
1138
|
-
const mod = await import(cdnUrl);
|
|
1139
|
-
esbuild = mod.default ?? mod;
|
|
1140
|
-
if (typeof esbuild?.initialize !== "function") {
|
|
1141
|
-
console.error("esbuild-wasm module structure:", mod);
|
|
1142
|
-
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return esbuild;
|
|
1146
|
-
}
|
|
1147
|
-
var ESBUILD_VERSION = "0.27.2";
|
|
1148
|
-
var initialized = false;
|
|
1149
|
-
var initPromise = null;
|
|
1150
|
-
function getWasmUrl() {
|
|
1151
|
-
return `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
|
|
1152
|
-
}
|
|
1153
|
-
function checkCrossOriginIsolation() {
|
|
1154
|
-
if (typeof window === "undefined")
|
|
1155
|
-
return;
|
|
1156
|
-
if (!window.crossOriginIsolated) {
|
|
1157
|
-
console.warn(`[sandlot] Cross-origin isolation is not enabled. esbuild-wasm may have reduced performance or fail on some browsers.
|
|
1158
|
-
To enable, add these headers to your dev server:
|
|
1159
|
-
Cross-Origin-Embedder-Policy: require-corp
|
|
1160
|
-
Cross-Origin-Opener-Policy: same-origin
|
|
1161
|
-
In Vite, add a plugin to configureServer. See sandlot README for details.`);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
async function initBundler() {
|
|
1165
|
-
if (initialized)
|
|
1166
|
-
return;
|
|
1167
|
-
if (initPromise) {
|
|
1168
|
-
await initPromise;
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
initPromise = (async () => {
|
|
1172
|
-
const es = await getEsbuild();
|
|
1173
|
-
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
1174
|
-
checkCrossOriginIsolation();
|
|
1175
|
-
await es.initialize({
|
|
1176
|
-
wasmURL: getWasmUrl()
|
|
1177
|
-
});
|
|
1405
|
+
const output = {
|
|
1406
|
+
success: true,
|
|
1407
|
+
code: bundleResult.code,
|
|
1408
|
+
includedFiles: bundleResult.includedFiles,
|
|
1409
|
+
warnings: bundleResult.warnings
|
|
1410
|
+
};
|
|
1411
|
+
lastBuild = output;
|
|
1412
|
+
for (const callback of onBuildCallbacks) {
|
|
1413
|
+
try {
|
|
1414
|
+
await callback(output);
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
console.error("[sandlot] onBuild callback error:", err);
|
|
1417
|
+
}
|
|
1178
1418
|
}
|
|
1179
|
-
|
|
1180
|
-
await initPromise;
|
|
1181
|
-
initialized = true;
|
|
1182
|
-
}
|
|
1183
|
-
function isBareImport(path) {
|
|
1184
|
-
return !path.startsWith(".") && !path.startsWith("/");
|
|
1185
|
-
}
|
|
1186
|
-
function getLoader(path) {
|
|
1187
|
-
const ext = path.split(".").pop()?.toLowerCase();
|
|
1188
|
-
switch (ext) {
|
|
1189
|
-
case "ts":
|
|
1190
|
-
return "ts";
|
|
1191
|
-
case "tsx":
|
|
1192
|
-
return "tsx";
|
|
1193
|
-
case "jsx":
|
|
1194
|
-
return "jsx";
|
|
1195
|
-
case "js":
|
|
1196
|
-
case "mjs":
|
|
1197
|
-
return "js";
|
|
1198
|
-
case "json":
|
|
1199
|
-
return "json";
|
|
1200
|
-
case "css":
|
|
1201
|
-
return "css";
|
|
1202
|
-
case "txt":
|
|
1203
|
-
return "text";
|
|
1204
|
-
default:
|
|
1205
|
-
return "js";
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
function matchesSharedModule(importPath, sharedModuleIds) {
|
|
1209
|
-
if (sharedModuleIds.has(importPath)) {
|
|
1210
|
-
return importPath;
|
|
1419
|
+
return output;
|
|
1211
1420
|
}
|
|
1212
|
-
|
|
1213
|
-
if (
|
|
1214
|
-
return
|
|
1421
|
+
async function typecheck(typecheckOptions) {
|
|
1422
|
+
if (!typechecker) {
|
|
1423
|
+
return { success: true, diagnostics: [] };
|
|
1215
1424
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
installedPackages,
|
|
1225
|
-
includedFiles,
|
|
1226
|
-
sharedModuleIds
|
|
1227
|
-
} = options;
|
|
1228
|
-
return {
|
|
1229
|
-
name: "virtual-fs",
|
|
1230
|
-
setup(build) {
|
|
1231
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
1232
|
-
if (args.kind === "entry-point") {
|
|
1233
|
-
return { path: entryPoint, namespace: "vfs" };
|
|
1234
|
-
}
|
|
1235
|
-
if (isBareImport(args.path)) {
|
|
1236
|
-
const sharedMatch = matchesSharedModule(args.path, sharedModuleIds);
|
|
1237
|
-
if (sharedMatch) {
|
|
1238
|
-
return {
|
|
1239
|
-
path: sharedMatch,
|
|
1240
|
-
namespace: "sandlot-shared"
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
switch (npmImports) {
|
|
1244
|
-
case "cdn": {
|
|
1245
|
-
const esmUrl = resolveToEsmUrl(args.path, installedPackages);
|
|
1246
|
-
if (esmUrl) {
|
|
1247
|
-
return { path: esmUrl, external: true };
|
|
1248
|
-
}
|
|
1249
|
-
return { path: args.path, external: true };
|
|
1250
|
-
}
|
|
1251
|
-
case "external":
|
|
1252
|
-
return { path: args.path, external: true };
|
|
1253
|
-
case "bundle": {
|
|
1254
|
-
const resolved2 = fs.resolvePath(args.resolveDir, `node_modules/${args.path}`);
|
|
1255
|
-
const exists = await fs.exists(resolved2);
|
|
1256
|
-
if (exists) {
|
|
1257
|
-
return { path: resolved2, namespace: "vfs" };
|
|
1258
|
-
}
|
|
1259
|
-
return { path: args.path, external: true };
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
const resolved = fs.resolvePath(args.resolveDir, args.path);
|
|
1264
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".json"];
|
|
1265
|
-
const hasExtension = extensions.some((ext) => resolved.endsWith(ext));
|
|
1266
|
-
if (hasExtension) {
|
|
1267
|
-
const exists = await fs.exists(resolved);
|
|
1268
|
-
if (exists) {
|
|
1269
|
-
return { path: resolved, namespace: "vfs" };
|
|
1270
|
-
}
|
|
1271
|
-
return { errors: [{ text: `File not found: ${resolved}` }] };
|
|
1272
|
-
}
|
|
1273
|
-
for (const ext of extensions) {
|
|
1274
|
-
const withExt = resolved + ext;
|
|
1275
|
-
if (await fs.exists(withExt)) {
|
|
1276
|
-
return { path: withExt, namespace: "vfs" };
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
for (const ext of extensions) {
|
|
1280
|
-
const indexPath = `${resolved}/index${ext}`;
|
|
1281
|
-
if (await fs.exists(indexPath)) {
|
|
1282
|
-
return { path: indexPath, namespace: "vfs" };
|
|
1425
|
+
const checkEntryPoint = typecheckOptions?.entryPoint ?? getEntryPoint(fs);
|
|
1426
|
+
if (!fs.exists(checkEntryPoint)) {
|
|
1427
|
+
return {
|
|
1428
|
+
success: false,
|
|
1429
|
+
diagnostics: [
|
|
1430
|
+
{
|
|
1431
|
+
message: `Entry point not found: ${checkEntryPoint}`,
|
|
1432
|
+
severity: "error"
|
|
1283
1433
|
}
|
|
1434
|
+
]
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
return typechecker.typecheck({
|
|
1438
|
+
fs,
|
|
1439
|
+
entryPoint: checkEntryPoint,
|
|
1440
|
+
tsconfigPath: TSCONFIG_PATH
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
async function run(runOptions) {
|
|
1444
|
+
if (!executor) {
|
|
1445
|
+
throw new Error("[sandlot] No executor configured. Provide an executor when creating Sandlot to use run().");
|
|
1446
|
+
}
|
|
1447
|
+
const buildResult = await build({
|
|
1448
|
+
entryPoint: runOptions?.entryPoint,
|
|
1449
|
+
skipTypecheck: runOptions?.skipTypecheck
|
|
1450
|
+
});
|
|
1451
|
+
if (!buildResult.success) {
|
|
1452
|
+
return {
|
|
1453
|
+
success: false,
|
|
1454
|
+
logs: [],
|
|
1455
|
+
error: buildResult.message ?? `Build failed in ${buildResult.phase} phase`,
|
|
1456
|
+
buildFailure: {
|
|
1457
|
+
phase: buildResult.phase,
|
|
1458
|
+
message: buildResult.message
|
|
1284
1459
|
}
|
|
1285
|
-
|
|
1286
|
-
});
|
|
1287
|
-
build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
|
|
1288
|
-
const contents = `
|
|
1289
|
-
const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
1290
|
-
export default __sandlot_mod__.default ?? __sandlot_mod__;
|
|
1291
|
-
${generateNamedExports(args.path)}
|
|
1292
|
-
`;
|
|
1293
|
-
return {
|
|
1294
|
-
contents: contents.trim(),
|
|
1295
|
-
loader: "js"
|
|
1296
|
-
};
|
|
1297
|
-
});
|
|
1298
|
-
build.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
|
|
1299
|
-
try {
|
|
1300
|
-
const contents = await fs.readFile(args.path);
|
|
1301
|
-
includedFiles.add(args.path);
|
|
1302
|
-
return {
|
|
1303
|
-
contents,
|
|
1304
|
-
loader: getLoader(args.path),
|
|
1305
|
-
resolveDir: args.path.substring(0, args.path.lastIndexOf("/"))
|
|
1306
|
-
};
|
|
1307
|
-
} catch (err) {
|
|
1308
|
-
return {
|
|
1309
|
-
errors: [{ text: `Failed to read ${args.path}: ${err}` }]
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
});
|
|
1460
|
+
};
|
|
1313
1461
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
return
|
|
1320
|
-
|
|
1462
|
+
const executeResult = await executor.execute(buildResult.code, {
|
|
1463
|
+
entryExport: runOptions?.entryExport ?? "main",
|
|
1464
|
+
context: runOptions?.context,
|
|
1465
|
+
timeout: runOptions?.timeout
|
|
1466
|
+
});
|
|
1467
|
+
return {
|
|
1468
|
+
success: executeResult.success,
|
|
1469
|
+
logs: executeResult.logs,
|
|
1470
|
+
returnValue: executeResult.returnValue,
|
|
1471
|
+
error: executeResult.error,
|
|
1472
|
+
executionTimeMs: executeResult.executionTimeMs
|
|
1473
|
+
};
|
|
1321
1474
|
}
|
|
1322
|
-
|
|
1323
|
-
}
|
|
1324
|
-
async function bundle(options) {
|
|
1325
|
-
await initBundler();
|
|
1326
|
-
const {
|
|
1475
|
+
const sandboxRef = {
|
|
1327
1476
|
fs,
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
minify = false,
|
|
1334
|
-
sourcemap = false,
|
|
1335
|
-
globalName,
|
|
1336
|
-
target = ["es2020"]
|
|
1337
|
-
} = options;
|
|
1338
|
-
const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
|
|
1339
|
-
if (!await fs.exists(normalizedEntry)) {
|
|
1340
|
-
throw new Error(`Entry point not found: ${normalizedEntry}`);
|
|
1341
|
-
}
|
|
1342
|
-
const manifest = await getPackageManifest(fs);
|
|
1343
|
-
const installedPackages = manifest.dependencies;
|
|
1344
|
-
const sharedModuleIds = new Set(sharedModules);
|
|
1345
|
-
const includedFiles = new Set;
|
|
1346
|
-
const plugin = createVfsPlugin({
|
|
1347
|
-
fs,
|
|
1348
|
-
entryPoint: normalizedEntry,
|
|
1349
|
-
npmImports,
|
|
1350
|
-
installedPackages,
|
|
1351
|
-
includedFiles,
|
|
1352
|
-
sharedModuleIds
|
|
1353
|
-
});
|
|
1354
|
-
const es = await getEsbuild();
|
|
1355
|
-
const result = await es.build({
|
|
1356
|
-
entryPoints: [normalizedEntry],
|
|
1357
|
-
bundle: true,
|
|
1358
|
-
write: false,
|
|
1359
|
-
format,
|
|
1360
|
-
minify,
|
|
1361
|
-
sourcemap: sourcemap ? "inline" : false,
|
|
1362
|
-
globalName,
|
|
1363
|
-
target,
|
|
1364
|
-
external,
|
|
1365
|
-
plugins: [plugin],
|
|
1366
|
-
jsx: "automatic"
|
|
1367
|
-
});
|
|
1368
|
-
const code = result.outputFiles?.[0]?.text ?? "";
|
|
1369
|
-
return {
|
|
1370
|
-
code,
|
|
1371
|
-
warnings: result.warnings,
|
|
1372
|
-
includedFiles: Array.from(includedFiles)
|
|
1477
|
+
install,
|
|
1478
|
+
uninstall,
|
|
1479
|
+
build,
|
|
1480
|
+
typecheck,
|
|
1481
|
+
run
|
|
1373
1482
|
};
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
return await import(url);
|
|
1384
|
-
} finally {
|
|
1385
|
-
URL.revokeObjectURL(url);
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
// src/commands/types.ts
|
|
1390
|
-
function formatEsbuildMessages(messages) {
|
|
1391
|
-
if (messages.length === 0)
|
|
1392
|
-
return "";
|
|
1393
|
-
return messages.map((msg) => {
|
|
1394
|
-
if (msg.location) {
|
|
1395
|
-
const { file, line, column } = msg.location;
|
|
1396
|
-
const loc = file ? `${file}${line ? `:${line}` : ""}${column ? `:${column}` : ""}` : "";
|
|
1397
|
-
return loc ? `${loc}: ${msg.text}` : msg.text;
|
|
1398
|
-
}
|
|
1399
|
-
return msg.text;
|
|
1400
|
-
}).join(`
|
|
1401
|
-
`);
|
|
1402
|
-
}
|
|
1403
|
-
// src/commands/compile.ts
|
|
1404
|
-
import { defineCommand } from "just-bash/browser";
|
|
1405
|
-
|
|
1406
|
-
// src/typechecker.ts
|
|
1407
|
-
import ts from "typescript";
|
|
1408
|
-
function categoryToString(category) {
|
|
1409
|
-
switch (category) {
|
|
1410
|
-
case ts.DiagnosticCategory.Error:
|
|
1411
|
-
return "error";
|
|
1412
|
-
case ts.DiagnosticCategory.Warning:
|
|
1413
|
-
return "warning";
|
|
1414
|
-
case ts.DiagnosticCategory.Suggestion:
|
|
1415
|
-
return "suggestion";
|
|
1416
|
-
case ts.DiagnosticCategory.Message:
|
|
1417
|
-
return "message";
|
|
1418
|
-
default:
|
|
1419
|
-
return "error";
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
function convertDiagnostic(diag) {
|
|
1423
|
-
let file = null;
|
|
1424
|
-
let line = null;
|
|
1425
|
-
let column = null;
|
|
1426
|
-
if (diag.file && diag.start !== undefined) {
|
|
1427
|
-
file = diag.file.fileName;
|
|
1428
|
-
const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
|
|
1429
|
-
line = pos.line + 1;
|
|
1430
|
-
column = pos.character + 1;
|
|
1431
|
-
}
|
|
1432
|
-
return {
|
|
1433
|
-
file,
|
|
1434
|
-
line,
|
|
1435
|
-
column,
|
|
1436
|
-
code: diag.code,
|
|
1437
|
-
category: categoryToString(diag.category),
|
|
1438
|
-
message: ts.flattenDiagnosticMessageText(diag.messageText, `
|
|
1439
|
-
`)
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
function normalizePath(path) {
|
|
1443
|
-
if (!path.startsWith("/")) {
|
|
1444
|
-
return "/" + path;
|
|
1445
|
-
}
|
|
1446
|
-
return path;
|
|
1447
|
-
}
|
|
1448
|
-
async function preloadFiles(fs) {
|
|
1449
|
-
const cache = new Map;
|
|
1450
|
-
const allPaths = fs.getAllPaths();
|
|
1451
|
-
for (const path of allPaths) {
|
|
1452
|
-
if (path.endsWith(".ts") || path.endsWith(".tsx") || path.endsWith(".js") || path.endsWith(".jsx") || path.endsWith(".json") || path.endsWith(".d.ts")) {
|
|
1453
|
-
try {
|
|
1454
|
-
const stat = await fs.stat(path);
|
|
1455
|
-
if (stat.isFile) {
|
|
1456
|
-
const content = await fs.readFile(path);
|
|
1457
|
-
cache.set(path, content);
|
|
1458
|
-
}
|
|
1459
|
-
} catch {}
|
|
1483
|
+
let bashInstance = null;
|
|
1484
|
+
function getBash() {
|
|
1485
|
+
if (!bashInstance) {
|
|
1486
|
+
const commands = createDefaultCommands(sandboxRef);
|
|
1487
|
+
bashInstance = new Bash({
|
|
1488
|
+
cwd: "/",
|
|
1489
|
+
fs: wrapFilesystemForJustBash(fs),
|
|
1490
|
+
customCommands: commands
|
|
1491
|
+
});
|
|
1460
1492
|
}
|
|
1493
|
+
return bashInstance;
|
|
1461
1494
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
...getDefaultCompilerOptions()
|
|
1495
|
+
async function exec(command) {
|
|
1496
|
+
const bash = getBash();
|
|
1497
|
+
const result = await bash.exec(command);
|
|
1498
|
+
return {
|
|
1499
|
+
exitCode: result.exitCode,
|
|
1500
|
+
stdout: result.stdout,
|
|
1501
|
+
stderr: result.stderr
|
|
1470
1502
|
};
|
|
1471
|
-
if (compilerOptions.target) {
|
|
1472
|
-
const targetMap = {
|
|
1473
|
-
es5: ts.ScriptTarget.ES5,
|
|
1474
|
-
es6: ts.ScriptTarget.ES2015,
|
|
1475
|
-
es2015: ts.ScriptTarget.ES2015,
|
|
1476
|
-
es2016: ts.ScriptTarget.ES2016,
|
|
1477
|
-
es2017: ts.ScriptTarget.ES2017,
|
|
1478
|
-
es2018: ts.ScriptTarget.ES2018,
|
|
1479
|
-
es2019: ts.ScriptTarget.ES2019,
|
|
1480
|
-
es2020: ts.ScriptTarget.ES2020,
|
|
1481
|
-
es2021: ts.ScriptTarget.ES2021,
|
|
1482
|
-
es2022: ts.ScriptTarget.ES2022,
|
|
1483
|
-
esnext: ts.ScriptTarget.ESNext
|
|
1484
|
-
};
|
|
1485
|
-
options.target = targetMap[compilerOptions.target.toLowerCase()] ?? ts.ScriptTarget.ES2020;
|
|
1486
|
-
}
|
|
1487
|
-
if (compilerOptions.module) {
|
|
1488
|
-
const moduleMap = {
|
|
1489
|
-
commonjs: ts.ModuleKind.CommonJS,
|
|
1490
|
-
amd: ts.ModuleKind.AMD,
|
|
1491
|
-
umd: ts.ModuleKind.UMD,
|
|
1492
|
-
system: ts.ModuleKind.System,
|
|
1493
|
-
es6: ts.ModuleKind.ES2015,
|
|
1494
|
-
es2015: ts.ModuleKind.ES2015,
|
|
1495
|
-
es2020: ts.ModuleKind.ES2020,
|
|
1496
|
-
es2022: ts.ModuleKind.ES2022,
|
|
1497
|
-
esnext: ts.ModuleKind.ESNext,
|
|
1498
|
-
node16: ts.ModuleKind.Node16,
|
|
1499
|
-
nodenext: ts.ModuleKind.NodeNext
|
|
1500
|
-
};
|
|
1501
|
-
options.module = moduleMap[compilerOptions.module.toLowerCase()] ?? ts.ModuleKind.ESNext;
|
|
1502
|
-
}
|
|
1503
|
-
if (compilerOptions.moduleResolution) {
|
|
1504
|
-
const resolutionMap = {
|
|
1505
|
-
classic: ts.ModuleResolutionKind.Classic,
|
|
1506
|
-
node: ts.ModuleResolutionKind.NodeJs,
|
|
1507
|
-
node10: ts.ModuleResolutionKind.NodeJs,
|
|
1508
|
-
node16: ts.ModuleResolutionKind.Node16,
|
|
1509
|
-
nodenext: ts.ModuleResolutionKind.NodeNext,
|
|
1510
|
-
bundler: ts.ModuleResolutionKind.Bundler
|
|
1511
|
-
};
|
|
1512
|
-
options.moduleResolution = resolutionMap[compilerOptions.moduleResolution.toLowerCase()] ?? ts.ModuleResolutionKind.Bundler;
|
|
1513
|
-
}
|
|
1514
|
-
if (compilerOptions.jsx) {
|
|
1515
|
-
const jsxMap = {
|
|
1516
|
-
preserve: ts.JsxEmit.Preserve,
|
|
1517
|
-
react: ts.JsxEmit.React,
|
|
1518
|
-
"react-jsx": ts.JsxEmit.ReactJSX,
|
|
1519
|
-
"react-jsxdev": ts.JsxEmit.ReactJSXDev,
|
|
1520
|
-
"react-native": ts.JsxEmit.ReactNative
|
|
1521
|
-
};
|
|
1522
|
-
options.jsx = jsxMap[compilerOptions.jsx.toLowerCase()] ?? ts.JsxEmit.ReactJSX;
|
|
1523
|
-
}
|
|
1524
|
-
if (compilerOptions.strict !== undefined)
|
|
1525
|
-
options.strict = compilerOptions.strict;
|
|
1526
|
-
if (compilerOptions.esModuleInterop !== undefined)
|
|
1527
|
-
options.esModuleInterop = compilerOptions.esModuleInterop;
|
|
1528
|
-
if (compilerOptions.skipLibCheck !== undefined)
|
|
1529
|
-
options.skipLibCheck = compilerOptions.skipLibCheck;
|
|
1530
|
-
if (compilerOptions.allowJs !== undefined)
|
|
1531
|
-
options.allowJs = compilerOptions.allowJs;
|
|
1532
|
-
if (compilerOptions.resolveJsonModule !== undefined)
|
|
1533
|
-
options.resolveJsonModule = compilerOptions.resolveJsonModule;
|
|
1534
|
-
if (compilerOptions.noImplicitAny !== undefined)
|
|
1535
|
-
options.noImplicitAny = compilerOptions.noImplicitAny;
|
|
1536
|
-
if (compilerOptions.strictNullChecks !== undefined)
|
|
1537
|
-
options.strictNullChecks = compilerOptions.strictNullChecks;
|
|
1538
|
-
if (Array.isArray(compilerOptions.lib)) {
|
|
1539
|
-
options.lib = compilerOptions.lib.map((lib) => lib.toLowerCase().startsWith("lib.") ? lib : `lib.${lib.toLowerCase()}.d.ts`);
|
|
1540
|
-
}
|
|
1541
|
-
options.noEmit = true;
|
|
1542
|
-
return options;
|
|
1543
|
-
} catch (err) {
|
|
1544
|
-
console.warn("Error parsing tsconfig.json:", err);
|
|
1545
|
-
return getDefaultCompilerOptions();
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
function getDefaultCompilerOptions() {
|
|
1549
|
-
return {
|
|
1550
|
-
target: ts.ScriptTarget.ES2020,
|
|
1551
|
-
module: ts.ModuleKind.ESNext,
|
|
1552
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
1553
|
-
esModuleInterop: true,
|
|
1554
|
-
strict: true,
|
|
1555
|
-
skipLibCheck: true,
|
|
1556
|
-
noEmit: true,
|
|
1557
|
-
jsx: ts.JsxEmit.ReactJSX,
|
|
1558
|
-
allowJs: true,
|
|
1559
|
-
resolveJsonModule: true,
|
|
1560
|
-
noLib: false,
|
|
1561
|
-
lib: [
|
|
1562
|
-
"lib.es2020.d.ts",
|
|
1563
|
-
"lib.dom.d.ts",
|
|
1564
|
-
"lib.dom.iterable.d.ts"
|
|
1565
|
-
]
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
|
|
1569
|
-
function createVfsCompilerHost(fileCache, libFiles, _options) {
|
|
1570
|
-
function getLibContent(fileName) {
|
|
1571
|
-
const libMatch = fileName.match(/lib\.([^/]+)\.d\.ts$/);
|
|
1572
|
-
if (libMatch && libMatch[1]) {
|
|
1573
|
-
return libFiles.get(libMatch[1]);
|
|
1574
|
-
}
|
|
1575
|
-
return;
|
|
1576
1503
|
}
|
|
1577
1504
|
return {
|
|
1578
|
-
getSourceFile(fileName, languageVersion, onError) {
|
|
1579
|
-
const normalizedPath = normalizePath(fileName);
|
|
1580
|
-
const content = fileCache.get(normalizedPath);
|
|
1581
|
-
if (content !== undefined) {
|
|
1582
|
-
return ts.createSourceFile(normalizedPath, content, languageVersion, true);
|
|
1583
|
-
}
|
|
1584
|
-
const altContent = fileCache.get(fileName);
|
|
1585
|
-
if (altContent !== undefined) {
|
|
1586
|
-
return ts.createSourceFile(fileName, altContent, languageVersion, true);
|
|
1587
|
-
}
|
|
1588
|
-
const libContent = getLibContent(fileName);
|
|
1589
|
-
if (libContent !== undefined) {
|
|
1590
|
-
return ts.createSourceFile(fileName, libContent, languageVersion, true);
|
|
1591
|
-
}
|
|
1592
|
-
if (onError) {
|
|
1593
|
-
onError(`File not found: ${fileName}`);
|
|
1594
|
-
}
|
|
1595
|
-
return;
|
|
1596
|
-
},
|
|
1597
|
-
getDefaultLibFileName(options) {
|
|
1598
|
-
return LIB_PATH_PREFIX + ts.getDefaultLibFileName(options);
|
|
1599
|
-
},
|
|
1600
|
-
writeFile() {},
|
|
1601
|
-
getCurrentDirectory() {
|
|
1602
|
-
return "/";
|
|
1603
|
-
},
|
|
1604
|
-
getCanonicalFileName(fileName) {
|
|
1605
|
-
return fileName;
|
|
1606
|
-
},
|
|
1607
|
-
useCaseSensitiveFileNames() {
|
|
1608
|
-
return true;
|
|
1609
|
-
},
|
|
1610
|
-
getNewLine() {
|
|
1611
|
-
return `
|
|
1612
|
-
`;
|
|
1613
|
-
},
|
|
1614
|
-
fileExists(fileName) {
|
|
1615
|
-
const normalizedPath = normalizePath(fileName);
|
|
1616
|
-
if (fileCache.has(normalizedPath) || fileCache.has(fileName)) {
|
|
1617
|
-
return true;
|
|
1618
|
-
}
|
|
1619
|
-
return getLibContent(fileName) !== undefined;
|
|
1620
|
-
},
|
|
1621
|
-
readFile(fileName) {
|
|
1622
|
-
const normalizedPath = normalizePath(fileName);
|
|
1623
|
-
const content = fileCache.get(normalizedPath) ?? fileCache.get(fileName);
|
|
1624
|
-
if (content !== undefined) {
|
|
1625
|
-
return content;
|
|
1626
|
-
}
|
|
1627
|
-
return getLibContent(fileName);
|
|
1628
|
-
},
|
|
1629
|
-
directoryExists(directoryName) {
|
|
1630
|
-
const normalizedDir = normalizePath(directoryName);
|
|
1631
|
-
for (const path of fileCache.keys()) {
|
|
1632
|
-
if (path.startsWith(normalizedDir + "/")) {
|
|
1633
|
-
return true;
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript") {
|
|
1637
|
-
return libFiles.size > 0;
|
|
1638
|
-
}
|
|
1639
|
-
if (normalizedDir === "/node_modules") {
|
|
1640
|
-
return libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
|
|
1641
|
-
}
|
|
1642
|
-
if (normalizedDir === "/node_modules/@types") {
|
|
1643
|
-
return Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/@types/"));
|
|
1644
|
-
}
|
|
1645
|
-
return false;
|
|
1646
|
-
},
|
|
1647
|
-
getDirectories(path) {
|
|
1648
|
-
const normalizedPath = normalizePath(path);
|
|
1649
|
-
const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
|
|
1650
|
-
const dirs = new Set;
|
|
1651
|
-
for (const filePath of fileCache.keys()) {
|
|
1652
|
-
if (filePath.startsWith(prefix)) {
|
|
1653
|
-
const relative = filePath.slice(prefix.length);
|
|
1654
|
-
const firstSlash = relative.indexOf("/");
|
|
1655
|
-
if (firstSlash > 0) {
|
|
1656
|
-
dirs.add(relative.slice(0, firstSlash));
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
if (normalizedPath === "/") {
|
|
1661
|
-
const hasNodeModules = libFiles.size > 0 || Array.from(fileCache.keys()).some((p) => p.startsWith("/node_modules/"));
|
|
1662
|
-
if (hasNodeModules) {
|
|
1663
|
-
dirs.add("node_modules");
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
return Array.from(dirs);
|
|
1667
|
-
},
|
|
1668
|
-
realpath(path) {
|
|
1669
|
-
return path;
|
|
1670
|
-
},
|
|
1671
|
-
getEnvironmentVariable() {
|
|
1672
|
-
return;
|
|
1673
|
-
}
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
async function typecheck(options) {
|
|
1677
|
-
const {
|
|
1678
1505
|
fs,
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
if (tsconfigContent) {
|
|
1691
|
-
compilerOptions = {
|
|
1692
|
-
...parseTsConfig(tsconfigContent, tsconfigPath),
|
|
1693
|
-
noEmit: true
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
const host = createVfsCompilerHost(fileCache, libFiles, compilerOptions);
|
|
1697
|
-
const program = ts.createProgram([normalizedEntry], compilerOptions, host);
|
|
1698
|
-
const allDiagnostics = [
|
|
1699
|
-
...program.getSyntacticDiagnostics(),
|
|
1700
|
-
...program.getSemanticDiagnostics(),
|
|
1701
|
-
...program.getDeclarationDiagnostics()
|
|
1702
|
-
];
|
|
1703
|
-
const diagnostics = allDiagnostics.map(convertDiagnostic);
|
|
1704
|
-
const checkedFiles = program.getSourceFiles().map((sf) => sf.fileName).filter((f) => !f.includes("node_modules/typescript/lib"));
|
|
1705
|
-
const hasErrors = diagnostics.some((d) => d.category === "error");
|
|
1706
|
-
return {
|
|
1707
|
-
diagnostics,
|
|
1708
|
-
hasErrors,
|
|
1709
|
-
checkedFiles
|
|
1710
|
-
};
|
|
1711
|
-
}
|
|
1712
|
-
function formatDiagnostics(diagnostics) {
|
|
1713
|
-
return diagnostics.map((d) => {
|
|
1714
|
-
const location = d.file ? `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}` : "(global)";
|
|
1715
|
-
return `${location} - ${d.category} TS${d.code}: ${d.message}`;
|
|
1716
|
-
}).join(`
|
|
1717
|
-
`);
|
|
1718
|
-
}
|
|
1719
|
-
function formatDiagnosticsForAgent(diagnostics) {
|
|
1720
|
-
if (diagnostics.length === 0) {
|
|
1721
|
-
return "No type errors found.";
|
|
1722
|
-
}
|
|
1723
|
-
const errorCount = diagnostics.filter((d) => d.category === "error").length;
|
|
1724
|
-
const warningCount = diagnostics.filter((d) => d.category === "warning").length;
|
|
1725
|
-
const summary = errorCount > 0 || warningCount > 0 ? `Found ${errorCount} error(s) and ${warningCount} warning(s):
|
|
1726
|
-
|
|
1727
|
-
` : "";
|
|
1728
|
-
const formatted = diagnostics.map((d) => {
|
|
1729
|
-
const location = d.file ? `${d.file}${d.line ? ` at line ${d.line}` : ""}` : "Global";
|
|
1730
|
-
const prefix = d.category === "error" ? "Error" : d.category === "warning" ? "Warning" : "Info";
|
|
1731
|
-
return `${prefix} in ${location}: ${d.message} (TS${d.code})`;
|
|
1732
|
-
}).join(`
|
|
1733
|
-
|
|
1734
|
-
`);
|
|
1735
|
-
return summary + formatted;
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
// src/loader.ts
|
|
1739
|
-
class ModuleLoadError extends Error {
|
|
1740
|
-
constructor(message, cause) {
|
|
1741
|
-
super(message, { cause });
|
|
1742
|
-
this.name = "ModuleLoadError";
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
class ExportNotFoundError extends Error {
|
|
1747
|
-
exportName;
|
|
1748
|
-
availableExports;
|
|
1749
|
-
constructor(exportName, availableExports) {
|
|
1750
|
-
super(`Export "${exportName}" not found. Available exports: ${availableExports.length > 0 ? availableExports.join(", ") : "(none)"}`);
|
|
1751
|
-
this.exportName = exportName;
|
|
1752
|
-
this.availableExports = availableExports;
|
|
1753
|
-
this.name = "ExportNotFoundError";
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
function createModuleUrl(result) {
|
|
1757
|
-
const blob = new Blob([result.code], { type: "application/javascript" });
|
|
1758
|
-
return URL.createObjectURL(blob);
|
|
1759
|
-
}
|
|
1760
|
-
function revokeModuleUrl(url) {
|
|
1761
|
-
URL.revokeObjectURL(url);
|
|
1762
|
-
}
|
|
1763
|
-
async function loadModule(result) {
|
|
1764
|
-
const url = createModuleUrl(result);
|
|
1765
|
-
try {
|
|
1766
|
-
const module = await import(url);
|
|
1767
|
-
return module;
|
|
1768
|
-
} catch (err) {
|
|
1769
|
-
throw new ModuleLoadError(`Failed to load module: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
1770
|
-
} finally {
|
|
1771
|
-
revokeModuleUrl(url);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
async function loadExport(result, exportName = "default") {
|
|
1775
|
-
const module = await loadModule(result);
|
|
1776
|
-
if (!(exportName in module)) {
|
|
1777
|
-
const availableExports = Object.keys(module).filter((key) => !key.startsWith("__"));
|
|
1778
|
-
throw new ExportNotFoundError(exportName, availableExports);
|
|
1779
|
-
}
|
|
1780
|
-
return module[exportName];
|
|
1781
|
-
}
|
|
1782
|
-
async function loadDefault(result) {
|
|
1783
|
-
return loadExport(result, "default");
|
|
1784
|
-
}
|
|
1785
|
-
async function getExportNames(result) {
|
|
1786
|
-
const module = await loadModule(result);
|
|
1787
|
-
return Object.keys(module).filter((key) => !key.startsWith("__"));
|
|
1788
|
-
}
|
|
1789
|
-
async function hasExport(result, exportName) {
|
|
1790
|
-
const module = await loadModule(result);
|
|
1791
|
-
return exportName in module;
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// src/commands/compile.ts
|
|
1795
|
-
function createTscCommand(deps) {
|
|
1796
|
-
const { fs, libFiles, tsconfigPath } = deps;
|
|
1797
|
-
return defineCommand("tsc", async (args, _ctx) => {
|
|
1798
|
-
const entryPoint = args[0];
|
|
1799
|
-
if (!entryPoint) {
|
|
1800
|
-
return {
|
|
1801
|
-
stdout: "",
|
|
1802
|
-
stderr: `Usage: tsc <entry-point>
|
|
1803
|
-
|
|
1804
|
-
Example: tsc /src/index.ts
|
|
1805
|
-
`,
|
|
1806
|
-
exitCode: 1
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
try {
|
|
1810
|
-
if (!await fs.exists(entryPoint)) {
|
|
1811
|
-
return {
|
|
1812
|
-
stdout: "",
|
|
1813
|
-
stderr: `Error: Entry point not found: ${entryPoint}
|
|
1814
|
-
`,
|
|
1815
|
-
exitCode: 1
|
|
1816
|
-
};
|
|
1817
|
-
}
|
|
1818
|
-
const result = await typecheck({
|
|
1819
|
-
fs,
|
|
1820
|
-
entryPoint,
|
|
1821
|
-
tsconfigPath,
|
|
1822
|
-
libFiles
|
|
1823
|
-
});
|
|
1824
|
-
if (result.hasErrors) {
|
|
1825
|
-
const formatted = formatDiagnosticsForAgent(result.diagnostics);
|
|
1826
|
-
return {
|
|
1827
|
-
stdout: "",
|
|
1828
|
-
stderr: formatted + `
|
|
1829
|
-
`,
|
|
1830
|
-
exitCode: 1
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
1833
|
-
const checkedCount = result.checkedFiles.length;
|
|
1834
|
-
const warningCount = result.diagnostics.filter((d) => d.category === "warning").length;
|
|
1835
|
-
let output = `Type check passed. Checked ${checkedCount} file(s).
|
|
1836
|
-
`;
|
|
1837
|
-
if (warningCount > 0) {
|
|
1838
|
-
output += `
|
|
1839
|
-
Warnings:
|
|
1840
|
-
${formatDiagnosticsForAgent(result.diagnostics.filter((d) => d.category === "warning"))}
|
|
1841
|
-
`;
|
|
1842
|
-
}
|
|
1843
|
-
return {
|
|
1844
|
-
stdout: output,
|
|
1845
|
-
stderr: "",
|
|
1846
|
-
exitCode: 0
|
|
1847
|
-
};
|
|
1848
|
-
} catch (err) {
|
|
1849
|
-
return {
|
|
1850
|
-
stdout: "",
|
|
1851
|
-
stderr: `Type check failed: ${err instanceof Error ? err.message : String(err)}
|
|
1852
|
-
`,
|
|
1853
|
-
exitCode: 1
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
function createBuildCommand(deps) {
|
|
1859
|
-
const { fs, libFiles, tsconfigPath, onBuild, getValidation, sharedModules } = deps;
|
|
1860
|
-
return defineCommand("build", async (args, _ctx) => {
|
|
1861
|
-
let entryPoint = null;
|
|
1862
|
-
let skipTypecheck = false;
|
|
1863
|
-
let minify = false;
|
|
1864
|
-
let format = "esm";
|
|
1865
|
-
for (let i = 0;i < args.length; i++) {
|
|
1866
|
-
const arg = args[i];
|
|
1867
|
-
if (arg === "--skip-typecheck" || arg === "-s") {
|
|
1868
|
-
skipTypecheck = true;
|
|
1869
|
-
} else if (arg === "--minify" || arg === "-m") {
|
|
1870
|
-
minify = true;
|
|
1871
|
-
} else if ((arg === "--format" || arg === "-f") && args[i + 1]) {
|
|
1872
|
-
const f = args[++i].toLowerCase();
|
|
1873
|
-
if (f === "esm" || f === "iife" || f === "cjs") {
|
|
1874
|
-
format = f;
|
|
1875
|
-
}
|
|
1876
|
-
} else if (!arg.startsWith("-")) {
|
|
1877
|
-
entryPoint = arg;
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
if (!entryPoint) {
|
|
1881
|
-
return {
|
|
1882
|
-
stdout: "",
|
|
1883
|
-
stderr: `Usage: build <entry-point> [options]
|
|
1884
|
-
|
|
1885
|
-
Options:
|
|
1886
|
-
--skip-typecheck, -s Skip type checking
|
|
1887
|
-
--minify, -m Minify output
|
|
1888
|
-
--format, -f <fmt> Output format (esm|iife|cjs)
|
|
1889
|
-
|
|
1890
|
-
Example: build /src/index.ts
|
|
1891
|
-
`,
|
|
1892
|
-
exitCode: 1
|
|
1893
|
-
};
|
|
1894
|
-
}
|
|
1895
|
-
try {
|
|
1896
|
-
if (!await fs.exists(entryPoint)) {
|
|
1897
|
-
return {
|
|
1898
|
-
stdout: "",
|
|
1899
|
-
stderr: `Error: Entry point not found: ${entryPoint}
|
|
1900
|
-
`,
|
|
1901
|
-
exitCode: 1
|
|
1902
|
-
};
|
|
1903
|
-
}
|
|
1904
|
-
let typecheckResult = null;
|
|
1905
|
-
if (!skipTypecheck) {
|
|
1906
|
-
typecheckResult = await typecheck({
|
|
1907
|
-
fs,
|
|
1908
|
-
entryPoint,
|
|
1909
|
-
tsconfigPath,
|
|
1910
|
-
libFiles
|
|
1911
|
-
});
|
|
1912
|
-
if (typecheckResult.hasErrors) {
|
|
1913
|
-
const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
|
|
1914
|
-
return {
|
|
1915
|
-
stdout: "",
|
|
1916
|
-
stderr: `Build failed: Type errors found.
|
|
1917
|
-
|
|
1918
|
-
${formatted}
|
|
1919
|
-
`,
|
|
1920
|
-
exitCode: 1
|
|
1921
|
-
};
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
const bundleResult = await bundle({
|
|
1925
|
-
fs,
|
|
1926
|
-
entryPoint,
|
|
1927
|
-
format,
|
|
1928
|
-
minify,
|
|
1929
|
-
sharedModules
|
|
1930
|
-
});
|
|
1931
|
-
let loadedModule;
|
|
1932
|
-
try {
|
|
1933
|
-
loadedModule = await loadModule(bundleResult);
|
|
1934
|
-
} catch (err) {
|
|
1935
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1936
|
-
return {
|
|
1937
|
-
stdout: "",
|
|
1938
|
-
stderr: `Build failed: Module failed to load.
|
|
1939
|
-
|
|
1940
|
-
${errorMessage}
|
|
1941
|
-
`,
|
|
1942
|
-
exitCode: 1
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
const validateFn = getValidation?.();
|
|
1946
|
-
let validatedModule = loadedModule;
|
|
1947
|
-
if (validateFn) {
|
|
1948
|
-
try {
|
|
1949
|
-
validatedModule = validateFn(loadedModule);
|
|
1950
|
-
} catch (err) {
|
|
1951
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1952
|
-
return {
|
|
1953
|
-
stdout: "",
|
|
1954
|
-
stderr: `Build failed: Validation error.
|
|
1955
|
-
|
|
1956
|
-
${errorMessage}
|
|
1957
|
-
`,
|
|
1958
|
-
exitCode: 1
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
if (onBuild) {
|
|
1963
|
-
await onBuild({ bundle: bundleResult, module: validatedModule });
|
|
1964
|
-
}
|
|
1965
|
-
let output = `Build successful!
|
|
1966
|
-
`;
|
|
1967
|
-
output += `Entry: ${entryPoint}
|
|
1968
|
-
`;
|
|
1969
|
-
output += `Format: ${format}
|
|
1970
|
-
`;
|
|
1971
|
-
output += `Size: ${(bundleResult.code.length / 1024).toFixed(2)} KB
|
|
1972
|
-
`;
|
|
1973
|
-
if (typecheckResult) {
|
|
1974
|
-
output += `Type checked: ${typecheckResult.checkedFiles.length} file(s)
|
|
1975
|
-
`;
|
|
1976
|
-
}
|
|
1977
|
-
output += `Bundled: ${bundleResult.includedFiles.length} file(s)
|
|
1978
|
-
`;
|
|
1979
|
-
const exportNames = Object.keys(loadedModule).filter((k) => !k.startsWith("__"));
|
|
1980
|
-
if (exportNames.length > 0) {
|
|
1981
|
-
output += `Exports: ${exportNames.join(", ")}
|
|
1982
|
-
`;
|
|
1983
|
-
}
|
|
1984
|
-
if (validateFn) {
|
|
1985
|
-
output += `Validation: passed
|
|
1986
|
-
`;
|
|
1987
|
-
}
|
|
1988
|
-
if (bundleResult.warnings.length > 0) {
|
|
1989
|
-
output += `
|
|
1990
|
-
Build warnings:
|
|
1991
|
-
${formatEsbuildMessages(bundleResult.warnings)}
|
|
1992
|
-
`;
|
|
1993
|
-
}
|
|
1994
|
-
if (typecheckResult) {
|
|
1995
|
-
const warnings = typecheckResult.diagnostics.filter((d) => d.category === "warning");
|
|
1996
|
-
if (warnings.length > 0) {
|
|
1997
|
-
output += `
|
|
1998
|
-
Type warnings:
|
|
1999
|
-
${formatDiagnosticsForAgent(warnings)}
|
|
2000
|
-
`;
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
return {
|
|
2004
|
-
stdout: output,
|
|
2005
|
-
stderr: "",
|
|
2006
|
-
exitCode: 0
|
|
2007
|
-
};
|
|
2008
|
-
} catch (err) {
|
|
2009
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2010
|
-
return {
|
|
2011
|
-
stdout: "",
|
|
2012
|
-
stderr: `Build failed: ${errorMessage}
|
|
2013
|
-
`,
|
|
2014
|
-
exitCode: 1
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
});
|
|
2018
|
-
}
|
|
2019
|
-
// src/commands/packages.ts
|
|
2020
|
-
import { defineCommand as defineCommand2 } from "just-bash/browser";
|
|
2021
|
-
function createInstallCommand(deps) {
|
|
2022
|
-
const { fs, typesCache } = deps;
|
|
2023
|
-
return defineCommand2("install", async (args, _ctx) => {
|
|
2024
|
-
if (args.length === 0) {
|
|
2025
|
-
return {
|
|
2026
|
-
stdout: "",
|
|
2027
|
-
stderr: `Usage: install <package>[@version] [...packages]
|
|
2028
|
-
|
|
2029
|
-
Examples:
|
|
2030
|
-
install react
|
|
2031
|
-
install lodash@4.17.21
|
|
2032
|
-
install @tanstack/react-query@5
|
|
2033
|
-
`,
|
|
2034
|
-
exitCode: 1
|
|
2035
|
-
};
|
|
2036
|
-
}
|
|
2037
|
-
const results = [];
|
|
2038
|
-
let hasError = false;
|
|
2039
|
-
for (const packageSpec of args) {
|
|
2040
|
-
try {
|
|
2041
|
-
const result = await installPackage(fs, packageSpec, { cache: typesCache });
|
|
2042
|
-
let status = `+ ${result.name}@${result.version}`;
|
|
2043
|
-
if (result.typesInstalled) {
|
|
2044
|
-
status += ` (${result.typeFilesCount} type file${result.typeFilesCount !== 1 ? "s" : ""})`;
|
|
2045
|
-
if (result.fromCache) {
|
|
2046
|
-
status += " [cached]";
|
|
2047
|
-
}
|
|
2048
|
-
} else if (result.typesError) {
|
|
2049
|
-
status += ` (no types: ${result.typesError})`;
|
|
2050
|
-
}
|
|
2051
|
-
results.push(status);
|
|
2052
|
-
} catch (err) {
|
|
2053
|
-
hasError = true;
|
|
2054
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2055
|
-
results.push(`x ${packageSpec}: ${message}`);
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
const output = results.join(`
|
|
2059
|
-
`) + `
|
|
2060
|
-
`;
|
|
2061
|
-
if (hasError) {
|
|
2062
|
-
return {
|
|
2063
|
-
stdout: "",
|
|
2064
|
-
stderr: output,
|
|
2065
|
-
exitCode: 1
|
|
2066
|
-
};
|
|
2067
|
-
}
|
|
2068
|
-
return {
|
|
2069
|
-
stdout: output,
|
|
2070
|
-
stderr: "",
|
|
2071
|
-
exitCode: 0
|
|
2072
|
-
};
|
|
2073
|
-
});
|
|
2074
|
-
}
|
|
2075
|
-
function createUninstallCommand(deps) {
|
|
2076
|
-
const { fs } = deps;
|
|
2077
|
-
return defineCommand2("uninstall", async (args, _ctx) => {
|
|
2078
|
-
if (args.length === 0) {
|
|
2079
|
-
return {
|
|
2080
|
-
stdout: "",
|
|
2081
|
-
stderr: `Usage: uninstall <package> [...packages]
|
|
2082
|
-
`,
|
|
2083
|
-
exitCode: 1
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
const results = [];
|
|
2087
|
-
let hasError = false;
|
|
2088
|
-
for (const packageName of args) {
|
|
2089
|
-
try {
|
|
2090
|
-
const removed = await uninstallPackage(fs, packageName);
|
|
2091
|
-
if (removed) {
|
|
2092
|
-
results.push(`- ${packageName}`);
|
|
2093
|
-
} else {
|
|
2094
|
-
results.push(`x ${packageName}: not installed`);
|
|
2095
|
-
hasError = true;
|
|
2096
|
-
}
|
|
2097
|
-
} catch (err) {
|
|
2098
|
-
hasError = true;
|
|
2099
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2100
|
-
results.push(`x ${packageName}: ${message}`);
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
const output = results.join(`
|
|
2104
|
-
`) + `
|
|
2105
|
-
`;
|
|
2106
|
-
if (hasError) {
|
|
2107
|
-
return {
|
|
2108
|
-
stdout: "",
|
|
2109
|
-
stderr: output,
|
|
2110
|
-
exitCode: 1
|
|
2111
|
-
};
|
|
2112
|
-
}
|
|
2113
|
-
return {
|
|
2114
|
-
stdout: output,
|
|
2115
|
-
stderr: "",
|
|
2116
|
-
exitCode: 0
|
|
2117
|
-
};
|
|
2118
|
-
});
|
|
2119
|
-
}
|
|
2120
|
-
function createListCommand(deps) {
|
|
2121
|
-
const { fs } = deps;
|
|
2122
|
-
return defineCommand2("list", async (_args, _ctx) => {
|
|
2123
|
-
try {
|
|
2124
|
-
const packages = await listPackages(fs);
|
|
2125
|
-
if (packages.length === 0) {
|
|
2126
|
-
return {
|
|
2127
|
-
stdout: `No packages installed.
|
|
2128
|
-
`,
|
|
2129
|
-
stderr: "",
|
|
2130
|
-
exitCode: 0
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2133
|
-
const output = packages.map((pkg) => `${pkg.name}@${pkg.version}`).join(`
|
|
2134
|
-
`) + `
|
|
2135
|
-
`;
|
|
2136
|
-
return {
|
|
2137
|
-
stdout: output,
|
|
2138
|
-
stderr: "",
|
|
2139
|
-
exitCode: 0
|
|
2140
|
-
};
|
|
2141
|
-
} catch (err) {
|
|
2142
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2143
|
-
return {
|
|
2144
|
-
stdout: "",
|
|
2145
|
-
stderr: `Failed to list packages: ${message}
|
|
2146
|
-
`,
|
|
2147
|
-
exitCode: 1
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
});
|
|
2151
|
-
}
|
|
2152
|
-
// src/commands/run.ts
|
|
2153
|
-
import { defineCommand as defineCommand3 } from "just-bash/browser";
|
|
2154
|
-
function createRunCommand(deps) {
|
|
2155
|
-
const { fs, libFiles, tsconfigPath, runOptions = {}, sharedModules } = deps;
|
|
2156
|
-
return defineCommand3("run", async (args, _ctx) => {
|
|
2157
|
-
let entryPoint = null;
|
|
2158
|
-
let skipTypecheck = runOptions.skipTypecheck ?? false;
|
|
2159
|
-
let timeout = runOptions.timeout ?? 30000;
|
|
2160
|
-
const scriptArgs = [];
|
|
2161
|
-
let collectingArgs = false;
|
|
2162
|
-
for (let i = 0;i < args.length; i++) {
|
|
2163
|
-
const arg = args[i];
|
|
2164
|
-
if (collectingArgs) {
|
|
2165
|
-
scriptArgs.push(arg);
|
|
2166
|
-
continue;
|
|
2167
|
-
}
|
|
2168
|
-
if (arg === "--") {
|
|
2169
|
-
collectingArgs = true;
|
|
2170
|
-
} else if (arg === "--skip-typecheck" || arg === "-s") {
|
|
2171
|
-
skipTypecheck = true;
|
|
2172
|
-
} else if ((arg === "--timeout" || arg === "-t") && args[i + 1]) {
|
|
2173
|
-
timeout = parseInt(args[++i], 10);
|
|
2174
|
-
if (isNaN(timeout))
|
|
2175
|
-
timeout = 30000;
|
|
2176
|
-
} else if (!arg.startsWith("-")) {
|
|
2177
|
-
entryPoint = arg;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
if (!entryPoint) {
|
|
2181
|
-
return {
|
|
2182
|
-
stdout: "",
|
|
2183
|
-
stderr: `Usage: run <entry-point> [options] [-- args...]
|
|
2184
|
-
|
|
2185
|
-
Options:
|
|
2186
|
-
--skip-typecheck, -s Skip type checking
|
|
2187
|
-
--timeout, -t <ms> Execution timeout (default: 30000)
|
|
2188
|
-
|
|
2189
|
-
Example: run /src/index.ts
|
|
2190
|
-
`,
|
|
2191
|
-
exitCode: 1
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
const logs = [];
|
|
2195
|
-
const originalConsole = {
|
|
2196
|
-
log: console.log,
|
|
2197
|
-
warn: console.warn,
|
|
2198
|
-
error: console.error,
|
|
2199
|
-
info: console.info,
|
|
2200
|
-
debug: console.debug
|
|
2201
|
-
};
|
|
2202
|
-
const formatArgs = (...a) => a.map((v) => typeof v === "object" ? JSON.stringify(v) : String(v)).join(" ");
|
|
2203
|
-
const captureLog = (...a) => {
|
|
2204
|
-
logs.push(formatArgs(...a));
|
|
2205
|
-
originalConsole.log.apply(console, a);
|
|
2206
|
-
};
|
|
2207
|
-
const captureWarn = (...a) => {
|
|
2208
|
-
logs.push(`[warn] ${formatArgs(...a)}`);
|
|
2209
|
-
originalConsole.warn.apply(console, a);
|
|
2210
|
-
};
|
|
2211
|
-
const captureError = (...a) => {
|
|
2212
|
-
logs.push(`[error] ${formatArgs(...a)}`);
|
|
2213
|
-
originalConsole.error.apply(console, a);
|
|
2214
|
-
};
|
|
2215
|
-
const captureInfo = (...a) => {
|
|
2216
|
-
logs.push(`[info] ${formatArgs(...a)}`);
|
|
2217
|
-
originalConsole.info.apply(console, a);
|
|
2218
|
-
};
|
|
2219
|
-
const captureDebug = (...a) => {
|
|
2220
|
-
logs.push(`[debug] ${formatArgs(...a)}`);
|
|
2221
|
-
originalConsole.debug.apply(console, a);
|
|
2222
|
-
};
|
|
2223
|
-
const restoreConsole = () => {
|
|
2224
|
-
console.log = originalConsole.log;
|
|
2225
|
-
console.warn = originalConsole.warn;
|
|
2226
|
-
console.error = originalConsole.error;
|
|
2227
|
-
console.info = originalConsole.info;
|
|
2228
|
-
console.debug = originalConsole.debug;
|
|
2229
|
-
};
|
|
2230
|
-
try {
|
|
2231
|
-
if (!await fs.exists(entryPoint)) {
|
|
2232
|
-
return {
|
|
2233
|
-
stdout: "",
|
|
2234
|
-
stderr: `Error: Entry point not found: ${entryPoint}
|
|
2235
|
-
`,
|
|
2236
|
-
exitCode: 1
|
|
2237
|
-
};
|
|
2238
|
-
}
|
|
2239
|
-
if (!skipTypecheck) {
|
|
2240
|
-
const typecheckResult = await typecheck({
|
|
2241
|
-
fs,
|
|
2242
|
-
entryPoint,
|
|
2243
|
-
tsconfigPath,
|
|
2244
|
-
libFiles
|
|
2245
|
-
});
|
|
2246
|
-
if (typecheckResult.hasErrors) {
|
|
2247
|
-
const formatted = formatDiagnosticsForAgent(typecheckResult.diagnostics);
|
|
2248
|
-
return {
|
|
2249
|
-
stdout: "",
|
|
2250
|
-
stderr: `Type errors:
|
|
2251
|
-
${formatted}
|
|
2252
|
-
`,
|
|
2253
|
-
exitCode: 1
|
|
2254
|
-
};
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
const bundleResult = await bundle({
|
|
2258
|
-
fs,
|
|
2259
|
-
entryPoint,
|
|
2260
|
-
format: "esm",
|
|
2261
|
-
sharedModules
|
|
2262
|
-
});
|
|
2263
|
-
console.log = captureLog;
|
|
2264
|
-
console.warn = captureWarn;
|
|
2265
|
-
console.error = captureError;
|
|
2266
|
-
console.info = captureInfo;
|
|
2267
|
-
console.debug = captureDebug;
|
|
2268
|
-
const context = {
|
|
2269
|
-
fs,
|
|
2270
|
-
env: { ...runOptions.env },
|
|
2271
|
-
args: scriptArgs,
|
|
2272
|
-
log: captureLog,
|
|
2273
|
-
error: captureError
|
|
2274
|
-
};
|
|
2275
|
-
const startTime = performance.now();
|
|
2276
|
-
let returnValue;
|
|
2277
|
-
const executeCode = async () => {
|
|
2278
|
-
const module = await loadModule(bundleResult);
|
|
2279
|
-
if (typeof module.main === "function") {
|
|
2280
|
-
returnValue = await module.main(context);
|
|
2281
|
-
}
|
|
2282
|
-
};
|
|
2283
|
-
if (timeout > 0) {
|
|
2284
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
2285
|
-
setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
|
|
2286
|
-
});
|
|
2287
|
-
await Promise.race([executeCode(), timeoutPromise]);
|
|
2288
|
-
} else {
|
|
2289
|
-
await executeCode();
|
|
2290
|
-
}
|
|
2291
|
-
const executionTimeMs = performance.now() - startTime;
|
|
2292
|
-
restoreConsole();
|
|
2293
|
-
let output = "";
|
|
2294
|
-
if (logs.length > 0) {
|
|
2295
|
-
output = logs.join(`
|
|
2296
|
-
`) + `
|
|
2297
|
-
`;
|
|
2298
|
-
}
|
|
2299
|
-
if (returnValue !== undefined) {
|
|
2300
|
-
const returnStr = typeof returnValue === "object" ? JSON.stringify(returnValue, null, 2) : String(returnValue);
|
|
2301
|
-
output += `[return] ${returnStr}
|
|
2302
|
-
`;
|
|
2303
|
-
}
|
|
2304
|
-
output += `
|
|
2305
|
-
Execution completed in ${executionTimeMs.toFixed(2)}ms
|
|
2306
|
-
`;
|
|
2307
|
-
return {
|
|
2308
|
-
stdout: output,
|
|
2309
|
-
stderr: "",
|
|
2310
|
-
exitCode: 0
|
|
2311
|
-
};
|
|
2312
|
-
} catch (err) {
|
|
2313
|
-
restoreConsole();
|
|
2314
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
2315
|
-
const errorStack = err instanceof Error && err.stack ? `
|
|
2316
|
-
${err.stack}` : "";
|
|
2317
|
-
let output = "";
|
|
2318
|
-
if (logs.length > 0) {
|
|
2319
|
-
output = logs.join(`
|
|
2320
|
-
`) + `
|
|
2321
|
-
|
|
2322
|
-
`;
|
|
2323
|
-
}
|
|
2324
|
-
return {
|
|
2325
|
-
stdout: output,
|
|
2326
|
-
stderr: `Runtime error: ${errorMessage}${errorStack}
|
|
2327
|
-
`,
|
|
2328
|
-
exitCode: 1
|
|
1506
|
+
exec,
|
|
1507
|
+
get lastBuild() {
|
|
1508
|
+
return lastBuild;
|
|
1509
|
+
},
|
|
1510
|
+
getState() {
|
|
1511
|
+
return { files: fs.getFiles() };
|
|
1512
|
+
},
|
|
1513
|
+
onBuild(callback) {
|
|
1514
|
+
onBuildCallbacks.add(callback);
|
|
1515
|
+
return () => {
|
|
1516
|
+
onBuildCallbacks.delete(callback);
|
|
2329
1517
|
};
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
createInstallCommand(deps),
|
|
2340
|
-
createUninstallCommand(deps),
|
|
2341
|
-
createListCommand(deps)
|
|
2342
|
-
];
|
|
1518
|
+
},
|
|
1519
|
+
install,
|
|
1520
|
+
uninstall,
|
|
1521
|
+
build,
|
|
1522
|
+
typecheck,
|
|
1523
|
+
run,
|
|
1524
|
+
readFile: (path) => fs.readFile(path),
|
|
1525
|
+
writeFile: (path, content) => fs.writeFile(path, content)
|
|
1526
|
+
};
|
|
2343
1527
|
}
|
|
2344
1528
|
|
|
2345
|
-
// src/
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
1529
|
+
// src/core/sandlot.ts
|
|
1530
|
+
function createSandlot(options) {
|
|
1531
|
+
const {
|
|
1532
|
+
bundler,
|
|
1533
|
+
typechecker,
|
|
1534
|
+
typesResolver,
|
|
1535
|
+
executor,
|
|
1536
|
+
sharedModules,
|
|
1537
|
+
sandboxDefaults = {}
|
|
1538
|
+
} = options;
|
|
1539
|
+
const sharedModuleRegistry = createSharedModuleRegistry(sharedModules);
|
|
1540
|
+
const sandboxContext = {
|
|
1541
|
+
bundler,
|
|
1542
|
+
typechecker,
|
|
1543
|
+
typesResolver,
|
|
1544
|
+
sharedModuleRegistry,
|
|
1545
|
+
executor
|
|
1546
|
+
};
|
|
1547
|
+
return {
|
|
1548
|
+
async createSandbox(sandboxOptions = {}) {
|
|
1549
|
+
const fs = Filesystem.create({
|
|
1550
|
+
maxSizeBytes: sandboxOptions.maxFilesystemSize ?? sandboxDefaults.maxFilesystemSize
|
|
1551
|
+
});
|
|
1552
|
+
return createSandboxImpl(fs, sandboxOptions, sandboxContext);
|
|
1553
|
+
},
|
|
1554
|
+
get sharedModules() {
|
|
1555
|
+
return sharedModuleRegistry;
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
2350
1558
|
}
|
|
1559
|
+
// src/core/typechecker.ts
|
|
1560
|
+
import ts from "typescript";
|
|
1561
|
+
var TS_VERSION = "5.9.3";
|
|
1562
|
+
var DEFAULT_CDN_BASE = `https://cdn.jsdelivr.net/npm/typescript@${TS_VERSION}/lib`;
|
|
1563
|
+
var DEFAULT_LIBS = ["es2020", "dom", "dom.iterable"];
|
|
1564
|
+
var LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
|
|
2351
1565
|
function parseLibReferences(content) {
|
|
2352
1566
|
const refs = [];
|
|
2353
1567
|
const regex = /\/\/\/\s*<reference\s+lib="([^"]+)"\s*\/>/g;
|
|
@@ -2362,20 +1576,16 @@ function parseLibReferences(content) {
|
|
|
2362
1576
|
function libNameToFileName(name) {
|
|
2363
1577
|
return `lib.${name}.d.ts`;
|
|
2364
1578
|
}
|
|
2365
|
-
function
|
|
2366
|
-
const match = filePath.match(/lib\.([^/]+)\.d\.ts$/);
|
|
2367
|
-
return match?.[1] ?? null;
|
|
2368
|
-
}
|
|
2369
|
-
async function fetchLibFile(name) {
|
|
1579
|
+
async function fetchLibFile(name, baseUrl) {
|
|
2370
1580
|
const fileName = libNameToFileName(name);
|
|
2371
|
-
const url = `${
|
|
1581
|
+
const url = `${baseUrl}/${fileName}`;
|
|
2372
1582
|
const response = await fetch(url);
|
|
2373
1583
|
if (!response.ok) {
|
|
2374
1584
|
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2375
1585
|
}
|
|
2376
1586
|
return response.text();
|
|
2377
1587
|
}
|
|
2378
|
-
async function fetchAllLibs(libs) {
|
|
1588
|
+
async function fetchAllLibs(libs, baseUrl) {
|
|
2379
1589
|
const result = new Map;
|
|
2380
1590
|
const pending = new Set(libs);
|
|
2381
1591
|
const fetched = new Set;
|
|
@@ -2388,10 +1598,10 @@ async function fetchAllLibs(libs) {
|
|
|
2388
1598
|
}
|
|
2389
1599
|
fetched.add(name);
|
|
2390
1600
|
try {
|
|
2391
|
-
const content = await fetchLibFile(name);
|
|
1601
|
+
const content = await fetchLibFile(name, baseUrl);
|
|
2392
1602
|
return { name, content };
|
|
2393
1603
|
} catch (err) {
|
|
2394
|
-
console.warn(`Failed to fetch lib.${name}.d.ts:`, err);
|
|
1604
|
+
console.warn(`[typechecker] Failed to fetch lib.${name}.d.ts:`, err);
|
|
2395
1605
|
return { name, content: null };
|
|
2396
1606
|
}
|
|
2397
1607
|
}));
|
|
@@ -2409,292 +1619,471 @@ async function fetchAllLibs(libs) {
|
|
|
2409
1619
|
}
|
|
2410
1620
|
return result;
|
|
2411
1621
|
}
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
async getOrFetch(libs) {
|
|
2416
|
-
if (memoryCache) {
|
|
2417
|
-
const missing = libs.filter((lib) => !memoryCache.has(lib));
|
|
2418
|
-
if (missing.length === 0) {
|
|
2419
|
-
return memoryCache;
|
|
2420
|
-
}
|
|
2421
|
-
console.log(`Cache missing libs: ${missing.join(", ")}, fetching all...`);
|
|
2422
|
-
}
|
|
2423
|
-
console.log(`Fetching TypeScript libs from CDN: ${libs.join(", ")}...`);
|
|
2424
|
-
const fetched = await fetchAllLibs(libs);
|
|
2425
|
-
console.log(`Fetched ${fetched.size} lib files`);
|
|
2426
|
-
memoryCache = fetched;
|
|
2427
|
-
return fetched;
|
|
2428
|
-
}
|
|
2429
|
-
get() {
|
|
2430
|
-
return memoryCache;
|
|
2431
|
-
}
|
|
2432
|
-
set(libs) {
|
|
2433
|
-
memoryCache = libs;
|
|
1622
|
+
function normalizePath(path) {
|
|
1623
|
+
if (!path.startsWith("/")) {
|
|
1624
|
+
return "/" + path;
|
|
2434
1625
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
1626
|
+
return path;
|
|
1627
|
+
}
|
|
1628
|
+
function getLibContent(fileName, libFiles) {
|
|
1629
|
+
const match = fileName.match(/lib\.([^/]+)\.d\.ts$/);
|
|
1630
|
+
if (match?.[1]) {
|
|
1631
|
+
return libFiles.get(match[1]);
|
|
2437
1632
|
}
|
|
1633
|
+
return;
|
|
2438
1634
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
1635
|
+
function createCompilerHost(fs, libFiles, options) {
|
|
1636
|
+
return {
|
|
1637
|
+
getSourceFile(fileName, languageVersion, onError) {
|
|
1638
|
+
const normalizedPath = normalizePath(fileName);
|
|
1639
|
+
try {
|
|
1640
|
+
if (fs.exists(normalizedPath)) {
|
|
1641
|
+
const stat = fs.stat(normalizedPath);
|
|
1642
|
+
if (stat.isFile) {
|
|
1643
|
+
const content = fs.readFile(normalizedPath);
|
|
1644
|
+
return ts.createSourceFile(normalizedPath, content, languageVersion, true);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
} catch {}
|
|
1648
|
+
try {
|
|
1649
|
+
if (fs.exists(fileName)) {
|
|
1650
|
+
const stat = fs.stat(fileName);
|
|
1651
|
+
if (stat.isFile) {
|
|
1652
|
+
const content = fs.readFile(fileName);
|
|
1653
|
+
return ts.createSourceFile(fileName, content, languageVersion, true);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
} catch {}
|
|
1657
|
+
const libContent = getLibContent(fileName, libFiles);
|
|
1658
|
+
if (libContent !== undefined) {
|
|
1659
|
+
return ts.createSourceFile(fileName, libContent, languageVersion, true);
|
|
1660
|
+
}
|
|
1661
|
+
if (onError) {
|
|
1662
|
+
onError(`File not found: ${fileName}`);
|
|
1663
|
+
}
|
|
1664
|
+
return;
|
|
1665
|
+
},
|
|
1666
|
+
getDefaultLibFileName(opts) {
|
|
1667
|
+
return LIB_PATH_PREFIX + ts.getDefaultLibFileName(opts);
|
|
1668
|
+
},
|
|
1669
|
+
writeFile() {},
|
|
1670
|
+
getCurrentDirectory() {
|
|
1671
|
+
return "/";
|
|
1672
|
+
},
|
|
1673
|
+
getCanonicalFileName(fileName) {
|
|
1674
|
+
return fileName;
|
|
1675
|
+
},
|
|
1676
|
+
useCaseSensitiveFileNames() {
|
|
1677
|
+
return true;
|
|
1678
|
+
},
|
|
1679
|
+
getNewLine() {
|
|
1680
|
+
return `
|
|
1681
|
+
`;
|
|
1682
|
+
},
|
|
1683
|
+
fileExists(fileName) {
|
|
1684
|
+
const normalizedPath = normalizePath(fileName);
|
|
1685
|
+
try {
|
|
1686
|
+
if (fs.exists(normalizedPath)) {
|
|
1687
|
+
return fs.stat(normalizedPath).isFile;
|
|
1688
|
+
}
|
|
1689
|
+
} catch {}
|
|
1690
|
+
return getLibContent(fileName, libFiles) !== undefined;
|
|
1691
|
+
},
|
|
1692
|
+
readFile(fileName) {
|
|
1693
|
+
const normalizedPath = normalizePath(fileName);
|
|
1694
|
+
try {
|
|
1695
|
+
if (fs.exists(normalizedPath)) {
|
|
1696
|
+
return fs.readFile(normalizedPath);
|
|
1697
|
+
}
|
|
1698
|
+
} catch {}
|
|
1699
|
+
return getLibContent(fileName, libFiles);
|
|
1700
|
+
},
|
|
1701
|
+
directoryExists(directoryName) {
|
|
1702
|
+
const normalizedDir = normalizePath(directoryName);
|
|
1703
|
+
try {
|
|
1704
|
+
if (fs.exists(normalizedDir)) {
|
|
1705
|
+
return fs.stat(normalizedDir).isDirectory;
|
|
1706
|
+
}
|
|
1707
|
+
} catch {}
|
|
1708
|
+
if (normalizedDir === "/node_modules/typescript/lib" || normalizedDir === "/node_modules/typescript" || normalizedDir === "/node_modules") {
|
|
1709
|
+
return libFiles.size > 0;
|
|
1710
|
+
}
|
|
1711
|
+
return false;
|
|
1712
|
+
},
|
|
1713
|
+
getDirectories(path) {
|
|
1714
|
+
const normalizedPath = normalizePath(path);
|
|
1715
|
+
try {
|
|
1716
|
+
if (!fs.exists(normalizedPath)) {
|
|
1717
|
+
return [];
|
|
1718
|
+
}
|
|
1719
|
+
const stat = fs.stat(normalizedPath);
|
|
1720
|
+
if (!stat.isDirectory) {
|
|
1721
|
+
return [];
|
|
1722
|
+
}
|
|
1723
|
+
const entries = fs.readdir(normalizedPath);
|
|
1724
|
+
const dirs = [];
|
|
1725
|
+
for (const name of entries) {
|
|
1726
|
+
const childPath = normalizedPath === "/" ? `/${name}` : `${normalizedPath}/${name}`;
|
|
1727
|
+
try {
|
|
1728
|
+
if (fs.stat(childPath).isDirectory) {
|
|
1729
|
+
dirs.push(name);
|
|
1730
|
+
}
|
|
1731
|
+
} catch {}
|
|
1732
|
+
}
|
|
1733
|
+
return dirs;
|
|
1734
|
+
} catch {
|
|
1735
|
+
return [];
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
realpath(path) {
|
|
1739
|
+
return path;
|
|
1740
|
+
},
|
|
1741
|
+
getEnvironmentVariable() {
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
2442
1745
|
}
|
|
2443
|
-
|
|
2444
|
-
// src/shared-resources.ts
|
|
2445
|
-
async function createSharedResources(options = {}) {
|
|
2446
|
-
const { libs = getDefaultBrowserLibs(), skipLibs = false, skipBundler = false } = options;
|
|
2447
|
-
const libsPromise = skipLibs ? Promise.resolve(new Map) : fetchAndCacheLibs(libs);
|
|
2448
|
-
const bundlerPromise = skipBundler ? Promise.resolve() : initBundler();
|
|
2449
|
-
const typesCache = new InMemoryTypesCache;
|
|
2450
|
-
const [libFiles] = await Promise.all([libsPromise, bundlerPromise]);
|
|
1746
|
+
function getDefaultCompilerOptions() {
|
|
2451
1747
|
return {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
1748
|
+
target: ts.ScriptTarget.ES2020,
|
|
1749
|
+
module: ts.ModuleKind.ESNext,
|
|
1750
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
1751
|
+
esModuleInterop: true,
|
|
1752
|
+
strict: true,
|
|
1753
|
+
skipLibCheck: true,
|
|
1754
|
+
noEmit: true,
|
|
1755
|
+
jsx: ts.JsxEmit.ReactJSX,
|
|
1756
|
+
allowJs: true,
|
|
1757
|
+
resolveJsonModule: true,
|
|
1758
|
+
lib: ["lib.es2020.d.ts", "lib.dom.d.ts", "lib.dom.iterable.d.ts"]
|
|
2455
1759
|
};
|
|
2456
1760
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
return
|
|
2467
|
-
}
|
|
1761
|
+
function parseTsConfig(fs, configPath) {
|
|
1762
|
+
try {
|
|
1763
|
+
if (!fs.exists(configPath)) {
|
|
1764
|
+
return getDefaultCompilerOptions();
|
|
1765
|
+
}
|
|
1766
|
+
const configText = fs.readFile(configPath);
|
|
1767
|
+
const { config, error } = ts.parseConfigFileTextToJson(configPath, configText);
|
|
1768
|
+
if (error) {
|
|
1769
|
+
console.warn("[typechecker] Error parsing tsconfig:", error.messageText);
|
|
1770
|
+
return getDefaultCompilerOptions();
|
|
1771
|
+
}
|
|
1772
|
+
const parseHost = {
|
|
1773
|
+
useCaseSensitiveFileNames: true,
|
|
1774
|
+
readDirectory: () => [],
|
|
1775
|
+
fileExists: (path) => fs.exists(normalizePath(path)),
|
|
1776
|
+
readFile: (path) => {
|
|
1777
|
+
try {
|
|
1778
|
+
return fs.readFile(normalizePath(path));
|
|
1779
|
+
} catch {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
const parsed = ts.parseJsonConfigFileContent(config, parseHost, "/", undefined, configPath);
|
|
1785
|
+
if (parsed.errors.length > 0) {
|
|
1786
|
+
console.warn("[typechecker] tsconfig parse errors:", parsed.errors.map((e) => e.messageText));
|
|
1787
|
+
}
|
|
1788
|
+
return {
|
|
1789
|
+
...parsed.options,
|
|
1790
|
+
noEmit: true
|
|
1791
|
+
};
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
console.warn("[typechecker] Error reading tsconfig:", err);
|
|
1794
|
+
return getDefaultCompilerOptions();
|
|
2468
1795
|
}
|
|
2469
|
-
return defaultResourcesPromise;
|
|
2470
1796
|
}
|
|
2471
|
-
function
|
|
2472
|
-
|
|
2473
|
-
|
|
1797
|
+
function categoryToSeverity(category) {
|
|
1798
|
+
switch (category) {
|
|
1799
|
+
case ts.DiagnosticCategory.Error:
|
|
1800
|
+
return "error";
|
|
1801
|
+
case ts.DiagnosticCategory.Warning:
|
|
1802
|
+
return "warning";
|
|
1803
|
+
default:
|
|
1804
|
+
return "info";
|
|
1805
|
+
}
|
|
2474
1806
|
}
|
|
2475
|
-
function
|
|
2476
|
-
|
|
1807
|
+
function convertDiagnostic(diag) {
|
|
1808
|
+
let file;
|
|
1809
|
+
let line;
|
|
1810
|
+
let column;
|
|
1811
|
+
if (diag.file && diag.start !== undefined) {
|
|
1812
|
+
file = diag.file.fileName;
|
|
1813
|
+
const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
|
|
1814
|
+
line = pos.line + 1;
|
|
1815
|
+
column = pos.character + 1;
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
file,
|
|
1819
|
+
line,
|
|
1820
|
+
column,
|
|
1821
|
+
message: ts.flattenDiagnosticMessageText(diag.messageText, `
|
|
1822
|
+
`),
|
|
1823
|
+
severity: categoryToSeverity(diag.category)
|
|
1824
|
+
};
|
|
2477
1825
|
}
|
|
2478
1826
|
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
1827
|
+
class Typechecker {
|
|
1828
|
+
options;
|
|
1829
|
+
libCache = new Map;
|
|
1830
|
+
initPromise = null;
|
|
1831
|
+
constructor(options = {}) {
|
|
1832
|
+
this.options = options;
|
|
1833
|
+
}
|
|
1834
|
+
async initialize() {
|
|
1835
|
+
if (this.initPromise) {
|
|
1836
|
+
await this.initPromise;
|
|
1837
|
+
return;
|
|
2489
1838
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
this.
|
|
2494
|
-
|
|
2495
|
-
|
|
1839
|
+
if (this.libCache.size > 0) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
this.initPromise = this.fetchLibs();
|
|
1843
|
+
await this.initPromise;
|
|
1844
|
+
}
|
|
1845
|
+
async fetchLibs() {
|
|
1846
|
+
const libs = this.options.libs ?? DEFAULT_LIBS;
|
|
1847
|
+
const baseUrl = this.options.libsBaseUrl ?? DEFAULT_CDN_BASE;
|
|
1848
|
+
console.log(`[typechecker] Fetching TypeScript libs: ${libs.join(", ")}...`);
|
|
1849
|
+
const fetched = await fetchAllLibs(libs, baseUrl);
|
|
1850
|
+
console.log(`[typechecker] Fetched ${fetched.size} lib files`);
|
|
1851
|
+
this.libCache = fetched;
|
|
1852
|
+
}
|
|
1853
|
+
async typecheck(options) {
|
|
1854
|
+
await this.initialize();
|
|
1855
|
+
const { fs, entryPoint, tsconfigPath = "/tsconfig.json" } = options;
|
|
1856
|
+
const normalizedEntry = normalizePath(entryPoint);
|
|
1857
|
+
if (!fs.exists(normalizedEntry)) {
|
|
1858
|
+
return {
|
|
1859
|
+
success: false,
|
|
1860
|
+
diagnostics: [
|
|
1861
|
+
{
|
|
1862
|
+
file: normalizedEntry,
|
|
1863
|
+
message: `Entry point not found: ${normalizedEntry}`,
|
|
1864
|
+
severity: "error"
|
|
1865
|
+
}
|
|
1866
|
+
]
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
const compilerOptions = parseTsConfig(fs, tsconfigPath);
|
|
1870
|
+
const host = createCompilerHost(fs, this.libCache, compilerOptions);
|
|
1871
|
+
const program = ts.createProgram([normalizedEntry], compilerOptions, host);
|
|
1872
|
+
const allDiagnostics = [
|
|
1873
|
+
...program.getSyntacticDiagnostics(),
|
|
1874
|
+
...program.getSemanticDiagnostics(),
|
|
1875
|
+
...program.getDeclarationDiagnostics()
|
|
1876
|
+
];
|
|
1877
|
+
const diagnostics = allDiagnostics.map(convertDiagnostic);
|
|
1878
|
+
const success = !diagnostics.some((d) => d.severity === "error");
|
|
1879
|
+
return {
|
|
1880
|
+
success,
|
|
1881
|
+
diagnostics
|
|
2496
1882
|
};
|
|
2497
1883
|
}
|
|
2498
1884
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
onBuild: onBuildCallback,
|
|
2508
|
-
customCommands = [],
|
|
2509
|
-
sharedModules,
|
|
2510
|
-
bashOptions = {}
|
|
2511
|
-
} = options;
|
|
2512
|
-
const fs = Filesystem.create({
|
|
2513
|
-
initialFiles,
|
|
2514
|
-
maxSizeBytes: maxFilesystemSize
|
|
2515
|
-
});
|
|
2516
|
-
const resourcesPromise = providedResources ? Promise.resolve(providedResources) : getDefaultResources();
|
|
2517
|
-
const bundlerPromise = initBundler();
|
|
2518
|
-
const [resources] = await Promise.all([resourcesPromise, bundlerPromise]);
|
|
2519
|
-
const libFiles = resources.libFiles;
|
|
2520
|
-
const typesCache = resources.typesCache;
|
|
2521
|
-
if (sharedModules && sharedModules.length > 0) {
|
|
2522
|
-
const basePackages = new Set;
|
|
2523
|
-
for (const moduleId of sharedModules) {
|
|
2524
|
-
const { packageName } = parseImportPath(moduleId);
|
|
2525
|
-
basePackages.add(packageName);
|
|
2526
|
-
}
|
|
2527
|
-
await Promise.all(Array.from(basePackages).map(async (packageName) => {
|
|
2528
|
-
try {
|
|
2529
|
-
await installPackage(fs, packageName, { cache: typesCache });
|
|
2530
|
-
} catch (err) {
|
|
2531
|
-
console.warn(`[sandlot] Failed to install types for shared module "${packageName}":`, err);
|
|
2532
|
-
}
|
|
2533
|
-
}));
|
|
1885
|
+
function createTypechecker(options) {
|
|
1886
|
+
return new Typechecker(options);
|
|
1887
|
+
}
|
|
1888
|
+
// src/core/esm-types-resolver.ts
|
|
1889
|
+
class InMemoryTypesCache {
|
|
1890
|
+
cache = new Map;
|
|
1891
|
+
async get(key) {
|
|
1892
|
+
return this.cache.get(key) ?? null;
|
|
2534
1893
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
1894
|
+
async set(key, value) {
|
|
1895
|
+
this.cache.set(key, value);
|
|
1896
|
+
}
|
|
1897
|
+
async has(key) {
|
|
1898
|
+
return this.cache.has(key);
|
|
1899
|
+
}
|
|
1900
|
+
clear() {
|
|
1901
|
+
this.cache.clear();
|
|
2542
1902
|
}
|
|
2543
|
-
let validationFn = null;
|
|
2544
|
-
const commandDeps = {
|
|
2545
|
-
fs,
|
|
2546
|
-
libFiles,
|
|
2547
|
-
tsconfigPath,
|
|
2548
|
-
onBuild: buildEmitter.emit,
|
|
2549
|
-
getValidation: () => validationFn,
|
|
2550
|
-
typesCache,
|
|
2551
|
-
sharedModules
|
|
2552
|
-
};
|
|
2553
|
-
const defaultCommands = createDefaultCommands(commandDeps);
|
|
2554
|
-
const bash = new Bash({
|
|
2555
|
-
...bashOptions,
|
|
2556
|
-
cwd: "/",
|
|
2557
|
-
fs,
|
|
2558
|
-
customCommands: [...defaultCommands, ...customCommands]
|
|
2559
|
-
});
|
|
2560
|
-
return {
|
|
2561
|
-
fs,
|
|
2562
|
-
bash,
|
|
2563
|
-
get lastBuild() {
|
|
2564
|
-
return lastBuild;
|
|
2565
|
-
},
|
|
2566
|
-
getState: () => ({ files: fs.getFiles() }),
|
|
2567
|
-
onBuild: (callback) => buildEmitter.on(callback),
|
|
2568
|
-
setValidation: (fn) => {
|
|
2569
|
-
validationFn = fn;
|
|
2570
|
-
},
|
|
2571
|
-
clearValidation: () => {
|
|
2572
|
-
validationFn = null;
|
|
2573
|
-
}
|
|
2574
|
-
};
|
|
2575
1903
|
}
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
const
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
1904
|
+
|
|
1905
|
+
class EsmTypesResolver {
|
|
1906
|
+
baseUrl;
|
|
1907
|
+
cache;
|
|
1908
|
+
tryTypesPackages;
|
|
1909
|
+
constructor(options = {}) {
|
|
1910
|
+
this.baseUrl = options.baseUrl ?? "https://esm.sh";
|
|
1911
|
+
this.cache = options.cache ?? null;
|
|
1912
|
+
this.tryTypesPackages = options.tryTypesPackages ?? true;
|
|
1913
|
+
}
|
|
1914
|
+
async resolveTypes(specifier, version) {
|
|
1915
|
+
const resolved = await this.resolve(specifier, version);
|
|
1916
|
+
if (!resolved) {
|
|
1917
|
+
return {};
|
|
1918
|
+
}
|
|
1919
|
+
const result = {};
|
|
1920
|
+
const pkgPath = `/node_modules/${resolved.packageName}`;
|
|
1921
|
+
for (const [relativePath, content] of Object.entries(resolved.files)) {
|
|
1922
|
+
result[`${pkgPath}/${relativePath}`] = content;
|
|
1923
|
+
}
|
|
1924
|
+
return result;
|
|
1925
|
+
}
|
|
1926
|
+
async resolve(specifier, version) {
|
|
1927
|
+
const { packageName, subpath } = parseSpecifier(specifier);
|
|
1928
|
+
const cacheKey = makeCacheKey(packageName, subpath, version);
|
|
1929
|
+
if (this.cache) {
|
|
1930
|
+
const cached = await this.cache.get(cacheKey);
|
|
1931
|
+
if (cached) {
|
|
1932
|
+
return cached;
|
|
2596
1933
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
1934
|
+
}
|
|
1935
|
+
let result = await this.tryResolve(packageName, subpath, version);
|
|
1936
|
+
if (!result && this.tryTypesPackages && !packageName.startsWith("@types/")) {
|
|
1937
|
+
const typesPackageName = toTypesPackageName(packageName);
|
|
1938
|
+
result = await this.tryResolve(typesPackageName, subpath, version);
|
|
1939
|
+
if (result) {
|
|
1940
|
+
result.fromTypesPackage = true;
|
|
1941
|
+
result.packageName = packageName;
|
|
2605
1942
|
}
|
|
2606
1943
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
1944
|
+
if (result && this.cache) {
|
|
1945
|
+
await this.cache.set(cacheKey, result);
|
|
1946
|
+
}
|
|
1947
|
+
return result;
|
|
1948
|
+
}
|
|
1949
|
+
async tryResolve(packageName, subpath, version) {
|
|
2609
1950
|
try {
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
reject(err);
|
|
2617
|
-
}, { once: true });
|
|
2618
|
-
if (abortController.signal.aborted) {
|
|
2619
|
-
const err = abortController.signal.reason instanceof Error ? abortController.signal.reason : new Error("Build aborted");
|
|
2620
|
-
err.name = "AbortError";
|
|
2621
|
-
reject(err);
|
|
2622
|
-
}
|
|
2623
|
-
});
|
|
2624
|
-
result = await Promise.race([buildPromise, abortPromise]);
|
|
2625
|
-
} else {
|
|
2626
|
-
result = await buildPromise;
|
|
1951
|
+
const versionSuffix = version ? `@${version}` : "";
|
|
1952
|
+
const pathSuffix = subpath ? `/${subpath}` : "";
|
|
1953
|
+
const url = `${this.baseUrl}/${packageName}${versionSuffix}${pathSuffix}`;
|
|
1954
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
1955
|
+
if (!response.ok) {
|
|
1956
|
+
return null;
|
|
2627
1957
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
clearTimeout(timeoutId);
|
|
1958
|
+
const resolvedVersion = this.extractVersion(response, packageName, version);
|
|
1959
|
+
const typesHeader = response.headers.get("X-TypeScript-Types");
|
|
1960
|
+
if (!typesHeader) {
|
|
1961
|
+
return null;
|
|
2633
1962
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
1963
|
+
const typesUrl = new URL(typesHeader, response.url).href;
|
|
1964
|
+
const files = await this.fetchTypesRecursively(typesUrl, subpath);
|
|
1965
|
+
if (Object.keys(files).length === 0) {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
return {
|
|
1969
|
+
packageName,
|
|
1970
|
+
version: resolvedVersion,
|
|
1971
|
+
files,
|
|
1972
|
+
fromTypesPackage: packageName.startsWith("@types/")
|
|
1973
|
+
};
|
|
1974
|
+
} catch {
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
async fetchTypesRecursively(entryUrl, subpath, visited = new Set) {
|
|
1979
|
+
if (visited.has(entryUrl)) {
|
|
1980
|
+
return {};
|
|
1981
|
+
}
|
|
1982
|
+
visited.add(entryUrl);
|
|
1983
|
+
const response = await fetch(entryUrl);
|
|
1984
|
+
if (!response.ok) {
|
|
1985
|
+
return {};
|
|
1986
|
+
}
|
|
1987
|
+
const content = await response.text();
|
|
1988
|
+
const files = {};
|
|
1989
|
+
const fileName = subpath ? `${subpath}.d.ts` : "index.d.ts";
|
|
1990
|
+
files[fileName] = content;
|
|
1991
|
+
if (subpath) {
|
|
1992
|
+
files[`${subpath}/index.d.ts`] = content;
|
|
1993
|
+
}
|
|
1994
|
+
const refs = parseReferences(content);
|
|
1995
|
+
for (const ref of refs.paths) {
|
|
1996
|
+
const refUrl = new URL(ref, entryUrl).href;
|
|
1997
|
+
const refFiles = await this.fetchTypesRecursively(refUrl, undefined, visited);
|
|
1998
|
+
for (const [refPath, refContent] of Object.entries(refFiles)) {
|
|
1999
|
+
const normalizedRef = ref.replace(/^\.\//, "");
|
|
2000
|
+
if (refPath === "index.d.ts") {
|
|
2001
|
+
files[normalizedRef] = refContent;
|
|
2002
|
+
} else {
|
|
2003
|
+
const dir = normalizedRef.replace(/\.d\.ts$/, "");
|
|
2004
|
+
files[`${dir}/${refPath}`] = refContent;
|
|
2005
|
+
}
|
|
2637
2006
|
}
|
|
2638
2007
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2008
|
+
return files;
|
|
2009
|
+
}
|
|
2010
|
+
extractVersion(response, packageName, requestedVersion) {
|
|
2011
|
+
const esmId = response.headers.get("x-esm-id");
|
|
2012
|
+
if (esmId) {
|
|
2013
|
+
const match = esmId.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
|
|
2014
|
+
if (match?.[1]) {
|
|
2015
|
+
return match[1];
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
const urlMatch = response.url.match(new RegExp(`${escapeRegex(packageName)}@([^/]+)`));
|
|
2019
|
+
if (urlMatch?.[1]) {
|
|
2020
|
+
return urlMatch[1];
|
|
2021
|
+
}
|
|
2022
|
+
return requestedVersion ?? "latest";
|
|
2023
|
+
}
|
|
2648
2024
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2025
|
+
function parseSpecifier(specifier) {
|
|
2026
|
+
if (specifier.startsWith("@")) {
|
|
2027
|
+
const parts = specifier.split("/");
|
|
2028
|
+
if (parts.length >= 2) {
|
|
2029
|
+
const packageName = `${parts[0]}/${parts[1]}`;
|
|
2030
|
+
const subpath = parts.length > 2 ? parts.slice(2).join("/") : undefined;
|
|
2031
|
+
return { packageName, subpath };
|
|
2032
|
+
}
|
|
2033
|
+
return { packageName: specifier, subpath: undefined };
|
|
2034
|
+
}
|
|
2035
|
+
const slashIndex = specifier.indexOf("/");
|
|
2036
|
+
if (slashIndex === -1) {
|
|
2037
|
+
return { packageName: specifier, subpath: undefined };
|
|
2038
|
+
}
|
|
2039
|
+
return {
|
|
2040
|
+
packageName: specifier.slice(0, slashIndex),
|
|
2041
|
+
subpath: specifier.slice(slashIndex + 1)
|
|
2659
2042
|
};
|
|
2660
2043
|
}
|
|
2044
|
+
function toTypesPackageName(packageName) {
|
|
2045
|
+
if (packageName.startsWith("@")) {
|
|
2046
|
+
return "@types/" + packageName.slice(1).replace("/", "__");
|
|
2047
|
+
}
|
|
2048
|
+
return `@types/${packageName}`;
|
|
2049
|
+
}
|
|
2050
|
+
function parseReferences(content) {
|
|
2051
|
+
const paths = [];
|
|
2052
|
+
const types = [];
|
|
2053
|
+
const pathRegex = /\/\/\/\s*<reference\s+path="([^"]+)"\s*\/>/g;
|
|
2054
|
+
let match;
|
|
2055
|
+
while ((match = pathRegex.exec(content)) !== null) {
|
|
2056
|
+
if (match[1])
|
|
2057
|
+
paths.push(match[1]);
|
|
2058
|
+
}
|
|
2059
|
+
const typesRegex = /\/\/\/\s*<reference\s+types="([^"]+)"\s*\/>/g;
|
|
2060
|
+
while ((match = typesRegex.exec(content)) !== null) {
|
|
2061
|
+
if (match[1])
|
|
2062
|
+
types.push(match[1]);
|
|
2063
|
+
}
|
|
2064
|
+
return { paths, types };
|
|
2065
|
+
}
|
|
2066
|
+
function makeCacheKey(packageName, subpath, version) {
|
|
2067
|
+
const base = version ? `${packageName}@${version}` : packageName;
|
|
2068
|
+
return subpath ? `${base}/${subpath}` : base;
|
|
2069
|
+
}
|
|
2070
|
+
function escapeRegex(str) {
|
|
2071
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2072
|
+
}
|
|
2661
2073
|
export {
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
typecheck,
|
|
2665
|
-
registerSharedModules,
|
|
2666
|
-
loadModule,
|
|
2667
|
-
loadExport,
|
|
2668
|
-
loadDefault,
|
|
2669
|
-
listPackages,
|
|
2670
|
-
installPackage,
|
|
2671
|
-
initBundler,
|
|
2672
|
-
hasExport,
|
|
2673
|
-
hasDefaultResources,
|
|
2674
|
-
getPackageManifest,
|
|
2675
|
-
getExportNames,
|
|
2676
|
-
getDefaultResources,
|
|
2677
|
-
getDefaultBrowserLibs,
|
|
2678
|
-
formatDiagnosticsForAgent,
|
|
2074
|
+
wrapFilesystemForJustBash,
|
|
2075
|
+
formatSize,
|
|
2679
2076
|
formatDiagnostics,
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
createRunCommand,
|
|
2686
|
-
createListCommand,
|
|
2687
|
-
createInstallCommand,
|
|
2077
|
+
formatBundleErrors,
|
|
2078
|
+
createTypechecker,
|
|
2079
|
+
createSharedModuleRegistry,
|
|
2080
|
+
createSandlotCommand,
|
|
2081
|
+
createSandlot,
|
|
2688
2082
|
createFilesystem,
|
|
2689
2083
|
createDefaultCommands,
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
clearDefaultResources,
|
|
2694
|
-
bundleToUrl,
|
|
2695
|
-
bundleAndImport,
|
|
2696
|
-
bundle,
|
|
2697
|
-
ModuleLoadError,
|
|
2084
|
+
Typechecker,
|
|
2085
|
+
SharedModuleRegistry,
|
|
2086
|
+
InMemoryTypesCache,
|
|
2698
2087
|
Filesystem,
|
|
2699
|
-
|
|
2088
|
+
EsmTypesResolver
|
|
2700
2089
|
};
|