(mainFilePath, typescript)
| 65 | * @returns {Promise<{tempFile: string, allTempFiles: string[], fileMapping: any}>} - Main temp file and all temp files created |
| 66 | */ |
| 67 | export async function transpileTypeScript(mainFilePath, typescript) { |
| 68 | const { transpile } = typescript |
| 69 | |
| 70 | /** |
| 71 | * Transpile a single TypeScript file to JavaScript |
| 72 | * Injects CommonJS shims (require, module, exports, __dirname, __filename) as needed |
| 73 | */ |
| 74 | const transpileTS = (filePath) => { |
| 75 | const tsContent = fs.readFileSync(filePath, 'utf8') |
| 76 | |
| 77 | // Transpile TypeScript to JavaScript with ES module output |
| 78 | let jsContent = transpile(tsContent, { |
| 79 | module: 99, // ModuleKind.ESNext |
| 80 | target: 99, // ScriptTarget.ESNext |
| 81 | esModuleInterop: true, |
| 82 | allowSyntheticDefaultImports: true, |
| 83 | lib: ['lib.esnext.d.ts'], // Enable latest features including top-level await |
| 84 | suppressOutputPathCheck: true, |
| 85 | skipLibCheck: true, |
| 86 | }) |
| 87 | |
| 88 | // Check if the code uses CommonJS globals |
| 89 | const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent) |
| 90 | const usesRequire = /\brequire\s*\(/.test(jsContent) |
| 91 | const usesModuleExports = /\b(module\.exports|exports\.)/.test(jsContent) |
| 92 | |
| 93 | if (usesCommonJSGlobals || usesRequire || usesModuleExports) { |
| 94 | // Inject ESM equivalents at the top of the file |
| 95 | let esmGlobals = '' |
| 96 | |
| 97 | if (usesRequire || usesModuleExports) { |
| 98 | // IMPORTANT: Use the original .ts file path as the base for require() |
| 99 | // This ensures dynamic require() calls work with relative paths from the original file location |
| 100 | const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}` |
| 101 | esmGlobals += `import { createRequire } from 'module'; |
| 102 | import { extname as __extname } from 'path'; |
| 103 | const __baseRequire = createRequire('${originalFileUrl}'); |
| 104 | |
| 105 | // Wrap require to auto-resolve extensions (mimics CommonJS behavior) |
| 106 | const require = (id) => { |
| 107 | try { |
| 108 | return __baseRequire(id); |
| 109 | } catch (err) { |
| 110 | // If module not found and it's a relative/absolute path without extension, try common extensions |
| 111 | if (err.code === 'MODULE_NOT_FOUND' && (id.startsWith('./') || id.startsWith('../') || id.startsWith('/'))) { |
| 112 | const ext = __extname(id); |
| 113 | // Only treat known file extensions as real extensions (so names like .TEST don't block probing) |
| 114 | const __knownExts = ['.js', '.cjs', '.mjs', '.json', '.node']; |
| 115 | const hasKnownExt = ext && __knownExts.includes(ext.toLowerCase()); |
| 116 | if (!hasKnownExt) { |
| 117 | // Try common extensions in order: .js, .cjs, .json, .node |
| 118 | // Note: .ts files cannot be required - they need transpilation first |
| 119 | const extensions = ['.js', '.cjs', '.json', '.node']; |
| 120 | for (const testExt of extensions) { |
| 121 | try { |
| 122 | return __baseRequire(id + testExt); |
| 123 | } catch (e) { |
| 124 | // Continue to next extension |
no test coverage detected