import * as fs from 'fs'; import * as path from 'path'; import type { CompilerOptions } from 'typescript'; import typescript from 'typescript'; /** * Default compiler options for TypeScript compilation */ export const compilerOptionsDefault: CompilerOptions = { declaration: true, inlineSourceMap: true, noEmitOnError: true, outDir: 'dist_ts/', module: typescript.ModuleKind.NodeNext, target: typescript.ScriptTarget.ESNext, moduleResolution: typescript.ModuleResolutionKind.NodeNext, lib: ['lib.dom.d.ts', 'lib.esnext.d.ts'], noImplicitAny: false, esModuleInterop: true, verbatimModuleSyntax: true, baseUrl: './', }; /** * TsConfig handles loading and merging TypeScript compiler configurations. * It supports reading tsconfig.json and merging with defaults and custom options. */ export class TsConfig { private cwd: string; private cachedTsConfig: CompilerOptions | null = null; constructor(cwd: string = process.cwd()) { this.cwd = cwd; } /** * Get the current working directory */ public getCwd(): string { return this.cwd; } /** * Load and parse tsconfig.json from the current directory */ public load(): CompilerOptions { if (this.cachedTsConfig !== null) { return this.cachedTsConfig; } let tsconfig: any; try { const tsconfigPath = path.join(this.cwd, 'tsconfig.json'); const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8'); tsconfig = JSON.parse(tsconfigContent); } catch { this.cachedTsConfig = {}; return {}; } if (!tsconfig || !tsconfig.compilerOptions) { this.cachedTsConfig = {}; return {}; } const returnObject: CompilerOptions = { ...tsconfig.compilerOptions }; // Convert target string to enum if (tsconfig.compilerOptions.target && typeof tsconfig.compilerOptions.target === 'string') { const targetKey = tsconfig.compilerOptions.target.toUpperCase(); if (targetKey in typescript.ScriptTarget) { returnObject.target = typescript.ScriptTarget[targetKey as keyof typeof typescript.ScriptTarget]; } } // Convert module string to enum if (tsconfig.compilerOptions.module && typeof tsconfig.compilerOptions.module === 'string') { const moduleKey = tsconfig.compilerOptions.module.toUpperCase(); if (moduleKey in typescript.ModuleKind) { returnObject.module = typescript.ModuleKind[moduleKey as keyof typeof typescript.ModuleKind]; } else if (moduleKey === 'NODENEXT') { returnObject.module = typescript.ModuleKind.NodeNext; } } // Convert moduleResolution string to enum if (tsconfig.compilerOptions.moduleResolution && typeof tsconfig.compilerOptions.moduleResolution === 'string') { const moduleResolutionKey = tsconfig.compilerOptions.moduleResolution.toUpperCase(); if (moduleResolutionKey in typescript.ModuleResolutionKind) { returnObject.moduleResolution = typescript.ModuleResolutionKind[ moduleResolutionKey as keyof typeof typescript.ModuleResolutionKind ]; } else if (moduleResolutionKey === 'NODENEXT') { returnObject.moduleResolution = typescript.ModuleResolutionKind.NodeNext; } } // Apply path transformations (ts_ → dist_ts_) if (tsconfig.compilerOptions.paths) { returnObject.paths = { ...tsconfig.compilerOptions.paths }; for (const pathKey of Object.keys(returnObject.paths)) { if (Array.isArray(returnObject.paths[pathKey]) && returnObject.paths[pathKey].length > 0) { returnObject.paths[pathKey][0] = returnObject.paths[pathKey][0].replace('./ts_', './dist_ts_'); } } } this.cachedTsConfig = returnObject; return returnObject; } /** * Get default compiler options */ public getDefaultOptions(): CompilerOptions { return { ...compilerOptionsDefault }; } /** * Get critical/protected default options that shouldn't be overridden by tsconfig.json */ public getProtectedDefaults(): CompilerOptions { return { outDir: 'dist_ts/', noEmitOnError: true, declaration: true, inlineSourceMap: true, }; } /** * Process command line arguments and return applicable compiler options */ public getCommandLineOptions(argvArg?: any): CompilerOptions { if (!argvArg) return {}; const options: CompilerOptions = {}; if (argvArg.skiplibcheck) { options.skipLibCheck = true; } if (argvArg.disallowimplicitany) { options.noImplicitAny = true; } if (argvArg.commonjs) { options.module = typescript.ModuleKind.CommonJS; options.moduleResolution = typescript.ModuleResolutionKind.NodeJs; } return options; } /** * Merge compiler options with proper priority order: * 1. Default options * 2. tsconfig.json options * 3. Protected defaults (cannot be overridden by tsconfig) * 4. Custom options (programmatic) * 5. CLI options (highest priority) */ public merge(customOptions: CompilerOptions = {}, argvArg?: any): CompilerOptions { return { ...this.getDefaultOptions(), ...this.load(), ...this.getProtectedDefaults(), ...customOptions, ...this.getCommandLineOptions(argvArg), }; } /** * Clear the cached tsconfig (useful for reloading) */ public clearCache(): void { this.cachedTsConfig = null; } }