import { Compiler } from "../Compiler/Compiler" ; import { CompilerResult } from "../Compiler/CompilerResult" ; import { DiagnosticsReporter } from "../Reporting/DiagnosticsReporter" ; import { CachingCompilerHost } from "../Compiler/CachingCompilerHost" ; import { ProjectBuildContext } from "./ProjectBuildContext" ; import { CompileStream } from "../Compiler/CompileStream" ; import { TsCompilerOptions } from "../Compiler/TsCompilerOptions" ; import { BundleBuilder } from "../Bundler/BundleBuilder" ; import { BundleFile , BundleResult } from "../Bundler/BundleResult" ; import { BundleCompiler } from "../Bundler/BundleCompiler" ; import { ProjectConfig } from "./ProjectConfig" ; import { StatisticsReporter } from "../Reporting/StatisticsReporter" ; import { Logger } from "../Reporting/Logger" ; import { BundleParser , Bundle } from "../Bundler/BundleParser" ; import { Glob } from "./Glob" ; import { TsCore } from "../Utils/TsCore" ; import { Utils } from "../Utils/Utilities" ; import * as ts from "typescript" ; import * as _ from "lodash" ; import * as fs from "fs" ; import * as path from "path" ; import * as chalk from "chalk" ; export class Project { private configFilePath : string ; private settings : any ; private outputStream : CompileStream ; private configFileDir : string ; private configFileName : string ; private totalBuildTime : number = 0 ; private totalCompileTime : number = 0 ; private totalPreBuildTime : number = 0 ; private totalBundleTime : number = 0 ; private buildContext : ProjectBuildContext ; constructor ( configFilePath , settings ? ) { this . configFilePath = configFilePath ; this . settings = settings ; } public static version = "4.0.0" ; public build ( outputStream ) { let config = this . parseProjectConfig ( ) ; if ( ! config . success ) { DiagnosticsReporter . reportDiagnostics ( config . errors ) ; return ts . ExitStatus . DiagnosticsPresent_OutputsSkipped ; } this . buildContext = this . createBuildContext ( config ) ; Logger . log ( "Building Project with: " + chalk . magenta ( `${ this . configFileName }` ) ) ; Logger . log ( "TsProject version: " , Project . version ) ; Logger . log ( "TypeScript version: " , ts . version ) ; this . outputStream = outputStream ; var buildStatus = this . buildWorker ( ) ; this . reportBuildStatus ( buildStatus ) ; this . completeProjectBuild ( ) ; return buildStatus ; } private createBuildContext ( config ) { let compilerHost = new CachingCompilerHost ( config . compilerOptions ) ; return new ProjectBuildContext ( compilerHost , config ) ; } private completeProjectBuild ( ) { this . outputStream . push ( null ) ; } private buildWorker ( ) { this . totalBuildTime = this . totalPreBuildTime = new Date ( ) . getTime ( ) ; if ( ! this . buildContext ) { let config = this . parseProjectConfig ( ) ; if ( ! config . success ) { DiagnosticsReporter . reportDiagnostics ( config . errors ) ; return ts . ExitStatus . DiagnosticsPresent_OutputsSkipped ; } this . buildContext = this . createBuildContext ( config ) ; } let allDiagnostics = [ ] ; let fileNames = this . buildContext . config . fileNames ; let bundles = this . buildContext . config . bundles ; let compilerOptions = this . buildContext . config . compilerOptions ; let program = ts . createProgram ( fileNames , compilerOptions , this . buildContext . host , this . buildContext . getProgram ( ) ) ; this . totalPreBuildTime = new Date ( ) . getTime ( ) - this . totalPreBuildTime ; this . buildContext . setProgram ( program ) ; let compiler = new Compiler ( this . buildContext . host , program , this . outputStream ) ; this . totalCompileTime = new Date ( ) . getTime ( ) ; var compileResult = compiler . compile ( ) ; this . totalCompileTime = new Date ( ) . getTime ( ) - this . totalCompileTime ; if ( ! compileResult . succeeded ( ) ) { DiagnosticsReporter . reportDiagnostics ( compileResult . getErrors ( ) ) ; return compileResult . getStatus ( ) ; } if ( compilerOptions . listFiles ) { Utils . forEach ( this . buildContext . getProgram ( ) . getSourceFiles ( ) , file => { Logger . log ( file . fileName ) ; } ) ; } this . totalBundleTime = new Date ( ) . getTime ( ) ; var bundleBuilder = new BundleBuilder ( this . buildContext . host , this . buildContext . getProgram ( ) ) ; var bundleCompiler = new BundleCompiler ( this . buildContext . host , this . buildContext . getProgram ( ) , this . outputStream ) ; var bundleResult ; for ( var i = 0 , len = bundles . length ; i < len ; i ++ ) { Logger . log ( "Building bundle: " , chalk . cyan ( bundles [ i ] . name ) ) ; bundleResult = bundleBuilder . build ( bundles [ i ] ) ; if ( ! bundleResult . succeeded ( ) ) { DiagnosticsReporter . reportDiagnostics ( bundleResult . getErrors ( ) ) ; return ts . ExitStatus . DiagnosticsPresent_OutputsSkipped ; } compileResult = bundleCompiler . compile ( bundleResult . getBundleSource ( ) , bundles [ i ] . config ) ; if ( ! compileResult . succeeded ( ) ) { DiagnosticsReporter . reportDiagnostics ( compileResult . getErrors ( ) ) ; return compileResult . getStatus ( ) ; } } this . totalBundleTime = new Date ( ) . getTime ( ) - this . totalBundleTime ; this . totalBuildTime = new Date ( ) . getTime ( ) - this . totalBuildTime ; if ( compilerOptions . diagnostics ) { this . reportStatistics ( ) ; } if ( allDiagnostics . length > 0 ) { return ts . ExitStatus . DiagnosticsPresent_OutputsGenerated ; } return ts . ExitStatus . Success ; } private parseProjectConfig ( ) { try { var isConfigDirectory = fs . lstatSync ( this . configFilePath ) . isDirectory ( ) ; } catch ( e ) { let diagnostic = TsCore . createDiagnostic ( { code : 6064 , category : ts . DiagnosticCategory . Error , key : "Cannot_read_project_path_0_6064" , message : "Cannot read project path '{0}'." } , this . configFilePath ) ; return { success : false , errors : [ diagnostic ] } ; } if ( isConfigDirectory ) { this . configFileDir = this . configFilePath ; this . configFileName = path . join ( this . configFilePath , "tsconfig.json" ) ; } else { this . configFileDir = path . dirname ( this . configFilePath ) ; this . configFileName = this . configFilePath ; } Logger . info ( "Reading config file:" , this . configFileName ) ; let readConfigResult = ts . readConfigFile ( this . configFileName , this . readFile ) ; if ( readConfigResult . error ) { return { success : false , errors : [ readConfigResult . error ] } ; } let configObject = readConfigResult . config ; Logger . info ( "Parsing config file..." ) ; var configParseResult = ts . parseJsonConfigFileContent ( configObject , ts . sys , this . configFileDir ) ; if ( configParseResult . errors . length > 0 ) { return { success : false , errors : configParseResult . errors } ; } configParseResult . fileNames = this . expandFileNames ( configParseResult . fileNames , this . configFileDir ) ; if ( this . settings . convertFiles === true ) { this . convertProjectFileNames ( configParseResult . fileNames , this . configFileDir ) ; } var bundleParser = new BundleParser ( ) ; var bundlesParseResult = bundleParser . parseConfigFile ( configObject , this . configFileDir ) ; if ( bundlesParseResult . errors . length > 0 ) { return { success : false , errors : bundlesParseResult . errors } ; } bundlesParseResult . bundles . forEach ( bundle => { bundle . fileNames = this . expandFileNames ( bundle . fileNames , this . configFileDir ) ; } ) ; let settingsCompilerOptions = this . getSettingsCompilerOptions ( this . settings , this . configFileDir ) ; if ( settingsCompilerOptions . errors . length > 0 ) { return { success : false , errors : settingsCompilerOptions . errors } ; } let compilerOptions = Utils . extend ( settingsCompilerOptions . options , configParseResult . options ) ; Logger . info ( "Compiler options: " , compilerOptions ) ; return { success : true , compilerOptions : compilerOptions , fileNames : configParseResult . fileNames , bundles : bundlesParseResult . bundles } ; } private readFile ( fileName ) { return ts . sys . readFile ( fileName ) ; } private getSettingsCompilerOptions ( jsonSettings , configDirPath ) { let parsedResult = ts . parseJsonConfigFileContent ( jsonSettings , ts . sys , configDirPath ) ; if ( parsedResult . options . project ) { let diagnostic = TsCore . createDiagnostic ( { code : 5099 , category : ts . DiagnosticCategory . Error , key : "The_compiler_option_0_is_not_supported_in_this_context_5099" , message : "The compiler option '{0}' is not supported in this context." } , "--project" ) ; parsedResult . errors . push ( diagnostic ) ; } return parsedResult ; } private expandFileNames ( files , configDirPath ) { var glob = new Glob ( ) ; var nonglobFiles = [ ] ; Utils . forEach ( files , file => { if ( ! glob . hasPattern ( file ) ) { nonglobFiles . push ( path . normalize ( file ) ) ; } } ) ; var globFiles = glob . expand ( files , configDirPath ) ; var normalizedGlobFiles = [ ] ; Utils . forEach ( globFiles , file => { normalizedGlobFiles . push ( path . normalize ( file ) ) ; } ) ; return _ . union ( normalizedGlobFiles , nonglobFiles ) ; } private convertProjectFileNames ( fileNames , configDirPath ) { let configFileText = "" ; try { configFileText = fs . readFileSync ( this . configFileName , 'utf8' ) ; if ( configFileText !== undefined ) { let jsonConfigObject = JSON . parse ( configFileText ) ; let relativeFileNames = [ ] ; fileNames . forEach ( fileName => { relativeFileNames . push ( path . relative ( configDirPath , fileName ) . replace ( /\\/g , "/" ) ) ; } ) ; jsonConfigObject [ "files" ] = relativeFileNames ; fs . writeFileSync ( this . configFileName , JSON . stringify ( jsonConfigObject , undefined , 4 ) ) ; } } catch ( e ) { Logger . log ( chalk . yellow ( "Converting project files failed." ) ) ; } } private reportBuildStatus ( buildStatus ) { switch ( buildStatus ) { case ts . ExitStatus . Success : Logger . log ( chalk . green ( "Project build completed successfully." ) ) ; break ; case ts . ExitStatus . DiagnosticsPresent_OutputsSkipped : Logger . log ( chalk . red ( "Build completed with errors." ) ) ; break ; case ts . ExitStatus . DiagnosticsPresent_OutputsGenerated : Logger . log ( chalk . red ( "Build completed with errors. " + chalk . italic ( "Outputs generated." ) ) ) ; break ; } } private reportStatistics ( ) { let statisticsReporter = new StatisticsReporter ( ) ; statisticsReporter . reportTitle ( "Total build times..." ) ; statisticsReporter . reportTime ( "Pre-build time" , this . totalPreBuildTime ) ; statisticsReporter . reportTime ( "Compiling time" , this . totalCompileTime ) ; statisticsReporter . reportTime ( "Bundling time" , this . totalBundleTime ) ; statisticsReporter . reportTime ( "Build time" , this . totalBuildTime ) ; } }