Skip to content

Commit 36c9d06

Browse files
committed
fix: TypeScript require() now tries extensions for paths with non-standard suffixes (e.g., environment.TEST)
- Fixed require() wrapper to only treat known extensions (.js, .cjs, .mjs, .json, .node) as real extensions - Paths like './environments/environment.TEST' will now try .TEST.js, .TEST.cjs, etc. - Previously, .TEST was treated as an extension, blocking the extension probe logic - Added test case for require() with dynamic environment-based paths - Bumped version to 4.0.0-beta.17
1 parent c74a4ef commit 36c9d06

File tree

10 files changed

+130
-2
lines changed

10 files changed

+130
-2
lines changed

lib/utils/typescript.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,37 @@ export async function transpileTypeScript(mainFilePath, typescript) {
4141
// This ensures dynamic require() calls work with relative paths from the original file location
4242
const originalFileUrl = `file://${filePath.replace(/\\/g, '/')}`
4343
esmGlobals += `import { createRequire } from 'module';
44-
const require = createRequire('${originalFileUrl}');
44+
import { extname as __extname } from 'path';
45+
const __baseRequire = createRequire('${originalFileUrl}');
46+
47+
// Wrap require to auto-resolve extensions (mimics CommonJS behavior)
48+
const require = (id) => {
49+
try {
50+
return __baseRequire(id);
51+
} catch (err) {
52+
// If module not found and it's a relative/absolute path without extension, try common extensions
53+
if (err.code === 'MODULE_NOT_FOUND' && (id.startsWith('./') || id.startsWith('../') || id.startsWith('/'))) {
54+
const ext = __extname(id);
55+
// Only treat known file extensions as real extensions (so names like .TEST don't block probing)
56+
const __knownExts = ['.js', '.cjs', '.mjs', '.json', '.node'];
57+
const hasKnownExt = ext && __knownExts.includes(ext.toLowerCase());
58+
if (!hasKnownExt) {
59+
// Try common extensions in order: .js, .cjs, .json, .node
60+
const extensions = ['.js', '.cjs', '.json', '.node'];
61+
for (const testExt of extensions) {
62+
try {
63+
return __baseRequire(id + testExt);
64+
} catch (e) {
65+
// Continue to next extension
66+
}
67+
}
68+
}
69+
}
70+
// Re-throw original error if all attempts failed
71+
throw err;
72+
}
73+
};
74+
4575
const module = { exports: {} };
4676
const exports = module.exports;
4777

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.0-beta.16",
3+
"version": "4.0.0-beta.17",
44
"type": "module",
55
"description": "Supercharged End 2 End Testing Framework for NodeJS",
66
"keywords": [

scripts/debug-transpile.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
import { transpileTypeScript } from '../lib/utils/typescript.js';
4+
import ts from 'typescript';
5+
6+
const target = process.argv[2];
7+
if (!target) {
8+
console.error('Usage: node debug-transpile.mjs <path-to-ts-file>');
9+
process.exit(1);
10+
}
11+
12+
const abs = path.resolve(target);
13+
console.log('Transpiling', abs);
14+
const result = await transpileTypeScript(abs, ts);
15+
console.log('Result:', result);
16+
console.log('Files exist:');
17+
for (const f of result.allTempFiles) {
18+
console.log(' -', f, 'exists?', fs.existsSync(f));
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { E2EEnvironment } from './environments';
2+
3+
export const config = {
4+
tests: './*_test.js',
5+
output: './output',
6+
helpers: {
7+
REST: {
8+
endpoint: E2EEnvironment.url,
9+
timeout: E2EEnvironment.timeout
10+
}
11+
},
12+
name: 'typescript-require-relative-test'
13+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Load environment configuration
2+
let env = process.env.E2E_ENV || "TEST";
3+
4+
if (process.env.PROD === 'true') {
5+
env = 'PROD';
6+
}
7+
8+
const environmentPath = `./environments/environment.${env}`;
9+
// Using require to load the file dynamically
10+
const environment = require(environmentPath);
11+
12+
environment.url = process.env.E2E_URL || environment.url;
13+
14+
if (environment.url.endsWith("/")) {
15+
// Remove tailing slash, since it would lead to problems with further configuration.
16+
environment.url = environment.url.slice(0, -1);
17+
}
18+
19+
// Setting default values on optional fields.
20+
if (!environment.urlApi) {
21+
environment.urlApi = `${environment.url}/api`;
22+
}
23+
24+
// Parse additional environment configurations.
25+
const environmentParsed = environment;
26+
environmentParsed.outputDir = `./../reports/e2e/results/`;
27+
environmentParsed.env = env;
28+
29+
// There is no other logger in the E2E environment.
30+
console.log(`Parsed E2E environment:`, environmentParsed);
31+
32+
export const E2EEnvironment = environmentParsed;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
url: 'https://test.example.com',
3+
timeout: 10000
4+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "typescript-require-relative",
3+
"version": "1.0.0",
4+
"type": "module"
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Simple test to check if require wrapper tries extensions
2+
const modulePath = './environments/environment.TEST';
3+
console.log('Testing require wrapper...');
4+
5+
try {
6+
const env = require(modulePath);
7+
console.log('SUCCESS: Loaded module without extension:', env);
8+
} catch (err) {
9+
console.log('FAILED: Could not load module:', err.message);
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const result = require('./environments/environment.TEST');
2+
console.log('SUCCESS! Loaded:', result);

test/unit/config_test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,17 @@ describe('Config', () => {
106106
expect(cfg.helpers.REST.timeout).to.equal(5000)
107107
expect(cfg.name).to.equal('typescript-directory-import-test')
108108
})
109+
110+
it('should load TypeScript config with require() for paths without extensions', async () => {
111+
const configPath = './test/data/typescript-require-relative/codecept.conf.ts'
112+
process.env.E2E_ENV = 'TEST'
113+
const cfg = await config.load(configPath)
114+
115+
expect(cfg).to.be.ok
116+
expect(cfg.helpers).to.have.property('REST')
117+
expect(cfg.helpers.REST.endpoint).to.equal('https://test.example.com')
118+
expect(cfg.helpers.REST.timeout).to.equal(10000)
119+
expect(cfg.name).to.equal('typescript-require-relative-test')
120+
delete process.env.E2E_ENV
121+
})
109122
})

0 commit comments

Comments
 (0)