diff --git a/package-lock.json b/package-lock.json index deb4b547d..3ca177c26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "husky": "^8.0.0", "inquirer": "^9.3.7", "jest-extended": "^6.0.0", - "jiti": "2.4.2", + "jiti": "^2.4.2", "jsdom": "~24.0.0", "jsonc-eslint-parser": "^2.4.0", "knip": "^5.33.3", diff --git a/package.json b/package.json index e4c55ca77..ffa649f64 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "husky": "^8.0.0", "inquirer": "^9.3.7", "jest-extended": "^6.0.0", - "jiti": "2.4.2", + "jiti": "^2.4.2", "jsdom": "~24.0.0", "jsonc-eslint-parser": "^2.4.0", "knip": "^5.33.3", diff --git a/packages/core/src/lib/implementation/read-rc-file.ts b/packages/core/src/lib/implementation/read-rc-file.ts index 9dd2afb5f..d91128fae 100644 --- a/packages/core/src/lib/implementation/read-rc-file.ts +++ b/packages/core/src/lib/implementation/read-rc-file.ts @@ -29,7 +29,6 @@ export async function readRcByPath( const cfg: CoreConfig = await importModule({ filepath: filePath, tsconfig, - format: 'esm', }); return validate(coreConfigSchema, cfg, { filePath }); diff --git a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts index 12a708134..69eba3c15 100644 --- a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts +++ b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts @@ -136,7 +136,6 @@ export async function getCoveragePathForVitest( const vitestConfig = await importModule({ filepath: config, - format: 'esm', }); const reportsDirectory = diff --git a/packages/plugin-lighthouse/src/lib/runner/utils.ts b/packages/plugin-lighthouse/src/lib/runner/utils.ts index 50a4ed47e..48f0a4d2d 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.ts @@ -134,7 +134,7 @@ export async function getConfig( // Resolve the config file path relative to where cli was called. return readJsonFile(filepath); } else if (/\.(ts|js|mjs)$/.test(filepath)) { - return importModule({ filepath, format: 'esm' }); + return importModule({ filepath }); } else { logger.warn(`Format of file ${filepath} not supported`); } diff --git a/packages/utils/src/lib/file-system.ts b/packages/utils/src/lib/file-system.ts index 5dacec734..8c1f7896b 100644 --- a/packages/utils/src/lib/file-system.ts +++ b/packages/utils/src/lib/file-system.ts @@ -1,7 +1,8 @@ import ansis from 'ansis'; -import { type Options, bundleRequire } from 'bundle-require'; +import { createJiti } from 'jiti'; import { mkdir, readFile, readdir, rm, stat } from 'node:fs/promises'; import path from 'node:path'; +import { parseJsonConfigFileContent, readConfigFile, sys } from 'typescript'; import type { Format, PersistConfig } from '@code-pushup/models'; import { formatBytes } from './formatting.js'; import { logMultipleResults } from './log-results.js'; @@ -77,10 +78,80 @@ export function logMultipleFileResults( ); } -export async function importModule(options: Options): Promise { - const { mod } = await bundleRequire(options); +export function loadTargetConfig(tsConfigPath: string) { + const resolvedConfigPath = path.resolve(tsConfigPath); + const { config, error } = readConfigFile(resolvedConfigPath, sys.readFile); - if (typeof mod === 'object' && 'default' in mod) { + if (error) { + throw new Error( + `Error reading TypeScript config file at ${tsConfigPath}:\n${error.messageText}`, + ); + } + + const parsedConfig = parseJsonConfigFileContent( + config, + sys, + path.dirname(resolvedConfigPath), + {}, + resolvedConfigPath, + ); + + if (parsedConfig.fileNames.length === 0) { + throw new Error( + 'No files matched by the TypeScript configuration. Check your "include", "exclude" or "files" settings.', + ); + } + + return parsedConfig; +} + +type JitiOptions = Parameters[1]; + +export function tsConfigToJitiOptionsTransformer( + tsConfigPath: string, +): JitiOptions { + const parsedConfig = loadTargetConfig(tsConfigPath); + const compilerOptions = parsedConfig.options; + + const jitiOptions: JitiOptions = {}; + + if (compilerOptions.paths) { + const aliases: Record = {}; + const basePath = compilerOptions.baseUrl || path.dirname(tsConfigPath); + + for (const alias in compilerOptions.paths) { + const paths = compilerOptions.paths[alias]; + if (paths && paths.length > 0) { + aliases[alias] = path.resolve(basePath, paths[0] as string); + } + } + jitiOptions.alias = aliases; + } + + if (compilerOptions.jsx) { + jitiOptions.jsx = true; + } + + return jitiOptions; +} + +export async function importModule( + options: JitiOptions & { filepath: string; tsconfig?: string }, +): Promise { + const { filepath, tsconfig, ...jitiOptions } = options; + + const jitiOptionsFromTsConfig = tsconfig + ? tsConfigToJitiOptionsTransformer(tsconfig) + : {}; + + const jiti = createJiti(process.cwd(), { + ...jitiOptions, + ...jitiOptionsFromTsConfig, + }); + + const mod = await jiti.import(filepath); + + if (mod && typeof mod === 'object' && 'default' in mod) { return mod.default as T; } return mod as T;