sandlot 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/browser/bundler.d.ts +68 -0
  2. package/dist/browser/bundler.d.ts.map +1 -0
  3. package/dist/browser/executor.d.ts +46 -0
  4. package/dist/browser/executor.d.ts.map +1 -0
  5. package/dist/browser/index.d.ts +9 -0
  6. package/dist/browser/index.d.ts.map +1 -0
  7. package/dist/browser/index.js +2692 -0
  8. package/dist/browser/preset.d.ts +63 -0
  9. package/dist/browser/preset.d.ts.map +1 -0
  10. package/dist/commands/index.d.ts +20 -11
  11. package/dist/commands/index.d.ts.map +1 -1
  12. package/dist/commands/types.d.ts +31 -132
  13. package/dist/commands/types.d.ts.map +1 -1
  14. package/dist/core/bundler-utils.d.ts +142 -0
  15. package/dist/core/bundler-utils.d.ts.map +1 -0
  16. package/dist/core/esm-types-resolver.d.ts +125 -0
  17. package/dist/core/esm-types-resolver.d.ts.map +1 -0
  18. package/dist/core/executor.d.ts +35 -0
  19. package/dist/core/executor.d.ts.map +1 -0
  20. package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
  21. package/dist/core/fs.d.ts.map +1 -0
  22. package/dist/core/sandbox.d.ts +30 -0
  23. package/dist/core/sandbox.d.ts.map +1 -0
  24. package/dist/core/sandlot.d.ts +30 -0
  25. package/dist/core/sandlot.d.ts.map +1 -0
  26. package/dist/core/shared-module-registry.d.ts +46 -0
  27. package/dist/core/shared-module-registry.d.ts.map +1 -0
  28. package/dist/core/typechecker.d.ts +60 -0
  29. package/dist/core/typechecker.d.ts.map +1 -0
  30. package/dist/index.d.ts +11 -16
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1399 -2010
  33. package/dist/node/bundler.d.ts +48 -0
  34. package/dist/node/bundler.d.ts.map +1 -0
  35. package/dist/node/executor.d.ts +48 -0
  36. package/dist/node/executor.d.ts.map +1 -0
  37. package/dist/node/index.d.ts +9 -0
  38. package/dist/node/index.d.ts.map +1 -0
  39. package/dist/node/index.js +2646 -0
  40. package/dist/node/preset.d.ts +62 -0
  41. package/dist/node/preset.d.ts.map +1 -0
  42. package/dist/types.d.ts +525 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +16 -6
  45. package/src/browser/bundler.ts +294 -0
  46. package/src/browser/executor.ts +71 -0
  47. package/src/browser/index.ts +57 -0
  48. package/src/browser/preset.ts +179 -0
  49. package/src/commands/index.ts +526 -43
  50. package/src/commands/types.ts +82 -146
  51. package/src/core/bundler-utils.ts +630 -0
  52. package/src/core/esm-types-resolver.ts +432 -0
  53. package/src/core/executor.ts +161 -0
  54. package/src/{fs.ts → core/fs.ts} +59 -37
  55. package/src/core/sandbox.ts +621 -0
  56. package/src/core/sandlot.ts +77 -0
  57. package/src/core/shared-module-registry.ts +138 -0
  58. package/src/core/typechecker.ts +607 -0
  59. package/src/index.ts +104 -139
  60. package/src/node/bundler.ts +194 -0
  61. package/src/node/executor.ts +87 -0
  62. package/src/node/index.ts +39 -0
  63. package/src/node/preset.ts +178 -0
  64. package/src/types.ts +668 -0
  65. package/README.md +0 -243
  66. package/dist/build-emitter.d.ts +0 -47
  67. package/dist/build-emitter.d.ts.map +0 -1
  68. package/dist/builder.d.ts +0 -370
  69. package/dist/builder.d.ts.map +0 -1
  70. package/dist/bundler.d.ts +0 -152
  71. package/dist/bundler.d.ts.map +0 -1
  72. package/dist/commands/compile.d.ts +0 -13
  73. package/dist/commands/compile.d.ts.map +0 -1
  74. package/dist/commands/packages.d.ts +0 -17
  75. package/dist/commands/packages.d.ts.map +0 -1
  76. package/dist/commands/run.d.ts +0 -40
  77. package/dist/commands/run.d.ts.map +0 -1
  78. package/dist/commands.d.ts +0 -179
  79. package/dist/commands.d.ts.map +0 -1
  80. package/dist/fs.d.ts.map +0 -1
  81. package/dist/internal.d.ts +0 -79
  82. package/dist/internal.d.ts.map +0 -1
  83. package/dist/internal.js +0 -1942
  84. package/dist/loader.d.ts +0 -164
  85. package/dist/loader.d.ts.map +0 -1
  86. package/dist/packages.d.ts +0 -199
  87. package/dist/packages.d.ts.map +0 -1
  88. package/dist/runner.d.ts +0 -314
  89. package/dist/runner.d.ts.map +0 -1
  90. package/dist/sandbox-manager.d.ts +0 -261
  91. package/dist/sandbox-manager.d.ts.map +0 -1
  92. package/dist/sandbox.d.ts +0 -267
  93. package/dist/sandbox.d.ts.map +0 -1
  94. package/dist/shared-modules.d.ts +0 -148
  95. package/dist/shared-modules.d.ts.map +0 -1
  96. package/dist/shared-resources.d.ts +0 -102
  97. package/dist/shared-resources.d.ts.map +0 -1
  98. package/dist/ts-libs.d.ts +0 -85
  99. package/dist/ts-libs.d.ts.map +0 -1
  100. package/dist/typechecker.d.ts +0 -127
  101. package/dist/typechecker.d.ts.map +0 -1
  102. package/src/build-emitter.ts +0 -64
  103. package/src/builder.ts +0 -498
  104. package/src/bundler.ts +0 -575
  105. package/src/commands/compile.ts +0 -236
  106. package/src/commands/packages.ts +0 -154
  107. package/src/commands/run.ts +0 -245
  108. package/src/internal.ts +0 -119
  109. package/src/loader.ts +0 -229
  110. package/src/packages.ts +0 -936
  111. package/src/sandbox.ts +0 -398
  112. package/src/shared-modules.ts +0 -280
  113. package/src/shared-resources.ts +0 -166
  114. package/src/ts-libs.ts +0 -218
  115. package/src/typechecker.ts +0 -635
@@ -1,635 +0,0 @@
1
- import type { IFileSystem } from "just-bash/browser";
2
- import ts from "typescript";
3
-
4
- /**
5
- * Options for type checking
6
- */
7
- export interface TypecheckOptions {
8
- /**
9
- * The virtual filesystem to read source files from
10
- */
11
- fs: IFileSystem;
12
-
13
- /**
14
- * Entry point path (absolute path in the virtual filesystem).
15
- * TypeScript will discover all imported files from this root.
16
- */
17
- entryPoint: string;
18
-
19
- /**
20
- * Path to tsconfig.json in the virtual filesystem.
21
- * Default: "/tsconfig.json"
22
- */
23
- tsconfigPath?: string;
24
-
25
- /**
26
- * Pre-loaded TypeScript lib files (e.g., lib.dom.d.ts, lib.es2020.d.ts).
27
- * Map from lib name (e.g., "dom", "es2020") to file content.
28
- * If provided, enables proper type checking for built-in APIs.
29
- * Use fetchAndCacheLibs() from ts-libs to fetch these.
30
- */
31
- libFiles?: Map<string, string>;
32
- }
33
-
34
- /**
35
- * A single diagnostic message from type checking
36
- */
37
- export interface Diagnostic {
38
- /**
39
- * File path where the error occurred (null for global errors)
40
- */
41
- file: string | null;
42
-
43
- /**
44
- * 1-based line number (null if not applicable)
45
- */
46
- line: number | null;
47
-
48
- /**
49
- * 1-based column number (null if not applicable)
50
- */
51
- column: number | null;
52
-
53
- /**
54
- * TypeScript error code (e.g., 2322 for type mismatch)
55
- */
56
- code: number;
57
-
58
- /**
59
- * Severity category
60
- */
61
- category: "error" | "warning" | "suggestion" | "message";
62
-
63
- /**
64
- * Human-readable error message
65
- */
66
- message: string;
67
- }
68
-
69
- /**
70
- * Result of type checking
71
- */
72
- export interface TypecheckResult {
73
- /**
74
- * All diagnostics from type checking
75
- */
76
- diagnostics: Diagnostic[];
77
-
78
- /**
79
- * True if there are any errors (not just warnings)
80
- */
81
- hasErrors: boolean;
82
-
83
- /**
84
- * List of files that were checked
85
- */
86
- checkedFiles: string[];
87
- }
88
-
89
- /**
90
- * Map category enum to string
91
- */
92
- function categoryToString(
93
- category: ts.DiagnosticCategory
94
- ): "error" | "warning" | "suggestion" | "message" {
95
- switch (category) {
96
- case ts.DiagnosticCategory.Error:
97
- return "error";
98
- case ts.DiagnosticCategory.Warning:
99
- return "warning";
100
- case ts.DiagnosticCategory.Suggestion:
101
- return "suggestion";
102
- case ts.DiagnosticCategory.Message:
103
- return "message";
104
- default:
105
- return "error";
106
- }
107
- }
108
-
109
- /**
110
- * Convert TypeScript diagnostic to our Diagnostic format
111
- */
112
- function convertDiagnostic(diag: ts.Diagnostic): Diagnostic {
113
- let file: string | null = null;
114
- let line: number | null = null;
115
- let column: number | null = null;
116
-
117
- if (diag.file && diag.start !== undefined) {
118
- file = diag.file.fileName;
119
- const pos = diag.file.getLineAndCharacterOfPosition(diag.start);
120
- line = pos.line + 1; // Convert to 1-based
121
- column = pos.character + 1; // Convert to 1-based
122
- }
123
-
124
- return {
125
- file,
126
- line,
127
- column,
128
- code: diag.code,
129
- category: categoryToString(diag.category),
130
- message: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
131
- };
132
- }
133
-
134
- /**
135
- * Normalize path to absolute
136
- */
137
- function normalizePath(path: string): string {
138
- if (!path.startsWith("/")) {
139
- return "/" + path;
140
- }
141
- return path;
142
- }
143
-
144
- /**
145
- * Pre-load files from the async filesystem into a sync cache
146
- */
147
- async function preloadFiles(
148
- fs: IFileSystem
149
- ): Promise<Map<string, string>> {
150
- const cache = new Map<string, string>();
151
- const allPaths = fs.getAllPaths();
152
-
153
- for (const path of allPaths) {
154
- // Only cache TypeScript-related files
155
- if (
156
- path.endsWith(".ts") ||
157
- path.endsWith(".tsx") ||
158
- path.endsWith(".js") ||
159
- path.endsWith(".jsx") ||
160
- path.endsWith(".json") ||
161
- path.endsWith(".d.ts")
162
- ) {
163
- try {
164
- const stat = await fs.stat(path);
165
- if (stat.isFile) {
166
- const content = await fs.readFile(path);
167
- cache.set(path, content);
168
- }
169
- } catch {
170
- // Skip files that can't be read
171
- }
172
- }
173
- }
174
-
175
- return cache;
176
- }
177
-
178
- /**
179
- * Parse tsconfig.json and return compiler options
180
- *
181
- * @param configText - The content of tsconfig.json
182
- * @param _configPath - Path to the tsconfig (unused but kept for future reference resolution)
183
- */
184
- function parseTsConfig(
185
- configText: string,
186
- _configPath: string
187
- ): ts.CompilerOptions {
188
- try {
189
- const config = JSON.parse(configText);
190
- const compilerOptions = config.compilerOptions || {};
191
-
192
- // Map string values to TypeScript enums
193
- const options: ts.CompilerOptions = {
194
- ...getDefaultCompilerOptions(),
195
- };
196
-
197
- // Target
198
- if (compilerOptions.target) {
199
- const targetMap: Record<string, ts.ScriptTarget> = {
200
- es5: ts.ScriptTarget.ES5,
201
- es6: ts.ScriptTarget.ES2015,
202
- es2015: ts.ScriptTarget.ES2015,
203
- es2016: ts.ScriptTarget.ES2016,
204
- es2017: ts.ScriptTarget.ES2017,
205
- es2018: ts.ScriptTarget.ES2018,
206
- es2019: ts.ScriptTarget.ES2019,
207
- es2020: ts.ScriptTarget.ES2020,
208
- es2021: ts.ScriptTarget.ES2021,
209
- es2022: ts.ScriptTarget.ES2022,
210
- esnext: ts.ScriptTarget.ESNext,
211
- };
212
- options.target = targetMap[compilerOptions.target.toLowerCase()] ?? ts.ScriptTarget.ES2020;
213
- }
214
-
215
- // Module
216
- if (compilerOptions.module) {
217
- const moduleMap: Record<string, ts.ModuleKind> = {
218
- commonjs: ts.ModuleKind.CommonJS,
219
- amd: ts.ModuleKind.AMD,
220
- umd: ts.ModuleKind.UMD,
221
- system: ts.ModuleKind.System,
222
- es6: ts.ModuleKind.ES2015,
223
- es2015: ts.ModuleKind.ES2015,
224
- es2020: ts.ModuleKind.ES2020,
225
- es2022: ts.ModuleKind.ES2022,
226
- esnext: ts.ModuleKind.ESNext,
227
- node16: ts.ModuleKind.Node16,
228
- nodenext: ts.ModuleKind.NodeNext,
229
- };
230
- options.module = moduleMap[compilerOptions.module.toLowerCase()] ?? ts.ModuleKind.ESNext;
231
- }
232
-
233
- // Module resolution
234
- if (compilerOptions.moduleResolution) {
235
- const resolutionMap: Record<string, ts.ModuleResolutionKind> = {
236
- classic: ts.ModuleResolutionKind.Classic,
237
- node: ts.ModuleResolutionKind.NodeJs,
238
- node10: ts.ModuleResolutionKind.NodeJs,
239
- node16: ts.ModuleResolutionKind.Node16,
240
- nodenext: ts.ModuleResolutionKind.NodeNext,
241
- bundler: ts.ModuleResolutionKind.Bundler,
242
- };
243
- options.moduleResolution =
244
- resolutionMap[compilerOptions.moduleResolution.toLowerCase()] ?? ts.ModuleResolutionKind.Bundler;
245
- }
246
-
247
- // JSX
248
- if (compilerOptions.jsx) {
249
- const jsxMap: Record<string, ts.JsxEmit> = {
250
- preserve: ts.JsxEmit.Preserve,
251
- react: ts.JsxEmit.React,
252
- "react-jsx": ts.JsxEmit.ReactJSX,
253
- "react-jsxdev": ts.JsxEmit.ReactJSXDev,
254
- "react-native": ts.JsxEmit.ReactNative,
255
- };
256
- options.jsx = jsxMap[compilerOptions.jsx.toLowerCase()] ?? ts.JsxEmit.ReactJSX;
257
- }
258
-
259
- // Boolean options
260
- if (compilerOptions.strict !== undefined) options.strict = compilerOptions.strict;
261
- if (compilerOptions.esModuleInterop !== undefined) options.esModuleInterop = compilerOptions.esModuleInterop;
262
- if (compilerOptions.skipLibCheck !== undefined) options.skipLibCheck = compilerOptions.skipLibCheck;
263
- if (compilerOptions.allowJs !== undefined) options.allowJs = compilerOptions.allowJs;
264
- if (compilerOptions.resolveJsonModule !== undefined) options.resolveJsonModule = compilerOptions.resolveJsonModule;
265
- if (compilerOptions.noImplicitAny !== undefined) options.noImplicitAny = compilerOptions.noImplicitAny;
266
- if (compilerOptions.strictNullChecks !== undefined) options.strictNullChecks = compilerOptions.strictNullChecks;
267
- // Note: noLib is intentionally not configurable - we always use our fetched lib files
268
-
269
- // Lib array (e.g., ["ES2020", "DOM"])
270
- if (Array.isArray(compilerOptions.lib)) {
271
- options.lib = compilerOptions.lib.map((lib: string) =>
272
- lib.toLowerCase().startsWith("lib.") ? lib : `lib.${lib.toLowerCase()}.d.ts`
273
- );
274
- }
275
-
276
- // Always ensure noEmit for type checking
277
- options.noEmit = true;
278
-
279
- return options;
280
- } catch (err) {
281
- console.warn("Error parsing tsconfig.json:", err);
282
- return getDefaultCompilerOptions();
283
- }
284
- }
285
-
286
- /**
287
- * Get default compiler options for type checking
288
- */
289
- function getDefaultCompilerOptions(): ts.CompilerOptions {
290
- return {
291
- target: ts.ScriptTarget.ES2020,
292
- module: ts.ModuleKind.ESNext,
293
- moduleResolution: ts.ModuleResolutionKind.Bundler,
294
- esModuleInterop: true,
295
- strict: true,
296
- skipLibCheck: true, // Skip type-checking lib files for performance
297
- noEmit: true, // We only want type checking, no output
298
- jsx: ts.JsxEmit.ReactJSX,
299
- allowJs: true,
300
- resolveJsonModule: true,
301
- noLib: false, // We provide lib files via the compiler host
302
- lib: [
303
- "lib.es2020.d.ts",
304
- "lib.dom.d.ts",
305
- "lib.dom.iterable.d.ts",
306
- ],
307
- };
308
- }
309
-
310
- /**
311
- * The virtual path where lib files are stored in the cache
312
- */
313
- const LIB_PATH_PREFIX = "/node_modules/typescript/lib/";
314
-
315
- /**
316
- * Create a custom compiler host that reads from the file cache
317
- * Note: We don't use ts.createCompilerHost as it requires Node.js fs module
318
- *
319
- * @param fileCache - Map of file paths to content (includes both user files and lib files)
320
- * @param libFiles - Map of lib names to content (e.g., "dom" -> content of lib.dom.d.ts)
321
- * @param _options - Compiler options (unused but kept for potential future use)
322
- */
323
- function createVfsCompilerHost(
324
- fileCache: Map<string, string>,
325
- libFiles: Map<string, string>,
326
- _options: ts.CompilerOptions
327
- ): ts.CompilerHost {
328
- /**
329
- * Try to get content for a lib file request.
330
- * TypeScript may request libs by full path or just filename.
331
- */
332
- function getLibContent(fileName: string): string | undefined {
333
- // Extract lib name from various path formats:
334
- // - "/node_modules/typescript/lib/lib.dom.d.ts" -> "dom"
335
- // - "lib.dom.d.ts" -> "dom"
336
- const libMatch = fileName.match(/lib\.([^/]+)\.d\.ts$/);
337
- if (libMatch && libMatch[1]) {
338
- return libFiles.get(libMatch[1]);
339
- }
340
- return undefined;
341
- }
342
-
343
- return {
344
- getSourceFile(
345
- fileName: string,
346
- languageVersion: ts.ScriptTarget,
347
- onError?: (message: string) => void
348
- ): ts.SourceFile | undefined {
349
- // First, try the file cache (user files)
350
- const normalizedPath = normalizePath(fileName);
351
- const content = fileCache.get(normalizedPath);
352
-
353
- if (content !== undefined) {
354
- return ts.createSourceFile(normalizedPath, content, languageVersion, true);
355
- }
356
-
357
- // Try without leading slash
358
- const altContent = fileCache.get(fileName);
359
- if (altContent !== undefined) {
360
- return ts.createSourceFile(fileName, altContent, languageVersion, true);
361
- }
362
-
363
- // Try lib files
364
- const libContent = getLibContent(fileName);
365
- if (libContent !== undefined) {
366
- return ts.createSourceFile(fileName, libContent, languageVersion, true);
367
- }
368
-
369
- // File not found
370
- if (onError) {
371
- onError(`File not found: ${fileName}`);
372
- }
373
- return undefined;
374
- },
375
-
376
- getDefaultLibFileName(options: ts.CompilerOptions): string {
377
- return LIB_PATH_PREFIX + ts.getDefaultLibFileName(options);
378
- },
379
-
380
- writeFile(): void {
381
- // No-op: we don't emit files
382
- },
383
-
384
- getCurrentDirectory(): string {
385
- return "/";
386
- },
387
-
388
- getCanonicalFileName(fileName: string): string {
389
- return fileName;
390
- },
391
-
392
- useCaseSensitiveFileNames(): boolean {
393
- return true;
394
- },
395
-
396
- getNewLine(): string {
397
- return "\n";
398
- },
399
-
400
- fileExists(fileName: string): boolean {
401
- const normalizedPath = normalizePath(fileName);
402
- if (fileCache.has(normalizedPath) || fileCache.has(fileName)) {
403
- return true;
404
- }
405
- // Check if it's a lib file
406
- return getLibContent(fileName) !== undefined;
407
- },
408
-
409
- readFile(fileName: string): string | undefined {
410
- const normalizedPath = normalizePath(fileName);
411
- const content = fileCache.get(normalizedPath) ?? fileCache.get(fileName);
412
- if (content !== undefined) {
413
- return content;
414
- }
415
- // Try lib files
416
- return getLibContent(fileName);
417
- },
418
-
419
- directoryExists(directoryName: string): boolean {
420
- const normalizedDir = normalizePath(directoryName);
421
-
422
- // Check user files (includes installed packages in node_modules)
423
- for (const path of fileCache.keys()) {
424
- if (path.startsWith(normalizedDir + "/")) {
425
- return true;
426
- }
427
- }
428
-
429
- // Check if it's a TypeScript lib directory and we have lib files
430
- if (
431
- normalizedDir === "/node_modules/typescript/lib" ||
432
- normalizedDir === "/node_modules/typescript"
433
- ) {
434
- return libFiles.size > 0;
435
- }
436
-
437
- // Check /node_modules - exists if we have lib files OR installed packages
438
- if (normalizedDir === "/node_modules") {
439
- return libFiles.size > 0 ||
440
- Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/"));
441
- }
442
-
443
- // Check @types directory specifically
444
- if (normalizedDir === "/node_modules/@types") {
445
- return Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/@types/"));
446
- }
447
-
448
- return false;
449
- },
450
-
451
- getDirectories(path: string): string[] {
452
- const normalizedPath = normalizePath(path);
453
- const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
454
- const dirs = new Set<string>();
455
-
456
- for (const filePath of fileCache.keys()) {
457
- if (filePath.startsWith(prefix)) {
458
- const relative = filePath.slice(prefix.length);
459
- const firstSlash = relative.indexOf("/");
460
- if (firstSlash > 0) {
461
- dirs.add(relative.slice(0, firstSlash));
462
- }
463
- }
464
- }
465
-
466
- // Add node_modules if we have lib files or packages and path is root
467
- if (normalizedPath === "/") {
468
- // Check if there are any files in node_modules (from packages or libs)
469
- const hasNodeModules = libFiles.size > 0 ||
470
- Array.from(fileCache.keys()).some(p => p.startsWith("/node_modules/"));
471
- if (hasNodeModules) {
472
- dirs.add("node_modules");
473
- }
474
- }
475
-
476
- return Array.from(dirs);
477
- },
478
-
479
- realpath(path: string): string {
480
- return path;
481
- },
482
-
483
- getEnvironmentVariable(): string | undefined {
484
- return undefined;
485
- },
486
- };
487
- }
488
-
489
- /**
490
- * Type check TypeScript files from a virtual filesystem
491
- *
492
- * @example
493
- * ```ts
494
- * const fs = Filesystem.create({
495
- * initialFiles: {
496
- * "/tsconfig.json": JSON.stringify({
497
- * compilerOptions: {
498
- * target: "ES2020",
499
- * module: "ESNext",
500
- * strict: true
501
- * }
502
- * }),
503
- * "/src/index.ts": `
504
- * const x: number = "hello"; // Type error!
505
- * export { x };
506
- * `,
507
- * }
508
- * });
509
- *
510
- * const result = await typecheck({
511
- * fs,
512
- * entryPoint: "/src/index.ts",
513
- * });
514
- *
515
- * console.log(result.hasErrors); // true
516
- * console.log(result.diagnostics[0].message); // Type 'string' is not assignable...
517
- * ```
518
- */
519
- export async function typecheck(options: TypecheckOptions): Promise<TypecheckResult> {
520
- const {
521
- fs,
522
- entryPoint,
523
- tsconfigPath = "/tsconfig.json",
524
- libFiles = new Map(),
525
- } = options;
526
-
527
- // Normalize entry point
528
- const normalizedEntry = normalizePath(entryPoint);
529
-
530
- // Verify entry point exists
531
- if (!(await fs.exists(normalizedEntry))) {
532
- throw new Error(`Entry point not found: ${normalizedEntry}`);
533
- }
534
-
535
- // Pre-load all files into sync cache
536
- const fileCache = await preloadFiles(fs);
537
-
538
- // Parse tsconfig if it exists
539
- let compilerOptions = getDefaultCompilerOptions();
540
- const tsconfigContent = fileCache.get(normalizePath(tsconfigPath));
541
- if (tsconfigContent) {
542
- compilerOptions = {
543
- ...parseTsConfig(tsconfigContent, tsconfigPath),
544
- noEmit: true, // Always ensure noEmit for type checking
545
- };
546
- }
547
-
548
- // Create compiler host with lib files
549
- const host = createVfsCompilerHost(fileCache, libFiles, compilerOptions);
550
-
551
- // Create program and get diagnostics
552
- const program = ts.createProgram([normalizedEntry], compilerOptions, host);
553
-
554
- // Collect all diagnostics
555
- const allDiagnostics = [
556
- ...program.getSyntacticDiagnostics(),
557
- ...program.getSemanticDiagnostics(),
558
- ...program.getDeclarationDiagnostics(),
559
- ];
560
-
561
- // Convert to our format
562
- const diagnostics = allDiagnostics.map(convertDiagnostic);
563
-
564
- // Get list of checked files
565
- const checkedFiles = program
566
- .getSourceFiles()
567
- .map((sf) => sf.fileName)
568
- .filter((f) => !f.includes("node_modules/typescript/lib"));
569
-
570
- // Check for errors
571
- const hasErrors = diagnostics.some((d) => d.category === "error");
572
-
573
- return {
574
- diagnostics,
575
- hasErrors,
576
- checkedFiles,
577
- };
578
- }
579
-
580
- /**
581
- * Format diagnostics as a human-readable string
582
- *
583
- * @example
584
- * ```ts
585
- * const result = await typecheck({ fs, entryPoint: "/src/index.ts" });
586
- * console.log(formatDiagnostics(result.diagnostics));
587
- * // /src/index.ts:3:7 - error TS2322: Type 'string' is not assignable to type 'number'.
588
- * ```
589
- */
590
- export function formatDiagnostics(diagnostics: Diagnostic[]): string {
591
- return diagnostics
592
- .map((d) => {
593
- const location = d.file
594
- ? `${d.file}${d.line ? `:${d.line}` : ""}${d.column ? `:${d.column}` : ""}`
595
- : "(global)";
596
- return `${location} - ${d.category} TS${d.code}: ${d.message}`;
597
- })
598
- .join("\n");
599
- }
600
-
601
- /**
602
- * Format diagnostics in a concise format suitable for AI agents
603
- *
604
- * @example
605
- * ```ts
606
- * const result = await typecheck({ fs, entryPoint: "/src/index.ts" });
607
- * console.log(formatDiagnosticsForAgent(result.diagnostics));
608
- * // Error in /src/index.ts at line 3: Type 'string' is not assignable to type 'number'. (TS2322)
609
- * ```
610
- */
611
- export function formatDiagnosticsForAgent(diagnostics: Diagnostic[]): string {
612
- if (diagnostics.length === 0) {
613
- return "No type errors found.";
614
- }
615
-
616
- const errorCount = diagnostics.filter((d) => d.category === "error").length;
617
- const warningCount = diagnostics.filter((d) => d.category === "warning").length;
618
-
619
- const summary =
620
- errorCount > 0 || warningCount > 0
621
- ? `Found ${errorCount} error(s) and ${warningCount} warning(s):\n\n`
622
- : "";
623
-
624
- const formatted = diagnostics
625
- .map((d) => {
626
- const location = d.file
627
- ? `${d.file}${d.line ? ` at line ${d.line}` : ""}`
628
- : "Global";
629
- const prefix = d.category === "error" ? "Error" : d.category === "warning" ? "Warning" : "Info";
630
- return `${prefix} in ${location}: ${d.message} (TS${d.code})`;
631
- })
632
- .join("\n\n");
633
-
634
- return summary + formatted;
635
- }