codeceptjs 4.0.0-beta.21 → 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/container.js +94 -102
- package/lib/parser.js +9 -5
- package/lib/utils/typescript.js +68 -59
- package/package.json +1 -1
package/lib/container.js
CHANGED
|
@@ -31,7 +31,7 @@ async function ensureTsxLoader() {
|
|
|
31
31
|
const { createRequire } = await import('module')
|
|
32
32
|
const { pathToFileURL } = await import('url')
|
|
33
33
|
const userRequire = createRequire(pathToFileURL(path.join(global.codecept_dir || process.cwd(), 'package.json')).href)
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
// Try to resolve tsx from user's project
|
|
36
36
|
try {
|
|
37
37
|
userRequire.resolve('tsx')
|
|
@@ -77,7 +77,7 @@ let container = {
|
|
|
77
77
|
translation: {},
|
|
78
78
|
/** @type {Result | null} */
|
|
79
79
|
result: null,
|
|
80
|
-
sharedKeys: new Set() // Track keys shared via share() function
|
|
80
|
+
sharedKeys: new Set(), // Track keys shared via share() function
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
@@ -295,10 +295,10 @@ class Container {
|
|
|
295
295
|
// Instead of using append which replaces the entire container,
|
|
296
296
|
// directly update the support object to maintain proxy references
|
|
297
297
|
Object.assign(container.support, data)
|
|
298
|
-
|
|
298
|
+
|
|
299
299
|
// Track which keys were explicitly shared
|
|
300
300
|
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
301
|
-
|
|
301
|
+
|
|
302
302
|
if (!options.local) {
|
|
303
303
|
WorkerStorage.share(data)
|
|
304
304
|
}
|
|
@@ -333,39 +333,30 @@ async function createHelpers(config) {
|
|
|
333
333
|
helperName = HelperClass.constructor.name
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
debug(`helper ${helperName} resolved type: ${typeof ResolvedHelperClass}`, ResolvedHelperClass)
|
|
346
|
-
|
|
347
|
-
// Extract default export from ESM module wrapper if needed
|
|
348
|
-
if (ResolvedHelperClass && ResolvedHelperClass.__esModule && ResolvedHelperClass.default) {
|
|
349
|
-
ResolvedHelperClass = ResolvedHelperClass.default
|
|
350
|
-
debug(`extracted default export for ${helperName}, new type: ${typeof ResolvedHelperClass}`)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (typeof ResolvedHelperClass !== 'function') {
|
|
354
|
-
throw new Error(`Helper '${helperName}' is not a class. Got: ${typeof ResolvedHelperClass}`)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
checkHelperRequirements(ResolvedHelperClass)
|
|
358
|
-
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
359
|
-
if (helpers[helperName]._init) await helpers[helperName]._init()
|
|
360
|
-
debug(`helper ${helperName} async initialized`)
|
|
361
|
-
})
|
|
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`)
|
|
362
345
|
continue
|
|
363
|
-
} else {
|
|
364
|
-
HelperClass = helperResult
|
|
365
346
|
}
|
|
366
347
|
}
|
|
367
348
|
|
|
368
|
-
//
|
|
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
|
|
369
360
|
if (isAsyncFunction(HelperClass)) {
|
|
370
361
|
helpers[helperName] = {}
|
|
371
362
|
|
|
@@ -396,7 +387,7 @@ async function createHelpers(config) {
|
|
|
396
387
|
|
|
397
388
|
// Wait for all async helpers to be resolved before calling _init
|
|
398
389
|
await asyncHelperPromise
|
|
399
|
-
|
|
390
|
+
|
|
400
391
|
for (const name in helpers) {
|
|
401
392
|
if (helpers[name]._init) await helpers[name]._init()
|
|
402
393
|
}
|
|
@@ -441,81 +432,83 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
|
441
432
|
HelperClass = mod.default || mod
|
|
442
433
|
} catch (err) {
|
|
443
434
|
if (err.code === 'ERR_UNKNOWN_FILE_EXTENSION' || (err.message && err.message.includes('Unknown file extension'))) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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)
|
|
449
440
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
}
|
|
457
448
|
|
|
458
|
-
|
|
459
|
-
|
|
449
|
+
const { tempFile, allTempFiles } = await transpileTypeScript(absolutePath, typescript)
|
|
450
|
+
debug(`Transpiled TypeScript helper: ${moduleName} -> ${tempFile}`)
|
|
460
451
|
|
|
452
|
+
try {
|
|
461
453
|
try {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return `import pkg__interop from ${q}${modName}${q};\nconst { ${cleanedNames} } = pkg__interop`
|
|
483
|
-
}
|
|
484
|
-
return m
|
|
485
|
-
})
|
|
486
|
-
|
|
487
|
-
// Write to a secondary temp file
|
|
488
|
-
const os = await import('os')
|
|
489
|
-
const path = await import('path')
|
|
490
|
-
const fallbackTemp = path.default.join(os.default.tmpdir(), `helper-fallback-${Date.now()}.mjs`)
|
|
491
|
-
fs.writeFileSync(fallbackTemp, content, 'utf8')
|
|
492
|
-
try {
|
|
493
|
-
const mod = await import(fallbackTemp)
|
|
494
|
-
HelperClass = mod.default || mod
|
|
495
|
-
debug(`helper ${helperName} loaded from transpiled JS after CommonJS interop fix: ${fallbackTemp}`)
|
|
496
|
-
} finally {
|
|
497
|
-
try { fs.unlinkSync(fallbackTemp) } catch (e) {}
|
|
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`
|
|
498
474
|
}
|
|
499
|
-
|
|
500
|
-
|
|
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) {}
|
|
501
491
|
}
|
|
492
|
+
} else {
|
|
493
|
+
throw importTempErr
|
|
502
494
|
}
|
|
503
|
-
} finally {
|
|
504
|
-
// Cleanup transpiled temporary files
|
|
505
|
-
const filesToClean = Array.isArray(allTempFiles) ? allTempFiles : [allTempFiles]
|
|
506
|
-
cleanupTempFiles(filesToClean)
|
|
507
495
|
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
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` +
|
|
511
504
|
`Path: ${moduleName}\n` +
|
|
512
505
|
`Error: ${importErr.message}\n\n` +
|
|
513
506
|
`To load TypeScript helpers, install 'typescript' in your project or use an ESM loader (e.g. tsx):\n` +
|
|
514
507
|
` npm install --save-dev typescript\n` +
|
|
515
508
|
` OR run CodeceptJS with an ESM loader: NODE_OPTIONS='--import tsx' npx codeceptjs run\n\n` +
|
|
516
|
-
`CodeceptJS will transpile TypeScript helpers automatically at runtime if 'typescript' is available
|
|
517
|
-
|
|
518
|
-
|
|
509
|
+
`CodeceptJS will transpile TypeScript helpers automatically at runtime if 'typescript' is available.`,
|
|
510
|
+
)
|
|
511
|
+
}
|
|
519
512
|
} else if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
|
|
520
513
|
// This is an ESM module, use dynamic import
|
|
521
514
|
try {
|
|
@@ -801,24 +794,23 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
801
794
|
// Use dynamic import for both ESM and CJS modules
|
|
802
795
|
let importPath = modulePath
|
|
803
796
|
let tempJsFile = null
|
|
804
|
-
|
|
797
|
+
|
|
805
798
|
if (typeof importPath === 'string') {
|
|
806
799
|
const ext = path.extname(importPath)
|
|
807
|
-
|
|
800
|
+
|
|
808
801
|
// Handle TypeScript files
|
|
809
802
|
if (ext === '.ts') {
|
|
810
803
|
try {
|
|
811
804
|
// Use the TypeScript transpilation utility
|
|
812
805
|
const typescript = await import('typescript')
|
|
813
806
|
const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
|
|
814
|
-
|
|
807
|
+
|
|
815
808
|
debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
|
|
816
|
-
|
|
809
|
+
|
|
817
810
|
// Attach cleanup handler
|
|
818
811
|
importPath = tempFile
|
|
819
812
|
// Store temp files list in a way that cleanup can access them
|
|
820
813
|
tempJsFile = allTempFiles
|
|
821
|
-
|
|
822
814
|
} catch (tsError) {
|
|
823
815
|
throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
|
|
824
816
|
}
|
|
@@ -827,7 +819,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
|
|
|
827
819
|
importPath = `${importPath}.js`
|
|
828
820
|
}
|
|
829
821
|
}
|
|
830
|
-
|
|
822
|
+
|
|
831
823
|
let obj
|
|
832
824
|
try {
|
|
833
825
|
obj = await import(importPath)
|
package/lib/parser.js
CHANGED
|
@@ -19,11 +19,15 @@ function getParams(fn) {
|
|
|
19
19
|
try {
|
|
20
20
|
// Convert arrow functions to regular functions for parsing
|
|
21
21
|
let fnString = fn.toString()
|
|
22
|
-
// Handle async arrow functions: async (...) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
fnString = fnString.replace(/^(\([^)]*\))\s
|
|
26
|
-
|
|
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
|
+
|
|
27
31
|
const reflected = parser.parse(fnString)
|
|
28
32
|
if (reflected.args.length > 1 || reflected.args[0] === 'I') {
|
|
29
33
|
output.error('Error: old CodeceptJS v2 format detected. Upgrade your project to the new format -> https://bit.ly/codecept3Up')
|
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
|
|