Skip to content

Commit 20c0e8f

Browse files
authored
fix: import in config file (#5373)
1 parent bff313a commit 20c0e8f

File tree

3 files changed

+86
-33
lines changed

3 files changed

+86
-33
lines changed

lib/config.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,27 +156,32 @@ async function loadConfigFile(configFile) {
156156
try {
157157
// For .ts files, try to compile and load as JavaScript
158158
if (extensionName === '.ts') {
159+
let transpileError = null
160+
let tempFile = null
161+
let allTempFiles = null
162+
let fileMapping = null
163+
159164
try {
160165
// Use the TypeScript transpilation utility
161166
const typescript = require('typescript')
162-
const { tempFile, allTempFiles, fileMapping } = await transpileTypeScript(configFile, typescript)
163-
164-
try {
165-
configModule = await import(tempFile)
166-
cleanupTempFiles(allTempFiles)
167-
} catch (err) {
167+
const result = await transpileTypeScript(configFile, typescript)
168+
tempFile = result.tempFile
169+
allTempFiles = result.allTempFiles
170+
fileMapping = result.fileMapping
171+
172+
configModule = await import(tempFile)
173+
cleanupTempFiles(allTempFiles)
174+
} catch (err) {
175+
transpileError = err
176+
if (fileMapping) {
168177
fixErrorStack(err, fileMapping)
169-
cleanupTempFiles(allTempFiles)
170-
throw err
171178
}
172-
} catch (tsError) {
173-
// If TypeScript compilation fails, fallback to ts-node
174-
try {
175-
require('ts-node/register')
176-
configModule = require(configFile)
177-
} catch (tsNodeError) {
178-
throw new Error(`Failed to load TypeScript config: ${tsError.message}`)
179+
if (allTempFiles) {
180+
cleanupTempFiles(allTempFiles)
179181
}
182+
// Throw immediately with the actual error - don't fall back to ts-node
183+
// as it will mask the real error with "Unexpected token 'export'"
184+
throw err
180185
}
181186
} else {
182187
// Try ESM import first for JS files

lib/utils/typescript.js

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,20 +118,25 @@ const __dirname = __dirname_fn(__filename);
118118
// Transpile this file
119119
let jsContent = transpileTS(filePath)
120120

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

126127
while ((match = importRegex.exec(jsContent)) !== null) {
127-
imports.push(match[1])
128+
imports.push({ path: match[1], type: 'import' })
129+
}
130+
131+
while ((match = requireRegex.exec(jsContent)) !== null) {
132+
imports.push({ path: match[1], type: 'require' })
128133
}
129134

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

133138
// Recursively transpile each imported TypeScript file
134-
for (const relativeImport of imports) {
139+
for (const { path: relativeImport } of imports) {
135140
let importedPath = path.resolve(fileBaseDir, relativeImport)
136141

137142
// Handle .js extensions that might actually be .ts files
@@ -153,11 +158,17 @@ const __dirname = __dirname_fn(__filename);
153158
if (fs.existsSync(tsPath)) {
154159
importedPath = tsPath
155160
} else {
156-
// Try .js extension as well
157-
const jsPath = importedPath + '.js'
158-
if (fs.existsSync(jsPath)) {
159-
// Skip .js files, they don't need transpilation
160-
continue
161+
// Try index.ts for directory imports
162+
const indexTsPath = path.join(importedPath, 'index.ts')
163+
if (fs.existsSync(indexTsPath)) {
164+
importedPath = indexTsPath
165+
} else {
166+
// Try .js extension as well
167+
const jsPath = importedPath + '.js'
168+
if (fs.existsSync(jsPath)) {
169+
// Skip .js files, they don't need transpilation
170+
continue
171+
}
161172
}
162173
}
163174
}
@@ -181,13 +192,11 @@ const __dirname = __dirname_fn(__filename);
181192
if (transpiledFiles.has(tsVersion)) {
182193
const tempFile = transpiledFiles.get(tsVersion)
183194
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
184-
// Ensure the path starts with ./
185195
if (!relPath.startsWith('.')) {
186196
return `from './${relPath}'`
187197
}
188198
return `from '${relPath}'`
189199
}
190-
// Keep .js extension as-is (might be a real .js file)
191200
return match
192201
}
193202

@@ -198,26 +207,65 @@ const __dirname = __dirname_fn(__filename);
198207
if (transpiledFiles.has(tsPath)) {
199208
const tempFile = transpiledFiles.get(tsPath)
200209
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
201-
// Ensure the path starts with ./
202210
if (!relPath.startsWith('.')) {
203211
return `from './${relPath}'`
204212
}
205213
return `from '${relPath}'`
206214
}
207215

208-
// If the import doesn't have a standard module extension (.js, .mjs, .cjs, .json)
209-
// add .js for ESM compatibility
210-
// This handles cases where:
211-
// 1. Import has no real extension (e.g., "./utils" or "./helper")
212-
// 2. Import has a non-standard extension that's part of the name (e.g., "./abstract.helper")
216+
// Try index.ts for directory imports
217+
const indexTsPath = path.join(resolvedPath, 'index.ts')
218+
if (transpiledFiles.has(indexTsPath)) {
219+
const tempFile = transpiledFiles.get(indexTsPath)
220+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
221+
if (!relPath.startsWith('.')) {
222+
return `from './${relPath}'`
223+
}
224+
return `from '${relPath}'`
225+
}
226+
227+
// If the import doesn't have a standard module extension, add .js for ESM compatibility
213228
const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
214229
const hasStandardExtension = standardExtensions.includes(originalExt.toLowerCase())
215230

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

220-
// Otherwise, keep the import as-is
235+
return match
236+
}
237+
)
238+
239+
// Also rewrite require() calls to point to transpiled TypeScript files
240+
jsContent = jsContent.replace(
241+
/require\s*\(\s*['"](\.[^'"]+?)(?:\.ts)?['"]\s*\)/g,
242+
(match, requirePath) => {
243+
let resolvedPath = path.resolve(fileBaseDir, requirePath)
244+
245+
// Handle .js extension that might be .ts
246+
if (resolvedPath.endsWith('.js')) {
247+
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
248+
if (transpiledFiles.has(tsVersion)) {
249+
const tempFile = transpiledFiles.get(tsVersion)
250+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
251+
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
252+
return `require('${finalPath}')`
253+
}
254+
return match
255+
}
256+
257+
// Try with .ts extension
258+
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
259+
260+
// If we transpiled this file, use the temp file
261+
if (transpiledFiles.has(tsPath)) {
262+
const tempFile = transpiledFiles.get(tsPath)
263+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
264+
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
265+
return `require('${finalPath}')`
266+
}
267+
268+
// Otherwise, keep the require as-is
221269
return match
222270
}
223271
)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeceptjs",
3-
"version": "4.0.2-beta.17",
3+
"version": "4.0.2-beta.19",
44
"type": "module",
55
"description": "Supercharged End 2 End Testing Framework for NodeJS",
66
"keywords": [

0 commit comments

Comments
 (0)