tsconfig-utils.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import fs from 'fs-extra';
  2. import path from 'path';
  3. import stripJsonComments from 'strip-json-comments';
  4. import { CompilerOptions } from 'typescript';
  5. import { Logger, TransformTsConfigPathMappingsFn } from '../types.js';
  6. export interface TsConfigPathsConfig {
  7. baseUrl: string;
  8. paths: Record<string, string[]>;
  9. }
  10. /**
  11. * Finds and parses tsconfig files in the given directory and its parent directories.
  12. */
  13. export async function findTsConfigPaths(
  14. configPath: string,
  15. logger: Logger,
  16. phase: 'compiling' | 'loading',
  17. transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
  18. ): Promise<TsConfigPathsConfig | undefined> {
  19. let currentDir = path.dirname(configPath);
  20. while (currentDir !== path.parse(currentDir).root) {
  21. try {
  22. const files = await fs.readdir(currentDir);
  23. const tsConfigFiles = files.filter(file => /^tsconfig(\..*)?\.json$/.test(file));
  24. for (const fileName of tsConfigFiles) {
  25. const tsConfigFilePath = path.join(currentDir, fileName);
  26. try {
  27. const { paths, baseUrl } = await getCompilerOptionsFromFile(tsConfigFilePath);
  28. if (paths) {
  29. const tsConfigBaseUrl = path.resolve(currentDir, baseUrl || '.');
  30. const pathMappings = getTransformedPathMappings(
  31. paths,
  32. phase,
  33. transformTsConfigPathMappings,
  34. );
  35. return { baseUrl: tsConfigBaseUrl, paths: pathMappings };
  36. }
  37. } catch (e) {
  38. logger.warn(
  39. `Could not read or parse tsconfig file ${tsConfigFilePath}: ${e instanceof Error ? e.message : String(e)}`,
  40. );
  41. }
  42. }
  43. } catch (e) {
  44. logger.warn(
  45. `Could not read directory ${currentDir}: ${e instanceof Error ? e.message : String(e)}`,
  46. );
  47. }
  48. currentDir = path.dirname(currentDir);
  49. }
  50. return undefined;
  51. }
  52. async function getCompilerOptionsFromFile(tsConfigFilePath: string): Promise<CompilerOptions> {
  53. const tsConfigContent = await fs.readFile(tsConfigFilePath, 'utf-8');
  54. const tsConfig = JSON.parse(stripJsonComments(tsConfigContent));
  55. return tsConfig.compilerOptions || {};
  56. }
  57. function getTransformedPathMappings(
  58. paths: Required<CompilerOptions>['paths'],
  59. phase: 'compiling' | 'loading',
  60. transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
  61. ) {
  62. const pathMappings: Record<string, string[]> = {};
  63. for (const [alias, patterns] of Object.entries(paths)) {
  64. const normalizedPatterns = patterns.map(pattern => pattern.replace(/\\/g, '/'));
  65. pathMappings[alias] = transformTsConfigPathMappings({
  66. phase,
  67. alias,
  68. patterns: normalizedPatterns,
  69. });
  70. }
  71. return pathMappings;
  72. }