codeceptjs 4.0.0-beta.9.esm-aria → 4.0.0-rc.10

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 (69) hide show
  1. package/README.md +39 -27
  2. package/bin/codecept.js +2 -2
  3. package/bin/mcp-server.js +610 -0
  4. package/docs/webapi/appendField.mustache +5 -0
  5. package/docs/webapi/attachFile.mustache +12 -0
  6. package/docs/webapi/checkOption.mustache +1 -1
  7. package/docs/webapi/clearField.mustache +5 -0
  8. package/docs/webapi/dontSeeCurrentPathEquals.mustache +10 -0
  9. package/docs/webapi/dontSeeElement.mustache +4 -0
  10. package/docs/webapi/dontSeeInField.mustache +5 -0
  11. package/docs/webapi/fillField.mustache +5 -0
  12. package/docs/webapi/moveCursorTo.mustache +5 -1
  13. package/docs/webapi/seeCurrentPathEquals.mustache +10 -0
  14. package/docs/webapi/seeElement.mustache +4 -0
  15. package/docs/webapi/seeInField.mustache +5 -0
  16. package/docs/webapi/selectOption.mustache +5 -0
  17. package/docs/webapi/uncheckOption.mustache +1 -1
  18. package/lib/actor.js +12 -8
  19. package/lib/codecept.js +51 -18
  20. package/lib/command/definitions.js +14 -7
  21. package/lib/command/init.js +2 -4
  22. package/lib/command/run-workers.js +13 -2
  23. package/lib/command/workers/runTests.js +121 -9
  24. package/lib/config.js +24 -33
  25. package/lib/container.js +177 -28
  26. package/lib/element/WebElement.js +81 -2
  27. package/lib/els.js +12 -6
  28. package/lib/helper/Appium.js +8 -8
  29. package/lib/helper/GraphQL.js +6 -4
  30. package/lib/helper/JSONResponse.js +3 -4
  31. package/lib/helper/Playwright.js +339 -505
  32. package/lib/helper/Puppeteer.js +324 -89
  33. package/lib/helper/REST.js +15 -9
  34. package/lib/helper/WebDriver.js +311 -81
  35. package/lib/helper/errors/ElementNotFound.js +5 -2
  36. package/lib/helper/errors/MultipleElementsFound.js +52 -0
  37. package/lib/helper/extras/elementSelection.js +58 -0
  38. package/lib/helper/scripts/dropFile.js +11 -0
  39. package/lib/html.js +14 -1
  40. package/lib/listener/config.js +11 -3
  41. package/lib/listener/globalRetry.js +32 -6
  42. package/lib/listener/helpers.js +2 -14
  43. package/lib/locator.js +32 -0
  44. package/lib/mocha/cli.js +16 -0
  45. package/lib/mocha/factory.js +7 -27
  46. package/lib/mocha/gherkin.js +4 -4
  47. package/lib/mocha/test.js +4 -2
  48. package/lib/output.js +2 -2
  49. package/lib/plugin/aiTrace.js +464 -0
  50. package/lib/plugin/auth.js +2 -1
  51. package/lib/plugin/retryFailedStep.js +28 -19
  52. package/lib/plugin/stepByStepReport.js +5 -1
  53. package/lib/step/base.js +14 -1
  54. package/lib/step/config.js +15 -2
  55. package/lib/step/meta.js +18 -1
  56. package/lib/step/record.js +9 -1
  57. package/lib/utils/loaderCheck.js +162 -0
  58. package/lib/utils/typescript.js +449 -0
  59. package/lib/utils.js +48 -0
  60. package/lib/workers.js +163 -54
  61. package/package.json +43 -32
  62. package/typings/index.d.ts +120 -4
  63. package/lib/helper/extras/PlaywrightLocator.js +0 -110
  64. package/lib/listener/enhancedGlobalRetry.js +0 -110
  65. package/lib/plugin/enhancedRetryFailedStep.js +0 -99
  66. package/lib/plugin/htmlReporter.js +0 -3648
  67. package/lib/retryCoordinator.js +0 -207
  68. package/typings/promiseBasedTypes.d.ts +0 -11011
  69. package/typings/types.d.ts +0 -13073
package/lib/step/meta.js CHANGED
@@ -58,17 +58,24 @@ class MetaStep extends Step {
58
58
  this.status = 'queued'
59
59
  this.setArguments(Array.from(arguments).slice(1))
60
60
  let result
61
+ let hasChildSteps = false
61
62
 
62
63
  const registerStep = step => {
63
64
  this.setMetaStep(null)
64
65
  step.setMetaStep(this)
66
+ hasChildSteps = true
65
67
  }
66
68
  event.dispatcher.prependListener(event.step.before, registerStep)
69
+
70
+ // Start timing
71
+ this.startTime = Date.now()
72
+
67
73
  // Handle async and sync methods.
68
74
  if (fn.constructor.name === 'AsyncFunction') {
69
75
  result = fn
70
76
  .apply(this.context, this.args)
71
77
  .then(result => {
78
+ this.setStatus('success')
72
79
  return result
73
80
  })
74
81
  .catch(error => {
@@ -78,17 +85,27 @@ class MetaStep extends Step {
78
85
  .finally(() => {
79
86
  this.endTime = Date.now()
80
87
  event.dispatcher.removeListener(event.step.before, registerStep)
88
+ // Only emit events if no child steps were registered
89
+ if (!hasChildSteps) {
90
+ event.emit(event.step.started, this)
91
+ event.emit(event.step.finished, this)
92
+ }
81
93
  })
82
94
  } else {
83
95
  try {
84
- this.startTime = Date.now()
85
96
  result = fn.apply(this.context, this.args)
97
+ this.setStatus('success')
86
98
  } catch (error) {
87
99
  this.setStatus('failed')
88
100
  throw error
89
101
  } finally {
90
102
  this.endTime = Date.now()
91
103
  event.dispatcher.removeListener(event.step.before, registerStep)
104
+ // Only emit events if no child steps were registered
105
+ if (!hasChildSteps) {
106
+ event.emit(event.step.started, this)
107
+ event.emit(event.step.finished, this)
108
+ }
92
109
  }
93
110
  }
94
111
 
@@ -5,12 +5,13 @@ import output from '../output.js'
5
5
  import store from '../store.js'
6
6
  import { TIMEOUT_ORDER } from '../timeout.js'
7
7
  import retryStep from './retry.js'
8
+ import { fixErrorStack } from '../utils/typescript.js'
8
9
  function recordStep(step, args) {
9
10
  step.status = 'queued'
10
11
 
11
12
  // apply step configuration
12
13
  const lastArg = args[args.length - 1]
13
- if (lastArg instanceof StepConfig) {
14
+ if (StepConfig.isStepConfig(lastArg)) {
14
15
  const stepConfig = args.pop()
15
16
  const { opts, timeout, retry } = stepConfig.getConfig()
16
17
 
@@ -60,6 +61,13 @@ function recordStep(step, args) {
60
61
  recorder.catch(err => {
61
62
  step.status = 'failed'
62
63
  step.endTime = +Date.now()
64
+
65
+ // Fix error stack to point to original .ts files (lazy import to avoid circular dependency)
66
+ const fileMapping = global.container?.tsFileMapping?.()
67
+ if (fileMapping) {
68
+ fixErrorStack(err, fileMapping)
69
+ }
70
+
63
71
  event.emit(event.step.failed, step, err)
64
72
  event.emit(event.step.finished, step)
65
73
  throw err
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Utilities for checking TypeScript loader availability
3
+ */
4
+
5
+ /**
6
+ * Check if a TypeScript loader is available for test files
7
+ * Note: This checks if loaders are in the require array, not if packages are installed
8
+ * Package installation is checked when actually requiring modules
9
+ * @param {string[]} requiredModules - Array of required modules from config
10
+ * @returns {boolean}
11
+ */
12
+ export function checkTypeScriptLoader(requiredModules = []) {
13
+ // Check if a loader is configured in the require array
14
+ return (
15
+ requiredModules.includes('tsx/esm') ||
16
+ requiredModules.includes('tsx/cjs') ||
17
+ requiredModules.includes('tsx') ||
18
+ requiredModules.includes('ts-node/esm') ||
19
+ requiredModules.includes('ts-node/register') ||
20
+ requiredModules.includes('ts-node')
21
+ )
22
+ }
23
+
24
+ /**
25
+ * Generate helpful error message if .ts tests found but no loader configured
26
+ * @param {string[]} testFiles - Array of test file paths
27
+ * @returns {string|null} Error message or null if no TypeScript files
28
+ */
29
+ export function getTypeScriptLoaderError(testFiles) {
30
+ const tsFiles = testFiles.filter(f => f.endsWith('.ts'))
31
+
32
+ if (tsFiles.length === 0) return null
33
+
34
+ return `
35
+ ╔═════════════════════════════════════════════════════════════════════════════╗
36
+ ║ ║
37
+ ║ ⚠️ TypeScript Test Files Detected but No Loader Configured ║
38
+ ║ ║
39
+ ╚═════════════════════════════════════════════════════════════════════════════╝
40
+
41
+ Found ${tsFiles.length} TypeScript test file(s) but no TypeScript loader is configured.
42
+
43
+ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tests.
44
+
45
+ ┌─────────────────────────────────────────────────────────────────────────────┐
46
+ │ Option 1: tsx (Recommended - Fast, Zero Config) │
47
+ └─────────────────────────────────────────────────────────────────────────────┘
48
+
49
+ Installation:
50
+ npm install --save-dev tsx
51
+
52
+ Configuration:
53
+ Add to your codecept.conf.ts or codecept.conf.js:
54
+
55
+ export const config = {
56
+ tests: './**/*_test.ts',
57
+ require: ['tsx/cjs'], // ← Add this line
58
+ helpers: { /* ... */ }
59
+ }
60
+
61
+ Why tsx?
62
+ ⚡ Fast: Built on esbuild
63
+ 🎯 Zero config: No tsconfig.json required
64
+ ✅ Works with Mocha: Uses CommonJS hooks
65
+ ✅ Complete: Handles all TypeScript features
66
+
67
+ ┌─────────────────────────────────────────────────────────────────────────────┐
68
+ │ Option 2: ts-node/esm (Not Recommended - Has Module Resolution Issues) │
69
+ └─────────────────────────────────────────────────────────────────────────────┘
70
+
71
+ ⚠️ ts-node/esm has significant limitations and is not recommended:
72
+ - Doesn't work with "type": "module" in package.json
73
+ - Module resolution doesn't work like standard TypeScript ESM
74
+ - Import statements must use explicit file paths
75
+
76
+ We strongly recommend using tsx/cjs instead.
77
+
78
+ If you still want to use ts-node/esm:
79
+
80
+ Installation:
81
+ npm install --save-dev ts-node
82
+
83
+ Configuration:
84
+ 1. Add to your codecept.conf.ts:
85
+ require: ['ts-node/esm']
86
+
87
+ 2. Create tsconfig.json:
88
+ {
89
+ "compilerOptions": {
90
+ "module": "ESNext",
91
+ "target": "ES2022",
92
+ "moduleResolution": "node",
93
+ "esModuleInterop": true
94
+ },
95
+ "ts-node": {
96
+ "esm": true
97
+ }
98
+ }
99
+
100
+ 3. Do NOT use "type": "module" in package.json
101
+
102
+ 📚 Documentation: https://codecept.io/typescript
103
+
104
+ Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
105
+ transpiled. Only test files require a loader to be configured.
106
+ `
107
+ }
108
+
109
+ /**
110
+ * Get warning message if ts-node/esm is being used
111
+ * @param {string[]} requiredModules - Array of required modules from config
112
+ * @returns {string|null} Warning message or null
113
+ */
114
+ export function getTSNodeESMWarning(requiredModules = []) {
115
+ if (!requiredModules.includes('ts-node/esm')) {
116
+ return null
117
+ }
118
+
119
+ return `
120
+ ⚠️ Warning: ts-node/esm with "module": "esnext" requires explicit file extensions in all imports.
121
+
122
+ This is a known limitation. Use tsx/cjs instead to write imports without extensions.
123
+
124
+ Examples:
125
+
126
+ ❌ Incorrect (will fail):
127
+ import loginPage from "./pages/Login";
128
+
129
+ ✅ Correct (must include .ts extension):
130
+ import loginPage from "./pages/Login.ts";
131
+
132
+ 📚 Documentation: https://codecept.io/typescript
133
+
134
+ `
135
+ }
136
+
137
+ /**
138
+ * Check if user is trying to run TypeScript tests without proper loader
139
+ * @param {string[]} testFiles - Array of test file paths
140
+ * @param {string[]} requiredModules - Array of required modules from config
141
+ * @returns {{hasError: boolean, message: string|null}}
142
+ */
143
+ export function validateTypeScriptSetup(testFiles, requiredModules = []) {
144
+ const tsFiles = testFiles.filter(f => f.endsWith('.ts'))
145
+
146
+ if (tsFiles.length === 0) {
147
+ // No TypeScript test files, all good
148
+ return { hasError: false, message: null }
149
+ }
150
+
151
+ // Check if a loader is configured in the require array
152
+ const hasLoader = checkTypeScriptLoader(requiredModules)
153
+
154
+ if (hasLoader) {
155
+ // Loader configured, all good (package will be checked when requireModules runs)
156
+ return { hasError: false, message: null }
157
+ }
158
+
159
+ // No loader configured and TypeScript tests exist
160
+ const message = getTypeScriptLoaderError(testFiles)
161
+ return { hasError: true, message }
162
+ }
@@ -0,0 +1,449 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { pathToFileURL } from 'url'
4
+
5
+ /**
6
+ * Load tsconfig.json if it exists
7
+ * @param {string} tsConfigPath - Path to tsconfig.json
8
+ * @returns {object|null} - Parsed tsconfig or null
9
+ */
10
+ function loadTsConfig(tsConfigPath) {
11
+ if (!fs.existsSync(tsConfigPath)) {
12
+ return null
13
+ }
14
+
15
+ try {
16
+ const tsConfigContent = fs.readFileSync(tsConfigPath, 'utf8')
17
+ return JSON.parse(tsConfigContent)
18
+ } catch (err) {
19
+ return null
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Resolve TypeScript path alias to actual file path
25
+ * @param {string} importPath - Import path with alias (e.g., '#config/urls')
26
+ * @param {object} tsConfig - Parsed tsconfig.json
27
+ * @param {string} configDir - Directory containing tsconfig.json
28
+ * @returns {string|null} - Resolved file path or null if not an alias
29
+ */
30
+ function resolveTsPathAlias(importPath, tsConfig, configDir) {
31
+ if (!tsConfig || !tsConfig.compilerOptions || !tsConfig.compilerOptions.paths) {
32
+ return null
33
+ }
34
+
35
+ const paths = tsConfig.compilerOptions.paths
36
+
37
+ for (const [pattern, targets] of Object.entries(paths)) {
38
+ if (!targets || targets.length === 0) {
39
+ continue
40
+ }
41
+
42
+ const patternRegex = new RegExp(
43
+ '^' + pattern.replace(/\*/g, '(.*)') + '$'
44
+ )
45
+ const match = importPath.match(patternRegex)
46
+
47
+ if (match) {
48
+ const wildcard = match[1] || ''
49
+ const target = targets[0]
50
+ const resolvedTarget = target.replace(/\*/g, wildcard)
51
+
52
+ return path.resolve(configDir, resolvedTarget)
53
+ }
54
+ }
55
+
56
+ return null
57
+ }
58
+
59
+ /**
60
+ * Transpile TypeScript files to ES modules with CommonJS shim support
61
+ * Handles recursive transpilation of imported TypeScript files
62
+ *
63
+ * @param {string} mainFilePath - Path to the main TypeScript file to transpile
64
+ * @param {object} typescript - TypeScript compiler instance
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
125
+ }
126
+ }
127
+ }
128
+ }
129
+ // Re-throw original error if all attempts failed
130
+ throw err;
131
+ }
132
+ };
133
+
134
+ const module = { exports: {} };
135
+ const exports = module.exports;
136
+
137
+ `
138
+ }
139
+
140
+ if (usesCommonJSGlobals) {
141
+ // For __dirname and __filename, also use the original file path
142
+ const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}`
143
+ esmGlobals += `import { fileURLToPath as __fileURLToPath } from 'url';
144
+ import { dirname as __dirname_fn } from 'path';
145
+ const __filename = '${filePath.replace(/\\/g, '/')}';
146
+ const __dirname = __dirname_fn(__filename);
147
+
148
+ `
149
+ }
150
+
151
+ jsContent = esmGlobals + jsContent
152
+
153
+ // If module.exports is used, we need to export it as default
154
+ if (usesModuleExports) {
155
+ jsContent += `\nexport default module.exports;\n`
156
+ }
157
+ }
158
+
159
+ return jsContent
160
+ }
161
+
162
+ // Create a map to track transpiled files
163
+ const transpiledFiles = new Map()
164
+ const baseDir = path.dirname(mainFilePath)
165
+
166
+ // Try to find tsconfig.json by walking up the directory tree
167
+ let tsConfigPath = path.join(baseDir, 'tsconfig.json')
168
+ let configDir = baseDir
169
+ let searchDir = baseDir
170
+
171
+ while (!fs.existsSync(tsConfigPath) && searchDir !== path.dirname(searchDir)) {
172
+ searchDir = path.dirname(searchDir)
173
+ tsConfigPath = path.join(searchDir, 'tsconfig.json')
174
+ if (fs.existsSync(tsConfigPath)) {
175
+ configDir = searchDir
176
+ break
177
+ }
178
+ }
179
+
180
+ const tsConfig = loadTsConfig(tsConfigPath)
181
+
182
+ // Recursive function to transpile a file and all its TypeScript dependencies
183
+ const transpileFileAndDeps = (filePath) => {
184
+ // Already transpiled, skip
185
+ if (transpiledFiles.has(filePath)) {
186
+ return
187
+ }
188
+
189
+ // Transpile this file
190
+ let jsContent = transpileTS(filePath)
191
+
192
+ // Find all TypeScript imports in this file (both ESM imports and require() calls)
193
+ const importRegex = /from\s+['"]([^'"]+?)['"]/g
194
+ const requireRegex = /require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g
195
+ let match
196
+ const imports = []
197
+
198
+ while ((match = importRegex.exec(jsContent)) !== null) {
199
+ imports.push({ path: match[1], type: 'import' })
200
+ }
201
+
202
+ while ((match = requireRegex.exec(jsContent)) !== null) {
203
+ imports.push({ path: match[1], type: 'require' })
204
+ }
205
+
206
+ // Get the base directory for this file
207
+ const fileBaseDir = path.dirname(filePath)
208
+
209
+ // Recursively transpile each imported TypeScript file
210
+ for (const { path: importPath } of imports) {
211
+ let importedPath = importPath
212
+
213
+ // Check if this is a path alias
214
+ const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir)
215
+ if (resolvedAlias) {
216
+ importedPath = resolvedAlias
217
+ } else if (importPath.startsWith('.')) {
218
+ importedPath = path.resolve(fileBaseDir, importPath)
219
+ } else {
220
+ continue
221
+ }
222
+
223
+ // Handle .js extensions that might actually be .ts files
224
+ if (importedPath.endsWith('.js')) {
225
+ const tsVersion = importedPath.replace(/\.js$/, '.ts')
226
+ if (fs.existsSync(tsVersion)) {
227
+ importedPath = tsVersion
228
+ }
229
+ }
230
+
231
+ // Check for standard module extensions to determine if we should try adding .ts
232
+ const ext = path.extname(importedPath)
233
+ const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
234
+ const hasStandardExtension = standardExtensions.includes(ext.toLowerCase())
235
+
236
+ // If it doesn't end with .ts and doesn't have a standard extension, try adding .ts
237
+ if (!importedPath.endsWith('.ts') && !hasStandardExtension) {
238
+ const tsPath = importedPath + '.ts'
239
+ if (fs.existsSync(tsPath)) {
240
+ importedPath = tsPath
241
+ } else {
242
+ // Try index.ts for directory imports
243
+ const indexTsPath = path.join(importedPath, 'index.ts')
244
+ if (fs.existsSync(indexTsPath)) {
245
+ importedPath = indexTsPath
246
+ } else {
247
+ // Try .js extension as well
248
+ const jsPath = importedPath + '.js'
249
+ if (fs.existsSync(jsPath)) {
250
+ // Skip .js files, they don't need transpilation
251
+ continue
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // If it's a TypeScript file, recursively transpile it and its dependencies
258
+ if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
259
+ transpileFileAndDeps(importedPath)
260
+ }
261
+ }
262
+
263
+ // After all dependencies are transpiled, rewrite imports in this file
264
+ jsContent = jsContent.replace(
265
+ /from\s+['"]([^'"]+?)['"]/g,
266
+ (match, importPath) => {
267
+ let resolvedPath = importPath
268
+ const originalExt = path.extname(importPath)
269
+
270
+ // Check if this is a path alias
271
+ const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir)
272
+ if (resolvedAlias) {
273
+ resolvedPath = resolvedAlias
274
+ } else if (importPath.startsWith('.')) {
275
+ resolvedPath = path.resolve(fileBaseDir, importPath)
276
+ } else {
277
+ return match
278
+ }
279
+
280
+ // If resolved path is a directory, try index.ts
281
+ if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
282
+ const indexPath = path.join(resolvedPath, 'index.ts')
283
+ if (fs.existsSync(indexPath) && transpiledFiles.has(indexPath)) {
284
+ const tempFile = transpiledFiles.get(indexPath)
285
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
286
+ if (!relPath.startsWith('.')) {
287
+ return `from './${relPath}'`
288
+ }
289
+ return `from '${relPath}'`
290
+ }
291
+ }
292
+
293
+ // Handle .js extension that might be .ts
294
+ if (resolvedPath.endsWith('.js')) {
295
+ const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
296
+ if (transpiledFiles.has(tsVersion)) {
297
+ const tempFile = transpiledFiles.get(tsVersion)
298
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
299
+ if (!relPath.startsWith('.')) {
300
+ return `from './${relPath}'`
301
+ }
302
+ return `from '${relPath}'`
303
+ }
304
+ return match
305
+ }
306
+
307
+ // Try with .ts extension
308
+ const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
309
+
310
+ // If we transpiled this file, use the temp file
311
+ if (transpiledFiles.has(tsPath)) {
312
+ const tempFile = transpiledFiles.get(tsPath)
313
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
314
+ if (!relPath.startsWith('.')) {
315
+ return `from './${relPath}'`
316
+ }
317
+ return `from '${relPath}'`
318
+ }
319
+
320
+ // Try index.ts for directory imports
321
+ const indexTsPath = path.join(resolvedPath, 'index.ts')
322
+ if (transpiledFiles.has(indexTsPath)) {
323
+ const tempFile = transpiledFiles.get(indexTsPath)
324
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
325
+ if (!relPath.startsWith('.')) {
326
+ return `from './${relPath}'`
327
+ }
328
+ return `from '${relPath}'`
329
+ }
330
+
331
+ // If the import doesn't have a standard module extension, add .js for ESM compatibility
332
+ const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
333
+ const hasStandardExtension = standardExtensions.includes(originalExt.toLowerCase())
334
+
335
+ if (!hasStandardExtension) {
336
+ return match.replace(importPath, importPath + '.js')
337
+ }
338
+
339
+ return match
340
+ }
341
+ )
342
+
343
+ // Also rewrite require() calls to point to transpiled TypeScript files
344
+ jsContent = jsContent.replace(
345
+ /require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g,
346
+ (match, requirePath) => {
347
+ let resolvedPath = requirePath
348
+
349
+ // Check if this is a path alias
350
+ const resolvedAlias = resolveTsPathAlias(requirePath, tsConfig, configDir)
351
+ if (resolvedAlias) {
352
+ resolvedPath = resolvedAlias
353
+ } else if (requirePath.startsWith('.')) {
354
+ resolvedPath = path.resolve(fileBaseDir, requirePath)
355
+ } else {
356
+ return match
357
+ }
358
+
359
+ // Handle .js extension that might be .ts
360
+ if (resolvedPath.endsWith('.js')) {
361
+ const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
362
+ if (transpiledFiles.has(tsVersion)) {
363
+ const tempFile = transpiledFiles.get(tsVersion)
364
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
365
+ const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
366
+ return `require('${finalPath}')`
367
+ }
368
+ return match
369
+ }
370
+
371
+ // Try with .ts extension
372
+ const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
373
+
374
+ // If we transpiled this file, use the temp file
375
+ if (transpiledFiles.has(tsPath)) {
376
+ const tempFile = transpiledFiles.get(tsPath)
377
+ const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
378
+ const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
379
+ return `require('${finalPath}')`
380
+ }
381
+
382
+ // Otherwise, keep the require as-is
383
+ return match
384
+ }
385
+ )
386
+
387
+ // Write the transpiled file with updated imports
388
+ const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
389
+ fs.writeFileSync(tempFile, jsContent)
390
+ transpiledFiles.set(filePath, tempFile)
391
+ }
392
+
393
+ // Start recursive transpilation from the main file
394
+ transpileFileAndDeps(mainFilePath)
395
+
396
+ // Get the main transpiled file
397
+ const tempJsFile = transpiledFiles.get(mainFilePath)
398
+
399
+ // Convert to file:// URL for dynamic import() (required on Windows)
400
+ const tempFileUrl = pathToFileURL(tempJsFile).href
401
+
402
+ // Store all temp files for cleanup (keep as paths, not URLs)
403
+ const allTempFiles = Array.from(transpiledFiles.values())
404
+
405
+ return { tempFile: tempFileUrl, allTempFiles, fileMapping: transpiledFiles }
406
+ }
407
+
408
+ /**
409
+ * Map error stack traces from temp .mjs files back to original .ts files
410
+ * @param {Error} error - The error object to fix
411
+ * @param {Map<string, string>} fileMapping - Map of original .ts files to temp .mjs files
412
+ * @returns {Error} - Error with fixed stack trace
413
+ */
414
+ export function fixErrorStack(error, fileMapping) {
415
+ if (!error.stack || !fileMapping) return error
416
+
417
+ let stack = error.stack
418
+
419
+ // Create reverse mapping (temp.mjs -> original.ts)
420
+ const reverseMap = new Map()
421
+ for (const [tsFile, mjsFile] of fileMapping.entries()) {
422
+ reverseMap.set(mjsFile, tsFile)
423
+ }
424
+
425
+ // Replace all temp.mjs references with original .ts files
426
+ for (const [mjsFile, tsFile] of reverseMap.entries()) {
427
+ const mjsPattern = mjsFile.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
428
+ stack = stack.replace(new RegExp(mjsPattern, 'g'), tsFile)
429
+ }
430
+
431
+ error.stack = stack
432
+ return error
433
+ }
434
+
435
+ /**
436
+ * Clean up temporary transpiled files
437
+ * @param {string[]} tempFiles - Array of temp file paths to delete
438
+ */
439
+ export function cleanupTempFiles(tempFiles) {
440
+ for (const file of tempFiles) {
441
+ if (fs.existsSync(file)) {
442
+ try {
443
+ fs.unlinkSync(file)
444
+ } catch (err) {
445
+ // Ignore cleanup errors
446
+ }
447
+ }
448
+ }
449
+ }