import Path from 'path' ; import { IMinimatch , Minimatch } from 'minimatch' ; import { REPO_ROOT } from '@kbn/utils' ; import { parseTsConfig } from './ts_configfile' ; import { ProjectSet } from './project_set' ; function makeMatchers ( directory , patterns ) { return patterns . map ( ( pattern ) => new Minimatch ( Path . resolve ( directory , pattern ) , { dot : true , } ) ) ; } function testMatchers ( matchers , path ) { return matchers . some ( ( matcher ) => matcher . match ( path ) ) ; } export interface ProjectOptions { name ? : string ; disableTypeCheck ? : boolean ; } interface LoadOptions { history ? : string [ ] ; cache ? : Map < string , Project > ; skipConfigValidation ? : boolean ; } export class Project { static load ( tsConfigPath , projectOptions ? , loadOptions = { } ) { const cache = loadOptions . cache ?? new Map < string , Project > ( ) ; const cached = cache . get ( tsConfigPath ) ; if ( cached ) { return cached ; } const config = parseTsConfig ( tsConfigPath ) ; if ( ! loadOptions ?. skipConfigValidation ) { if ( config . files ) { throw new Error ( `${ tsConfigPath } must not use "files" key` ) ; } if ( ! config . include ) { throw new Error ( `${ tsConfigPath } must have an "include" key` ) ; } } const directory = Path . dirname ( tsConfigPath ) ; const disableTypeCheck = projectOptions ?. disableTypeCheck || false ; const name = projectOptions ?. name || Path . relative ( REPO_ROOT , directory ) || Path . basename ( directory ) ; const includePatterns = config . include ; const include = includePatterns ? makeMatchers ( directory , includePatterns ) : undefined ; const excludePatterns = config . exclude ; const exclude = excludePatterns ? makeMatchers ( directory , excludePatterns ) : undefined ; let baseProject ; if ( config . extends ) { const baseConfigPath = Path . resolve ( directory , config . extends ) ; if ( loadOptions . history ?. includes ( baseConfigPath ) ) { throw new Error ( `circular "extends" are not supported in tsconfig files: ${ loadOptions . history } => ${ baseConfigPath }` ) ; } baseProject = Project . load ( baseConfigPath , { } , { skipConfigValidation : true , history : [ ... ( loadOptions . history ?? [ ] ) , tsConfigPath ] , cache , } ) ; } const project = new Project ( tsConfigPath , directory , name , config , disableTypeCheck , baseProject , include , includePatterns , exclude , excludePatterns ) ; cache . set ( tsConfigPath , project ) ; return project ; } constructor ( public readonly tsConfigPath , public readonly directory , public readonly name , public readonly config , public readonly disableTypeCheck , public readonly baseProject ? , private readonly include ? , private readonly includePatterns ? , private readonly exclude ? , private readonly excludePatterns ? ) { } public getIncludePatterns ( ) { return this . includePatterns ? this . includePatterns : this . baseProject ?. getIncludePatterns ( ) ?? [ ] ; } public getExcludePatterns ( ) { return this . excludePatterns ? this . excludePatterns : this . baseProject ?. getExcludePatterns ( ) ?? [ ] ; } private getInclude ( ) { return this . include ? this . include : this . baseProject ?. getInclude ( ) ?? [ ] ; } private getExclude ( ) { return this . exclude ? this . exclude : this . baseProject ?. getExclude ( ) ?? [ ] ; } public isAbsolutePathSelected ( path ) { return testMatchers ( this . getExclude ( ) , path ) ? false : testMatchers ( this . getInclude ( ) , path ) ; } public isCompositeProject ( ) { const own = this . config . compilerOptions ?. composite ; return ! ! ( own === undefined ? this . baseProject ?. isCompositeProject ( ) : own ) ; } public getOutDir ( ) { if ( this . config . compilerOptions ?. outDir ) { return Path . resolve ( this . directory , this . config . compilerOptions . outDir ) ; } if ( this . baseProject ) { return this . baseProject . getOutDir ( ) ; } return undefined ; } public getRefdPaths ( ) { if ( this . config . references ) { return ( this . config . references as Array < { path : string ; } > ) . map ( ( { path } ) => Path . resolve ( this . directory , path ) ) ; } return this . baseProject ? this . baseProject . getRefdPaths ( ) : [ ] ; } public getProjectsDeep ( cache ? ) { const projects = new Set < Project > ( ) ; const queue = new Set < string > ( [ this . tsConfigPath ] ) ; for ( const path of queue ) { const project = Project . load ( path , { } , { skipConfigValidation : true , cache } ) ; projects . add ( project ) ; for ( const refPath of project . getRefdPaths ( ) ) { queue . add ( refPath ) ; } } return new ProjectSet ( projects ) ; } public getConfigPaths ( ) { return this . baseProject ? [ this . tsConfigPath , ... this . baseProject . getConfigPaths ( ) ] : [ this . tsConfigPath ] ; } }