codeceptjs 4.0.0-beta.20 → 4.0.0-beta.22
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/lib/actor.js +2 -0
- package/lib/container.js +155 -39
- package/lib/parser.js +12 -1
- package/lib/utils/typescript.js +68 -59
- package/package.json +1 -1
package/lib/actor.js
CHANGED
package/lib/container.js
CHANGED
|
@@ -17,6 +17,51 @@ import ai from './ai.js'
|
|
|
17
17
|
import actorFactory from './actor.js'
|
|
18
18
|
|
|
19
19
|
let asyncHelperPromise
|
|
20
|
+
let tsxLoaderRegistered = false
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Automatically register tsx ESM loader for TypeScript imports
|
|
24
|
+
* This allows loading .ts files without NODE_OPTIONS
|
|
25
|
+
*/
|
|
26
|
+
async function ensureTsxLoader() {
|
|
27
|
+
if (tsxLoaderRegistered) return true
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Check if tsx is available
|
|
31
|
+
const { createRequire } = await import('module')
|
|
32
|
+
const { pathToFileURL } = await import('url')
|
|
33
|
+
const userRequire = createRequire(pathToFileURL(path.join(global.codecept_dir || process.cwd(), 'package.json')).href)
|
|
34
|
+
|
|
35
|
+
// Try to resolve tsx from user's project
|
|
36
|
+
try {
|
|
37
|
+
userRequire.resolve('tsx')
|
|
38
|
+
} catch {
|
|
39
|
+
debug('tsx not found in user project')
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Register tsx/esm loader dynamically
|
|
44
|
+
// Use Node.js register() API from node:module (Node 18.19+, 20.6+)
|
|
45
|
+
try {
|
|
46
|
+
const { register } = await import('node:module')
|
|
47
|
+
if (typeof register === 'function') {
|
|
48
|
+
debug('Registering tsx ESM loader via node:module register()')
|
|
49
|
+
const tsxPath = userRequire.resolve('tsx/esm')
|
|
50
|
+
register(tsxPath, import.meta.url)
|
|
51
|
+
tsxLoaderRegistered = true
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
} catch (registerErr) {
|
|
55
|
+
debug('node:module register() not available:', registerErr.message)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
debug('module.register not available, tsx loader must be set via NODE_OPTIONS')
|
|
59
|
+
return false
|
|
60
|
+
} catch (err) {
|
|
61
|
+
debug('Failed to register tsx loader:', err.message)
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
20
65
|
|
|
21
66
|
let container = {
|
|
22
67
|
helpers: {},
|
|
@@ -32,7 +77,7 @@ let container = {
|
|
|
32
77
|
translation: {},
|
|
33
78
|
/** @type {Result | null} */
|
|
34
79
|
result: null,
|
|
35
|
-
sharedKeys: new Set() // Track keys shared via share() function
|
|
80
|
+
sharedKeys: new Set(), // Track keys shared via share() function
|
|
36
81
|
}
|
|
37
82
|
|
|
38
83
|
/**
|
|
@@ -250,10 +295,10 @@ class Container {
|
|
|
250
295
|
// Instead of using append which replaces the entire container,
|
|
251
296
|
// directly update the support object to maintain proxy references
|
|
252
297
|
Object.assign(container.support, data)
|
|
253
|
-
|
|
298
|
+
|
|
254
299
|
// Track which keys were explicitly shared
|
|
255
300
|
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
256
|
-
|
|
301
|
+
|
|
257
302
|
if (!options.local) {
|
|
258
303
|
WorkerStorage.share(data)
|
|
259
304
|
}
|
|
@@ -288,39 +333,30 @@ async function createHelpers(config) {
|
|
|
288
333
|
helperName = HelperClass.constructor.name
|
|
289
334
|
}
|
|
290
335
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
debug(`helper ${helperName} resolved type: ${typeof ResolvedHelperClass}`, ResolvedHelperClass)
|
|
301
|
-
|
|
302
|
-
// Extract default export from ESM module wrapper if needed
|
|
303
|
-
if (ResolvedHelperClass && ResolvedHelperClass.__esModule && ResolvedHelperClass.default) {
|
|
304
|
-
ResolvedHelperClass = ResolvedHelperClass.default
|
|
305
|
-
debug(`extracted default export for ${helperName}, new type: ${typeof ResolvedHelperClass}`)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (typeof ResolvedHelperClass !== 'function') {
|
|
309
|
-
throw new Error(`Helper '${helperName}' is not a class. Got: ${typeof ResolvedHelperClass}`)
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
checkHelperRequirements(ResolvedHelperClass)
|
|
313
|
-
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
314
|
-
if (helpers[helperName]._init) await helpers[helperName]._init()
|
|
315
|
-
debug(`helper ${helperName} async initialized`)
|
|
316
|
-
})
|
|
336
|
+
// Check for inline helper object (plain object with callable methods, no require field)
|
|
337
|
+
// Inline helpers have methods defined directly on them, unlike config objects
|
|
338
|
+
if (!HelperClass && !config[helperName].require && typeof config[helperName] === 'object') {
|
|
339
|
+
// Check if this object has any callable methods (indicates it's an inline helper)
|
|
340
|
+
const hasMethods = Object.values(config[helperName]).some(val => typeof val === 'function')
|
|
341
|
+
if (hasMethods) {
|
|
342
|
+
// This is an inline helper object, use it directly
|
|
343
|
+
helpers[helperName] = config[helperName]
|
|
344
|
+
debug(`helper ${helperName} loaded as inline object`)
|
|
317
345
|
continue
|
|
318
|
-
} else {
|
|
319
|
-
HelperClass = helperResult
|
|
320
346
|
}
|
|
321
347
|
}
|
|
322
348
|
|
|
323
|
-
//
|
|
349
|
+
// classical require - may be async for ESM modules
|
|
350
|
+
if (!HelperClass) {
|
|
351
|
+
// requireHelperFromModule is async, so we need to await it
|
|
352
|
+
HelperClass = await requireHelperFromModule(helperName, config)
|
|
353
|
+
|
|
354
|
+
// Extract default export from ESM module wrapper if needed
|
|
355
|
+
if (HelperClass && HelperClass.__esModule && HelperClass.default) {
|
|
356
|
+
HelperClass = HelperClass.default
|
|
357
|
+
debug(`extracted default export for ${helperName}`)
|
|
358
|
+
}
|
|
359
|
+
} // handle async CJS modules that use dynamic import
|
|
324
360
|
if (isAsyncFunction(HelperClass)) {
|
|
325
361
|
helpers[helperName] = {}
|
|
326
362
|
|
|
@@ -349,6 +385,9 @@ async function createHelpers(config) {
|
|
|
349
385
|
}
|
|
350
386
|
}
|
|
351
387
|
|
|
388
|
+
// Wait for all async helpers to be resolved before calling _init
|
|
389
|
+
await asyncHelperPromise
|
|
390
|
+
|
|
352
391
|
for (const name in helpers) {
|
|
353
392
|
if (helpers[name]._init) await helpers[name]._init()
|
|
354
393
|
}
|
|
@@ -392,7 +431,85 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
392
431
|
}
|
|
393
432
|
HelperClass = mod.default || mod
|
|
394
433
|
} catch (err) {
|
|
395
|
-
if (err.code === '
|
|
434
|
+
if (err.code === 'ERR_UNKNOWN_FILE_EXTENSION' || (err.message && err.message.includes('Unknown file extension'))) {
|
|
435
|
+
// This is likely a TypeScript helper file. Transpile it to a temporary JS file
|
|
436
|
+
// and import that as a reliable fallback (no NODE_OPTIONS required).
|
|
437
|
+
try {
|
|
438
|
+
const pathModule = await import('path')
|
|
439
|
+
const absolutePath = pathModule.default.resolve(moduleName)
|
|
440
|
+
|
|
441
|
+
// Attempt to load local 'typescript' to transpile the helper
|
|
442
|
+
let typescript
|
|
443
|
+
try {
|
|
444
|
+
typescript = await import('typescript')
|
|
445
|
+
} catch (tsImportErr) {
|
|
446
|
+
throw new Error(`TypeScript helper detected (${moduleName}). Please install 'typescript' in your project: npm install --save-dev typescript`)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const { tempFile, allTempFiles } = await transpileTypeScript(absolutePath, typescript)
|
|
450
|
+
debug(`Transpiled TypeScript helper: ${moduleName} -> ${tempFile}`)
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
try {
|
|
454
|
+
const mod = await import(tempFile)
|
|
455
|
+
HelperClass = mod.default || mod
|
|
456
|
+
debug(`helper ${helperName} loaded from transpiled JS: ${tempFile}`)
|
|
457
|
+
} catch (importTempErr) {
|
|
458
|
+
// If import fails due to CommonJS named export incompatibility,
|
|
459
|
+
// try a quick transform: convert named imports from CommonJS packages
|
|
460
|
+
// into default import + destructuring. This resolves cases like
|
|
461
|
+
// "Named export 'Helper' not found. The requested module '@codeceptjs/helper' is a CommonJS module"
|
|
462
|
+
const msg = (importTempErr && importTempErr.message) || ''
|
|
463
|
+
const commonJsMatch = msg.match(/The requested module '(.+?)' is a CommonJS module/)
|
|
464
|
+
if (commonJsMatch) {
|
|
465
|
+
// Read the transpiled file, perform heuristic replacement, and import again
|
|
466
|
+
const fs = await import('fs')
|
|
467
|
+
let content = fs.readFileSync(tempFile, 'utf8')
|
|
468
|
+
// Heuristic: replace "import { X, Y } from 'mod'" with default import + destructure
|
|
469
|
+
content = content.replace(/import\s+\{([^}]+)\}\s+from\s+(['"])([^'"\)]+)\2/gm, (m, names, q, modName) => {
|
|
470
|
+
// Only adjust imports for the module reported in the error or for local modules
|
|
471
|
+
if (modName === commonJsMatch[1] || modName.startsWith('.') || !modName.includes('/')) {
|
|
472
|
+
const cleanedNames = names.trim()
|
|
473
|
+
return `import pkg__interop from ${q}${modName}${q};\nconst { ${cleanedNames} } = pkg__interop`
|
|
474
|
+
}
|
|
475
|
+
return m
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
// Write to a secondary temp file
|
|
479
|
+
const os = await import('os')
|
|
480
|
+
const path = await import('path')
|
|
481
|
+
const fallbackTemp = path.default.join(os.default.tmpdir(), `helper-fallback-${Date.now()}.mjs`)
|
|
482
|
+
fs.writeFileSync(fallbackTemp, content, 'utf8')
|
|
483
|
+
try {
|
|
484
|
+
const mod = await import(fallbackTemp)
|
|
485
|
+
HelperClass = mod.default || mod
|
|
486
|
+
debug(`helper ${helperName} loaded from transpiled JS after CommonJS interop fix: ${fallbackTemp}`)
|
|
487
|
+
} finally {
|
|
488
|
+
try {
|
|
489
|
+
fs.unlinkSync(fallbackTemp)
|
|
490
|
+
} catch (e) {}
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
throw importTempErr
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
} finally {
|
|
497
|
+
// Cleanup transpiled temporary files
|
|
498
|
+
const filesToClean = Array.isArray(allTempFiles) ? allTempFiles : [allTempFiles]
|
|
499
|
+
cleanupTempFiles(filesToClean)
|
|
500
|
+
}
|
|
501
|
+
} catch (importErr) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
`Helper '${helperName}' is a TypeScript file but could not be loaded.\n` +
|
|
504
|
+
`Path: ${moduleName}\n` +
|
|
505
|
+
`Error: ${importErr.message}\n\n` +
|
|
506
|
+
`To load TypeScript helpers, install 'typescript' in your project or use an ESM loader (e.g. tsx):\n` +
|
|
507
|
+
` npm install --save-dev typescript\n` +
|
|
508
|
+
` OR run CodeceptJS with an ESM loader: NODE_OPTIONS='--import tsx' npx codeceptjs run\n\n` +
|
|
509
|
+
`CodeceptJS will transpile TypeScript helpers automatically at runtime if 'typescript' is available.`,
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
} else if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
|
|
396
513
|
// This is an ESM module, use dynamic import
|
|
397
514
|
try {
|
|
398
515
|
const pathModule = await import('path')
|
|
@@ -677,24 +794,23 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
677
794
|
// Use dynamic import for both ESM and CJS modules
|
|
678
795
|
let importPath = modulePath
|
|
679
796
|
let tempJsFile = null
|
|
680
|
-
|
|
797
|
+
|
|
681
798
|
if (typeof importPath === 'string') {
|
|
682
799
|
const ext = path.extname(importPath)
|
|
683
|
-
|
|
800
|
+
|
|
684
801
|
// Handle TypeScript files
|
|
685
802
|
if (ext === '.ts') {
|
|
686
803
|
try {
|
|
687
804
|
// Use the TypeScript transpilation utility
|
|
688
805
|
const typescript = await import('typescript')
|
|
689
806
|
const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
|
|
690
|
-
|
|
807
|
+
|
|
691
808
|
debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
|
|
692
|
-
|
|
809
|
+
|
|
693
810
|
// Attach cleanup handler
|
|
694
811
|
importPath = tempFile
|
|
695
812
|
// Store temp files list in a way that cleanup can access them
|
|
696
813
|
tempJsFile = allTempFiles
|
|
697
|
-
|
|
698
814
|
} catch (tsError) {
|
|
699
815
|
throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
|
|
700
816
|
}
|
|
@@ -703,7 +819,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
703
819
|
importPath = `${importPath}.js`
|
|
704
820
|
}
|
|
705
821
|
}
|
|
706
|
-
|
|
822
|
+
|
|
707
823
|
let obj
|
|
708
824
|
try {
|
|
709
825
|
obj = await import(importPath)
|
package/lib/parser.js
CHANGED
|
@@ -17,7 +17,18 @@ export const getParamsToString = function (fn) {
|
|
|
17
17
|
function getParams(fn) {
|
|
18
18
|
if (fn.isSinonProxy) return []
|
|
19
19
|
try {
|
|
20
|
-
|
|
20
|
+
// Convert arrow functions to regular functions for parsing
|
|
21
|
+
let fnString = fn.toString()
|
|
22
|
+
// Handle async arrow functions: async (...) => expr becomes async function(...) { return expr }
|
|
23
|
+
// Handle async arrow functions: async (...) => { ... } becomes async function(...) { ... }
|
|
24
|
+
fnString = fnString.replace(/^async\s*(\([^)]*\))\s*=>\s*\{/, 'async function$1 {')
|
|
25
|
+
fnString = fnString.replace(/^async\s*(\([^)]*\))\s*=>\s*(.+)$/, 'async function$1 { return $2 }')
|
|
26
|
+
// Handle regular arrow functions: (...) => expr becomes function(...) { return expr }
|
|
27
|
+
// Handle regular arrow functions: (...) => { ... } becomes function(...) { ... }
|
|
28
|
+
fnString = fnString.replace(/^(\([^)]*\))\s*=>\s*\{/, 'function$1 {')
|
|
29
|
+
fnString = fnString.replace(/^(\([^)]*\))\s*=>\s*(.+)$/, 'function$1 { return $2 }')
|
|
30
|
+
|
|
31
|
+
const reflected = parser.parse(fnString)
|
|
21
32
|
if (reflected.args.length > 1 || reflected.args[0] === 'I') {
|
|
22
33
|
output.error('Error: old CodeceptJS v2 format detected. Upgrade your project to the new format -> https://bit.ly/codecept3Up')
|
|
23
34
|
}
|
package/lib/utils/typescript.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path'
|
|
|
4
4
|
/**
|
|
5
5
|
* Transpile TypeScript files to ES modules with CommonJS shim support
|
|
6
6
|
* Handles recursive transpilation of imported TypeScript files
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @param {string} mainFilePath - Path to the main TypeScript file to transpile
|
|
9
9
|
* @param {object} typescript - TypeScript compiler instance
|
|
10
10
|
* @returns {Promise<{tempFile: string, allTempFiles: string[]}>} - Main temp file and all temp files created
|
|
@@ -16,9 +16,9 @@ export async function transpileTypeScript(mainFilePath, typescript) {
|
|
|
16
16
|
* Transpile a single TypeScript file to JavaScript
|
|
17
17
|
* Injects CommonJS shims (require, module, exports, __dirname, __filename) as needed
|
|
18
18
|
*/
|
|
19
|
-
const transpileTS =
|
|
19
|
+
const transpileTS = filePath => {
|
|
20
20
|
const tsContent = fs.readFileSync(filePath, 'utf8')
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Transpile TypeScript to JavaScript with ES module output
|
|
23
23
|
let jsContent = transpile(tsContent, {
|
|
24
24
|
module: 99, // ModuleKind.ESNext
|
|
@@ -29,16 +29,16 @@ export async function transpileTypeScript(mainFilePath, typescript) {
|
|
|
29
29
|
suppressOutputPathCheck: true,
|
|
30
30
|
skipLibCheck: true,
|
|
31
31
|
})
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
// Check if the code uses CommonJS globals
|
|
34
34
|
const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent)
|
|
35
35
|
const usesRequire = /\brequire\s*\(/.test(jsContent)
|
|
36
36
|
const usesModuleExports = /\b(module\.exports|exports\.)/.test(jsContent)
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
if (usesCommonJSGlobals || usesRequire || usesModuleExports) {
|
|
39
39
|
// Inject ESM equivalents at the top of the file
|
|
40
40
|
let esmGlobals = ''
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
if (usesRequire || usesModuleExports) {
|
|
43
43
|
// IMPORTANT: Use the original .ts file path as the base for require()
|
|
44
44
|
// This ensures dynamic require() calls work with relative paths from the original file location
|
|
@@ -81,7 +81,7 @@ const exports = module.exports;
|
|
|
81
81
|
|
|
82
82
|
`
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
if (usesCommonJSGlobals) {
|
|
86
86
|
// For __dirname and __filename, also use the original file path
|
|
87
87
|
const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}`
|
|
@@ -92,48 +92,49 @@ const __dirname = __dirname_fn(__filename);
|
|
|
92
92
|
|
|
93
93
|
`
|
|
94
94
|
}
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
jsContent = esmGlobals + jsContent
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
// If module.exports is used, we need to export it as default
|
|
99
99
|
if (usesModuleExports) {
|
|
100
100
|
jsContent += `\nexport default module.exports;\n`
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
return jsContent
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
// Create a map to track transpiled files
|
|
108
108
|
const transpiledFiles = new Map()
|
|
109
109
|
const baseDir = path.dirname(mainFilePath)
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
// Recursive function to transpile a file and all its TypeScript dependencies
|
|
112
|
-
const transpileFileAndDeps =
|
|
112
|
+
const transpileFileAndDeps = filePath => {
|
|
113
113
|
// Already transpiled, skip
|
|
114
114
|
if (transpiledFiles.has(filePath)) {
|
|
115
115
|
return
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
// Transpile this file
|
|
119
119
|
let jsContent = transpileTS(filePath)
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
// Find all relative TypeScript imports in this file
|
|
122
|
-
|
|
122
|
+
// Match imports that start with ./ or ../
|
|
123
|
+
const importRegex = /from\s+['"](\.\.?\/[^'"]+?)(?:\.ts)?['"]/g
|
|
123
124
|
let match
|
|
124
125
|
const imports = []
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
while ((match = importRegex.exec(jsContent)) !== null) {
|
|
127
128
|
imports.push(match[1])
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
+
|
|
130
131
|
// Get the base directory for this file
|
|
131
132
|
const fileBaseDir = path.dirname(filePath)
|
|
132
|
-
|
|
133
|
+
|
|
133
134
|
// Recursively transpile each imported TypeScript file
|
|
134
135
|
for (const relativeImport of imports) {
|
|
135
136
|
let importedPath = path.resolve(fileBaseDir, relativeImport)
|
|
136
|
-
|
|
137
|
+
|
|
137
138
|
// Handle .js extensions that might actually be .ts files
|
|
138
139
|
if (importedPath.endsWith('.js')) {
|
|
139
140
|
const tsVersion = importedPath.replace(/\.js$/, '.ts')
|
|
@@ -141,7 +142,7 @@ const __dirname = __dirname_fn(__filename);
|
|
|
141
142
|
importedPath = tsVersion
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
|
|
145
146
|
// Try adding .ts extension if file doesn't exist and no extension provided
|
|
146
147
|
if (!path.extname(importedPath)) {
|
|
147
148
|
const tsPath = importedPath + '.ts'
|
|
@@ -155,68 +156,76 @@ const __dirname = __dirname_fn(__filename);
|
|
|
155
156
|
continue
|
|
156
157
|
}
|
|
157
158
|
}
|
|
159
|
+
} else if (importedPath.match(/\.[^./\\]+$/)) {
|
|
160
|
+
// Has an extension that's not .ts - check if .ts version exists by appending .ts
|
|
161
|
+
const tsPath = importedPath + '.ts'
|
|
162
|
+
if (fs.existsSync(tsPath)) {
|
|
163
|
+
importedPath = tsPath
|
|
164
|
+
}
|
|
158
165
|
}
|
|
159
|
-
|
|
166
|
+
|
|
160
167
|
// If it's a TypeScript file, recursively transpile it and its dependencies
|
|
161
168
|
if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
|
|
162
169
|
transpileFileAndDeps(importedPath)
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
|
-
|
|
172
|
+
|
|
166
173
|
// After all dependencies are transpiled, rewrite imports in this file
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
return `from '${relPath}'`
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Try with .ts extension
|
|
187
|
-
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
|
|
188
|
-
|
|
189
|
-
// If we transpiled this file, use the temp file
|
|
190
|
-
if (transpiledFiles.has(tsPath)) {
|
|
191
|
-
const tempFile = transpiledFiles.get(tsPath)
|
|
192
|
-
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
174
|
+
// IMPORTANT: We need to calculate temp file location first so we can compute correct relative paths
|
|
175
|
+
const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
|
|
176
|
+
const tempFileDir = path.dirname(tempFile)
|
|
177
|
+
|
|
178
|
+
jsContent = jsContent.replace(/from\s+['"](\.\.?\/[^'"]+?)(?:\.ts)?['"]/g, (match, importPath) => {
|
|
179
|
+
let resolvedPath = path.resolve(fileBaseDir, importPath)
|
|
180
|
+
|
|
181
|
+
// Handle .js extension that might be .ts
|
|
182
|
+
if (resolvedPath.endsWith('.js')) {
|
|
183
|
+
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
|
|
184
|
+
if (transpiledFiles.has(tsVersion)) {
|
|
185
|
+
const importedTempFile = transpiledFiles.get(tsVersion)
|
|
186
|
+
// Calculate relative path from THIS temp file to the imported temp file
|
|
187
|
+
const relPath = path.relative(tempFileDir, importedTempFile).replace(/\\/g, '/')
|
|
193
188
|
// Ensure the path starts with ./
|
|
194
189
|
if (!relPath.startsWith('.')) {
|
|
195
190
|
return `from './${relPath}'`
|
|
196
191
|
}
|
|
197
192
|
return `from '${relPath}'`
|
|
198
193
|
}
|
|
199
|
-
|
|
200
|
-
// Otherwise, keep the import as-is
|
|
201
|
-
return match
|
|
202
194
|
}
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
|
|
196
|
+
// Try with .ts extension
|
|
197
|
+
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
|
|
198
|
+
|
|
199
|
+
// If we transpiled this file, use the temp file
|
|
200
|
+
if (transpiledFiles.has(tsPath)) {
|
|
201
|
+
const importedTempFile = transpiledFiles.get(tsPath)
|
|
202
|
+
// Calculate relative path from THIS temp file to the imported temp file
|
|
203
|
+
const relPath = path.relative(tempFileDir, importedTempFile).replace(/\\/g, '/')
|
|
204
|
+
// Ensure the path starts with ./
|
|
205
|
+
if (!relPath.startsWith('.')) {
|
|
206
|
+
return `from './${relPath}'`
|
|
207
|
+
}
|
|
208
|
+
return `from '${relPath}'`
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Otherwise, keep the import as-is (for npm packages)
|
|
212
|
+
return match
|
|
213
|
+
})
|
|
214
|
+
|
|
205
215
|
// Write the transpiled file with updated imports
|
|
206
|
-
const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
|
|
207
216
|
fs.writeFileSync(tempFile, jsContent)
|
|
208
217
|
transpiledFiles.set(filePath, tempFile)
|
|
209
218
|
}
|
|
210
|
-
|
|
219
|
+
|
|
211
220
|
// Start recursive transpilation from the main file
|
|
212
221
|
transpileFileAndDeps(mainFilePath)
|
|
213
|
-
|
|
222
|
+
|
|
214
223
|
// Get the main transpiled file
|
|
215
224
|
const tempJsFile = transpiledFiles.get(mainFilePath)
|
|
216
|
-
|
|
225
|
+
|
|
217
226
|
// Store all temp files for cleanup
|
|
218
227
|
const allTempFiles = Array.from(transpiledFiles.values())
|
|
219
|
-
|
|
228
|
+
|
|
220
229
|
return { tempFile: tempJsFile, allTempFiles }
|
|
221
230
|
}
|
|
222
231
|
|