(modulePath: string, functionName?: string)
| 217 | * Uses Node.js native ESM import with proper URL resolution |
| 218 | */ |
| 219 | export async function importModule(modulePath: string, functionName?: string) { |
| 220 | logger.debug( |
| 221 | `Attempting to import module: ${JSON.stringify({ resolvedPath: safeResolve(modulePath), moduleId: modulePath })}`, |
| 222 | ); |
| 223 | |
| 224 | try { |
| 225 | await ensureTypescriptLoader(modulePath); |
| 226 | |
| 227 | const resolvedPath = pathToFileURL(safeResolve(modulePath)); |
| 228 | const resolvedPathStr = resolvedPath.toString(); |
| 229 | logger.debug(`Attempting ESM import from: ${resolvedPathStr}`); |
| 230 | |
| 231 | // Native dynamic import - no eval() needed in ESM-only environment |
| 232 | const importedModule = await import(resolvedPathStr); |
| 233 | |
| 234 | const mod = importedModule?.default?.default || importedModule?.default || importedModule; |
| 235 | logger.debug( |
| 236 | `Successfully imported module: ${JSON.stringify({ resolvedPath, moduleId: modulePath })}`, |
| 237 | ); |
| 238 | |
| 239 | if (functionName) { |
| 240 | logger.debug(`Returning named export: ${functionName}`); |
| 241 | return mod[functionName]; |
| 242 | } |
| 243 | return mod; |
| 244 | } catch (err) { |
| 245 | const errorMessage = err instanceof Error ? err.message : String(err); |
| 246 | |
| 247 | // Fall back to vm-based CJS execution for .js files that use CJS syntax |
| 248 | // Note: createRequire() doesn't work for .js files in "type": "module" packages |
| 249 | // because Node.js still treats them as ESM based on package.json. |
| 250 | // We use Node's vm module to execute the code with proper CJS globals. |
| 251 | if (modulePath.endsWith('.js') && isCjsInEsmError(errorMessage)) { |
| 252 | logger.debug( |
| 253 | `ESM import failed for ${modulePath}, attempting vm-based CJS fallback: ${errorMessage}`, |
| 254 | ); |
| 255 | |
| 256 | try { |
| 257 | const resolvedPath = safeResolve(modulePath); |
| 258 | const mod = loadCjsModule(resolvedPath); |
| 259 | logger.debug( |
| 260 | `Successfully loaded module via CJS fallback: ${JSON.stringify({ resolvedPath, moduleId: modulePath })}`, |
| 261 | ); |
| 262 | |
| 263 | if (functionName) { |
| 264 | logger.debug(`Returning named export: ${functionName}`); |
| 265 | return mod[functionName]; |
| 266 | } |
| 267 | return mod; |
| 268 | } catch (cjsErr) { |
| 269 | // If CJS fallback also fails, throw a combined error with both details |
| 270 | const cjsErrorMessage = cjsErr instanceof Error ? cjsErr.message : String(cjsErr); |
| 271 | logger.error(`ESM import failed for ${modulePath}: ${errorMessage}`); |
| 272 | logger.error(`CJS fallback also failed: ${cjsErrorMessage}`); |
| 273 | |
| 274 | // Create a combined error that includes both failure reasons |
| 275 | const combinedError = new Error( |
| 276 | `Failed to load module ${modulePath}:\n` + |
no test coverage detected
searching dependent graphs…