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