codeceptjs 4.0.0-beta.12 → 4.0.0-beta.14

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/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
4
  import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
5
+ import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
5
6
 
6
7
  const defaultConfig = {
7
8
  output: './_output',
@@ -156,31 +157,15 @@ async function loadConfigFile(configFile) {
156
157
  // For .ts files, try to compile and load as JavaScript
157
158
  if (extensionName === '.ts') {
158
159
  try {
159
- // Try to load ts-node and compile the file
160
- const { transpile } = require('typescript')
161
- const tsContent = fs.readFileSync(configFile, 'utf8')
162
-
163
- // Transpile TypeScript to JavaScript with ES module output
164
- const jsContent = transpile(tsContent, {
165
- module: 99, // ModuleKind.ESNext
166
- target: 99, // ScriptTarget.ESNext
167
- esModuleInterop: true,
168
- allowSyntheticDefaultImports: true,
169
- })
170
-
171
- // Create a temporary JS file with .mjs extension to force ES module treatment
172
- const tempJsFile = configFile.replace('.ts', '.temp.mjs')
173
- fs.writeFileSync(tempJsFile, jsContent)
160
+ // Use the TypeScript transpilation utility
161
+ const typescript = require('typescript')
162
+ const { tempFile, allTempFiles } = await transpileTypeScript(configFile, typescript)
174
163
 
175
164
  try {
176
- configModule = await import(tempJsFile)
177
- // Clean up temp file
178
- fs.unlinkSync(tempJsFile)
165
+ configModule = await import(tempFile)
166
+ cleanupTempFiles(allTempFiles)
179
167
  } catch (err) {
180
- // Clean up temp file even on error
181
- if (fs.existsSync(tempJsFile)) {
182
- fs.unlinkSync(tempJsFile)
183
- }
168
+ cleanupTempFiles(allTempFiles)
184
169
  throw err
185
170
  }
186
171
  } catch (tsError) {
package/lib/container.js CHANGED
@@ -5,6 +5,7 @@ import debugModule from 'debug'
5
5
  const debug = debugModule('codeceptjs:container')
6
6
  import { MetaStep } from './step.js'
7
7
  import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
+ import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
8
9
  import Translation from './translation.js'
9
10
  import MochaFactory from './mocha/factory.js'
10
11
  import recorder from './recorder.js'
@@ -683,124 +684,14 @@ async function loadSupportObject(modulePath, supportObjectName) {
683
684
  // Handle TypeScript files
684
685
  if (ext === '.ts') {
685
686
  try {
686
- const { transpile } = await import('typescript')
687
+ // Use the TypeScript transpilation utility
688
+ const typescript = await import('typescript')
689
+ const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
687
690
 
688
- // Recursively transpile the file and its dependencies
689
- const transpileTS = (filePath) => {
690
- const tsContent = fs.readFileSync(filePath, 'utf8')
691
-
692
- // Transpile TypeScript to JavaScript with ES module output
693
- let jsContent = transpile(tsContent, {
694
- module: 99, // ModuleKind.ESNext
695
- target: 99, // ScriptTarget.ESNext
696
- esModuleInterop: true,
697
- allowSyntheticDefaultImports: true,
698
- })
699
-
700
- // Check if the code uses __dirname or __filename (CommonJS globals)
701
- const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent)
702
-
703
- if (usesCommonJSGlobals) {
704
- // Inject ESM equivalents at the top of the file
705
- const esmGlobals = `import { fileURLToPath as __fileURLToPath } from 'url';
706
- import { dirname as __dirname_fn } from 'path';
707
- const __filename = __fileURLToPath(import.meta.url);
708
- const __dirname = __dirname_fn(__filename);
709
-
710
- `
711
- jsContent = esmGlobals + jsContent
712
- }
713
-
714
- return jsContent
715
- }
716
-
717
- // Create a map to track transpiled files
718
- const transpiledFiles = new Map()
719
- const baseDir = path.dirname(importPath)
720
-
721
- // Transpile main file
722
- let jsContent = transpileTS(importPath)
723
-
724
- // Find and transpile all relative TypeScript imports
725
- // Match: import ... from './file' or '../file' or './file.ts'
726
- const importRegex = /from\s+['"](\..+?)(?:\.ts)?['"]/g
727
- let match
728
- const imports = []
729
-
730
- while ((match = importRegex.exec(jsContent)) !== null) {
731
- imports.push(match[1])
732
- }
733
-
734
- // Transpile each imported TypeScript file
735
- for (const relativeImport of imports) {
736
- let importedPath = path.resolve(baseDir, relativeImport)
737
-
738
- // Handle .js extensions that might actually be .ts files
739
- if (importedPath.endsWith('.js')) {
740
- const tsVersion = importedPath.replace(/\.js$/, '.ts')
741
- if (fs.existsSync(tsVersion)) {
742
- importedPath = tsVersion
743
- }
744
- }
745
-
746
- // Try adding .ts extension if file doesn't exist and no extension provided
747
- if (!path.extname(importedPath)) {
748
- if (fs.existsSync(importedPath + '.ts')) {
749
- importedPath = importedPath + '.ts'
750
- }
751
- }
752
-
753
- // If it's a TypeScript file, transpile it
754
- if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
755
- const transpiledImportContent = transpileTS(importedPath)
756
- const tempImportFile = importedPath.replace(/\.ts$/, '.temp.mjs')
757
- fs.writeFileSync(tempImportFile, transpiledImportContent)
758
- transpiledFiles.set(importedPath, tempImportFile)
759
- debug(`Transpiled dependency: ${importedPath} -> ${tempImportFile}`)
760
- }
761
- }
762
-
763
- // Replace imports in the main file to point to temp .mjs files
764
- jsContent = jsContent.replace(
765
- /from\s+['"](\..+?)(?:\.ts)?['"]/g,
766
- (match, importPath) => {
767
- let resolvedPath = path.resolve(baseDir, importPath)
768
-
769
- // Handle .js extension that might be .ts
770
- if (resolvedPath.endsWith('.js')) {
771
- const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
772
- if (transpiledFiles.has(tsVersion)) {
773
- const tempFile = transpiledFiles.get(tsVersion)
774
- const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
775
- return `from './${relPath}'`
776
- }
777
- }
778
-
779
- // Try with .ts extension
780
- const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
781
-
782
- // If we transpiled this file, use the temp file
783
- if (transpiledFiles.has(tsPath)) {
784
- const tempFile = transpiledFiles.get(tsPath)
785
- // Get relative path from main temp file to this temp file
786
- const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
787
- return `from './${relPath}'`
788
- }
789
-
790
- // Otherwise, keep the import as-is
791
- return match
792
- }
793
- )
794
-
795
- // Create a temporary JS file with .mjs extension for the main file
796
- tempJsFile = importPath.replace(/\.ts$/, '.temp.mjs')
797
- fs.writeFileSync(tempJsFile, jsContent)
798
-
799
- // Store all temp files for cleanup
800
- const allTempFiles = [tempJsFile, ...Array.from(transpiledFiles.values())]
691
+ debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
801
692
 
802
693
  // Attach cleanup handler
803
- importPath = tempJsFile
694
+ importPath = tempFile
804
695
  // Store temp files list in a way that cleanup can access them
805
696
  tempJsFile = allTempFiles
806
697
 
@@ -820,30 +711,14 @@ const __dirname = __dirname_fn(__filename);
820
711
  // Clean up temp files if created before rethrowing
821
712
  if (tempJsFile) {
822
713
  const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
823
- for (const file of filesToClean) {
824
- try {
825
- if (fs.existsSync(file)) {
826
- fs.unlinkSync(file)
827
- }
828
- } catch (cleanupError) {
829
- // Ignore cleanup errors
830
- }
831
- }
714
+ cleanupTempFiles(filesToClean)
832
715
  }
833
716
  throw importError
834
717
  } finally {
835
718
  // Clean up temp files if created
836
719
  if (tempJsFile) {
837
720
  const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
838
- for (const file of filesToClean) {
839
- try {
840
- if (fs.existsSync(file)) {
841
- fs.unlinkSync(file)
842
- }
843
- } catch (cleanupError) {
844
- // Ignore cleanup errors
845
- }
846
- }
721
+ cleanupTempFiles(filesToClean)
847
722
  }
848
723
  }
849
724
 
@@ -0,0 +1,169 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * Transpile TypeScript files to ES modules with CommonJS shim support
6
+ * Handles recursive transpilation of imported TypeScript files
7
+ *
8
+ * @param {string} mainFilePath - Path to the main TypeScript file to transpile
9
+ * @param {object} typescript - TypeScript compiler instance
10
+ * @returns {Promise<{tempFile: string, allTempFiles: string[]}>} - Main temp file and all temp files created
11
+ */
12
+ export async function transpileTypeScript(mainFilePath, typescript) {
13
+ const { transpile } = typescript
14
+
15
+ /**
16
+ * Transpile a single TypeScript file to JavaScript
17
+ * Injects CommonJS shims (require, module, exports, __dirname, __filename) as needed
18
+ */
19
+ const transpileTS = (filePath) => {
20
+ const tsContent = fs.readFileSync(filePath, 'utf8')
21
+
22
+ // Transpile TypeScript to JavaScript with ES module output
23
+ let jsContent = transpile(tsContent, {
24
+ module: 99, // ModuleKind.ESNext
25
+ target: 99, // ScriptTarget.ESNext
26
+ esModuleInterop: true,
27
+ allowSyntheticDefaultImports: true,
28
+ })
29
+
30
+ // Check if the code uses CommonJS globals
31
+ const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent)
32
+ const usesRequire = /\brequire\s*\(/.test(jsContent)
33
+ const usesModuleExports = /\b(module\.exports|exports\.)/.test(jsContent)
34
+
35
+ if (usesCommonJSGlobals || usesRequire || usesModuleExports) {
36
+ // Inject ESM equivalents at the top of the file
37
+ let esmGlobals = ''
38
+
39
+ if (usesRequire || usesModuleExports) {
40
+ esmGlobals += `import { createRequire } from 'module';
41
+ const require = createRequire(import.meta.url);
42
+ const module = { exports: {} };
43
+ const exports = module.exports;
44
+
45
+ `
46
+ }
47
+
48
+ if (usesCommonJSGlobals) {
49
+ esmGlobals += `import { fileURLToPath as __fileURLToPath } from 'url';
50
+ import { dirname as __dirname_fn } from 'path';
51
+ const __filename = __fileURLToPath(import.meta.url);
52
+ const __dirname = __dirname_fn(__filename);
53
+
54
+ `
55
+ }
56
+
57
+ jsContent = esmGlobals + jsContent
58
+
59
+ // If module.exports is used, we need to export it as default
60
+ if (usesModuleExports) {
61
+ jsContent += `\nexport default module.exports;\n`
62
+ }
63
+ }
64
+
65
+ return jsContent
66
+ }
67
+
68
+ // Create a map to track transpiled files
69
+ const transpiledFiles = new Map()
70
+ const baseDir = path.dirname(mainFilePath)
71
+
72
+ // Transpile main file
73
+ let jsContent = transpileTS(mainFilePath)
74
+
75
+ // Find and transpile all relative TypeScript imports
76
+ // Match: import ... from './file' or '../file' or './file.ts'
77
+ const importRegex = /from\s+['"](\..+?)(?:\.ts)?['"]/g
78
+ let match
79
+ const imports = []
80
+
81
+ while ((match = importRegex.exec(jsContent)) !== null) {
82
+ imports.push(match[1])
83
+ }
84
+
85
+ // Transpile each imported TypeScript file
86
+ for (const relativeImport of imports) {
87
+ let importedPath = path.resolve(baseDir, relativeImport)
88
+
89
+ // Handle .js extensions that might actually be .ts files
90
+ if (importedPath.endsWith('.js')) {
91
+ const tsVersion = importedPath.replace(/\.js$/, '.ts')
92
+ if (fs.existsSync(tsVersion)) {
93
+ importedPath = tsVersion
94
+ }
95
+ }
96
+
97
+ // Try adding .ts extension if file doesn't exist and no extension provided
98
+ if (!path.extname(importedPath)) {
99
+ if (fs.existsSync(importedPath + '.ts')) {
100
+ importedPath = importedPath + '.ts'
101
+ }
102
+ }
103
+
104
+ // If it's a TypeScript file, transpile it
105
+ if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
106
+ const transpiledImportContent = transpileTS(importedPath)
107
+ const tempImportFile = importedPath.replace(/\.ts$/, '.temp.mjs')
108
+ fs.writeFileSync(tempImportFile, transpiledImportContent)
109
+ transpiledFiles.set(importedPath, tempImportFile)
110
+ }
111
+ }
112
+
113
+ // Replace imports in the main file to point to temp .mjs files
114
+ jsContent = jsContent.replace(
115
+ /from\s+['"](\..+?)(?:\.ts)?['"]/g,
116
+ (match, importPath) => {
117
+ let resolvedPath = path.resolve(baseDir, importPath)
118
+
119
+ // Handle .js extension that might be .ts
120
+ if (resolvedPath.endsWith('.js')) {
121
+ const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
122
+ if (transpiledFiles.has(tsVersion)) {
123
+ const tempFile = transpiledFiles.get(tsVersion)
124
+ const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
125
+ return `from './${relPath}'`
126
+ }
127
+ }
128
+
129
+ // Try with .ts extension
130
+ const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
131
+
132
+ // If we transpiled this file, use the temp file
133
+ if (transpiledFiles.has(tsPath)) {
134
+ const tempFile = transpiledFiles.get(tsPath)
135
+ // Get relative path from main temp file to this temp file
136
+ const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
137
+ return `from './${relPath}'`
138
+ }
139
+
140
+ // Otherwise, keep the import as-is
141
+ return match
142
+ }
143
+ )
144
+
145
+ // Create a temporary JS file with .mjs extension for the main file
146
+ const tempJsFile = mainFilePath.replace(/\.ts$/, '.temp.mjs')
147
+ fs.writeFileSync(tempJsFile, jsContent)
148
+
149
+ // Store all temp files for cleanup
150
+ const allTempFiles = [tempJsFile, ...Array.from(transpiledFiles.values())]
151
+
152
+ return { tempFile: tempJsFile, allTempFiles }
153
+ }
154
+
155
+ /**
156
+ * Clean up temporary transpiled files
157
+ * @param {string[]} tempFiles - Array of temp file paths to delete
158
+ */
159
+ export function cleanupTempFiles(tempFiles) {
160
+ for (const file of tempFiles) {
161
+ if (fs.existsSync(file)) {
162
+ try {
163
+ fs.unlinkSync(file)
164
+ } catch (err) {
165
+ // Ignore cleanup errors
166
+ }
167
+ }
168
+ }
169
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.0-beta.12",
3
+ "version": "4.0.0-beta.14",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [