Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,27 +156,32 @@ async function loadConfigFile(configFile) {
try {
// For .ts files, try to compile and load as JavaScript
if (extensionName === '.ts') {
let transpileError = null
let tempFile = null
let allTempFiles = null
let fileMapping = null

try {
// Use the TypeScript transpilation utility
const typescript = require('typescript')
const { tempFile, allTempFiles, fileMapping } = await transpileTypeScript(configFile, typescript)

try {
configModule = await import(tempFile)
cleanupTempFiles(allTempFiles)
} catch (err) {
const result = await transpileTypeScript(configFile, typescript)
tempFile = result.tempFile
allTempFiles = result.allTempFiles
fileMapping = result.fileMapping

configModule = await import(tempFile)
cleanupTempFiles(allTempFiles)
} catch (err) {
transpileError = err
if (fileMapping) {
fixErrorStack(err, fileMapping)
cleanupTempFiles(allTempFiles)
throw err
}
} catch (tsError) {
// If TypeScript compilation fails, fallback to ts-node
try {
require('ts-node/register')
configModule = require(configFile)
} catch (tsNodeError) {
throw new Error(`Failed to load TypeScript config: ${tsError.message}`)
if (allTempFiles) {
cleanupTempFiles(allTempFiles)
}
// Throw immediately with the actual error - don't fall back to ts-node
// as it will mask the real error with "Unexpected token 'export'"
throw err
}
} else {
// Try ESM import first for JS files
Expand Down
82 changes: 65 additions & 17 deletions lib/utils/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,25 @@ const __dirname = __dirname_fn(__filename);
// Transpile this file
let jsContent = transpileTS(filePath)

// Find all relative TypeScript imports in this file
// Find all relative TypeScript imports in this file (both ESM imports and require() calls)
const importRegex = /from\s+['"](\.[^'"]+?)(?:\.ts)?['"]/g
const requireRegex = /require\s*\(\s*['"](\.[^'"]+?)(?:\.ts)?['"]\s*\)/g
let match
const imports = []

while ((match = importRegex.exec(jsContent)) !== null) {
imports.push(match[1])
imports.push({ path: match[1], type: 'import' })
}

while ((match = requireRegex.exec(jsContent)) !== null) {
imports.push({ path: match[1], type: 'require' })
}

// Get the base directory for this file
const fileBaseDir = path.dirname(filePath)

// Recursively transpile each imported TypeScript file
for (const relativeImport of imports) {
for (const { path: relativeImport } of imports) {
let importedPath = path.resolve(fileBaseDir, relativeImport)

// Handle .js extensions that might actually be .ts files
Expand All @@ -153,11 +158,17 @@ const __dirname = __dirname_fn(__filename);
if (fs.existsSync(tsPath)) {
importedPath = tsPath
} else {
// Try .js extension as well
const jsPath = importedPath + '.js'
if (fs.existsSync(jsPath)) {
// Skip .js files, they don't need transpilation
continue
// Try index.ts for directory imports
const indexTsPath = path.join(importedPath, 'index.ts')
if (fs.existsSync(indexTsPath)) {
importedPath = indexTsPath
} else {
// Try .js extension as well
const jsPath = importedPath + '.js'
if (fs.existsSync(jsPath)) {
// Skip .js files, they don't need transpilation
continue
}
}
}
}
Expand All @@ -181,13 +192,11 @@ const __dirname = __dirname_fn(__filename);
if (transpiledFiles.has(tsVersion)) {
const tempFile = transpiledFiles.get(tsVersion)
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
// Ensure the path starts with ./
if (!relPath.startsWith('.')) {
return `from './${relPath}'`
}
return `from '${relPath}'`
}
// Keep .js extension as-is (might be a real .js file)
return match
}

Expand All @@ -198,26 +207,65 @@ const __dirname = __dirname_fn(__filename);
if (transpiledFiles.has(tsPath)) {
const tempFile = transpiledFiles.get(tsPath)
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
// Ensure the path starts with ./
if (!relPath.startsWith('.')) {
return `from './${relPath}'`
}
return `from '${relPath}'`
}

// If the import doesn't have a standard module extension (.js, .mjs, .cjs, .json)
// add .js for ESM compatibility
// This handles cases where:
// 1. Import has no real extension (e.g., "./utils" or "./helper")
// 2. Import has a non-standard extension that's part of the name (e.g., "./abstract.helper")
// Try index.ts for directory imports
const indexTsPath = path.join(resolvedPath, 'index.ts')
if (transpiledFiles.has(indexTsPath)) {
const tempFile = transpiledFiles.get(indexTsPath)
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
if (!relPath.startsWith('.')) {
return `from './${relPath}'`
}
return `from '${relPath}'`
}

// If the import doesn't have a standard module extension, add .js for ESM compatibility
const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
const hasStandardExtension = standardExtensions.includes(originalExt.toLowerCase())

if (!hasStandardExtension) {
return match.replace(importPath, importPath + '.js')
}

// Otherwise, keep the import as-is
return match
}
)

// Also rewrite require() calls to point to transpiled TypeScript files
jsContent = jsContent.replace(
/require\s*\(\s*['"](\.[^'"]+?)(?:\.ts)?['"]\s*\)/g,
(match, requirePath) => {
let resolvedPath = path.resolve(fileBaseDir, requirePath)

// Handle .js extension that might be .ts
if (resolvedPath.endsWith('.js')) {
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
if (transpiledFiles.has(tsVersion)) {
const tempFile = transpiledFiles.get(tsVersion)
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
return `require('${finalPath}')`
}
return match
}

// Try with .ts extension
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'

// If we transpiled this file, use the temp file
if (transpiledFiles.has(tsPath)) {
const tempFile = transpiledFiles.get(tsPath)
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
return `require('${finalPath}')`
}

// Otherwise, keep the require as-is
return match
}
)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeceptjs",
"version": "4.0.2-beta.17",
"version": "4.0.2-beta.19",
"type": "module",
"description": "Supercharged End 2 End Testing Framework for NodeJS",
"keywords": [
Expand Down