2017-03-01 18:28:51 +01:00
/// <reference path="../../compiler/core.ts" />
2016-09-08 22:26:01 +02:00
/// <reference path="../../compiler/moduleNameResolver.ts" />
2016-08-14 20:16:11 +02:00
/// <reference path="../../services/jsTyping.ts"/>
2017-01-01 02:37:51 +01:00
/// <reference path="../types.ts"/>
2016-11-07 22:36:08 +01:00
/// <reference path="../shared.ts"/>
2016-08-12 20:04:43 +02:00
namespace ts . server . typingsInstaller {
2016-08-26 01:25:34 +02:00
interface NpmConfig {
devDependencies : MapLike < any > ;
2016-08-15 20:48:28 +02:00
}
2016-08-16 00:59:31 +02:00
export interface Log {
isEnabled ( ) : boolean ;
writeLine ( text : string ) : void ;
}
2016-08-17 23:47:54 +02:00
2016-08-16 00:59:31 +02:00
const nullLog : Log = {
isEnabled : ( ) = > false ,
2016-10-19 23:15:14 +02:00
writeLine : noop
2016-08-17 23:47:54 +02:00
} ;
2016-08-16 00:59:31 +02:00
2016-11-15 21:53:46 +01:00
function typingToFileName ( cachePath : string , packageName : string , installTypingHost : InstallTypingHost , log : Log ) : string {
try {
const result = resolveModuleName ( packageName , combinePaths ( cachePath , "index.d.ts" ) , { module Resolution : ModuleResolutionKind . NodeJs } , installTypingHost ) ;
return result . resolvedModule && result . resolvedModule . resolvedFileName ;
}
catch ( e ) {
if ( log . isEnabled ( ) ) {
log . writeLine ( ` Failed to resolve ${ packageName } in folder ' ${ cachePath } ': ${ ( < Error > e ) . message } ` ) ;
}
return undefined ;
}
2016-08-15 20:48:28 +02:00
}
2016-10-01 20:56:51 +02:00
2016-10-13 19:19:18 +02:00
export type RequestCompletedAction = ( success : boolean ) = > void ;
2017-09-07 18:14:59 +02:00
interface PendingRequest {
2016-09-20 23:14:51 +02:00
requestId : number ;
2017-10-18 00:04:09 +02:00
packageNames : string [ ] ;
2016-09-20 23:14:51 +02:00
cwd : string ;
2016-11-10 23:28:34 +01:00
onRequestCompleted : RequestCompletedAction ;
2017-09-07 18:14:59 +02:00
}
2016-09-20 23:14:51 +02:00
2017-11-21 01:43:02 +01:00
interface TypeDeclarationTimestampFile {
entries : MapLike < number > ;
}
2017-11-28 05:29:10 +01:00
function loadTypeDeclarationTimestampFile ( typeDeclarationTimestampFilePath : string , host : InstallTypingHost , log : Log ) : MapLike < number > {
2017-11-21 01:43:02 +01:00
const fileExists = host . fileExists ( typeDeclarationTimestampFilePath ) ;
if ( ! fileExists ) {
if ( log . isEnabled ( ) ) {
log . writeLine ( ` Type declaration timestamp file ' ${ typeDeclarationTimestampFilePath } ' does not exist ` ) ;
}
}
try {
if ( fileExists ) {
const content = < TypeDeclarationTimestampFile > JSON . parse ( host . readFile ( typeDeclarationTimestampFilePath ) ) ;
2017-12-29 23:21:55 +01:00
return content . entries || { } ;
2017-11-21 01:43:02 +01:00
}
else {
host . writeFile ( typeDeclarationTimestampFilePath , "{}" ) ;
if ( log . isEnabled ( ) ) {
log . writeLine ( "Type declaration timestamp file was created." ) ;
}
2017-11-28 05:29:10 +01:00
return { } ;
2017-11-21 01:43:02 +01:00
}
}
catch ( e ) {
if ( log . isEnabled ( ) ) {
log . writeLine ( ` Error when loading type declaration timestamp file ' ${ typeDeclarationTimestampFilePath } ': ${ ( < Error > e ) . message } , ${ ( < Error > e ) . stack } ` ) ;
}
2017-11-28 05:29:10 +01:00
return { } ;
2017-11-21 01:43:02 +01:00
}
}
function writeTypeDeclarationTimestampFile ( typeDeclarationTimestampFilePath : string , newContents : TypeDeclarationTimestampFile , host : InstallTypingHost , log : Log ) : void {
const fileExists = host . fileExists ( typeDeclarationTimestampFilePath ) ;
if ( ! fileExists ) {
if ( log . isEnabled ( ) ) {
log . writeLine ( ` Type declaration timestamp file ' ${ typeDeclarationTimestampFilePath } ' does not exist ` ) ;
}
}
try {
if ( fileExists ) {
host . writeFile ( typeDeclarationTimestampFilePath , JSON . stringify ( newContents ) ) ;
return ;
}
else {
host . writeFile ( typeDeclarationTimestampFilePath , JSON . stringify ( newContents ) ) ;
if ( log . isEnabled ( ) ) {
log . writeLine ( "Type declaration time stamp file was created." ) ;
}
return ;
}
}
catch ( e ) {
if ( log . isEnabled ( ) ) {
log . writeLine ( ` Error when writing new type declaration timestamp file ' ${ typeDeclarationTimestampFilePath } ': ${ ( < Error > e ) . message } , ${ ( < Error > e ) . stack } ` ) ;
}
return ;
}
}
2016-08-12 20:04:43 +02:00
export abstract class TypingsInstaller {
2017-11-21 01:43:02 +01:00
private readonly packageNameToTypingLocation : Map < JsTyping.CachedTyping > = createMap < JsTyping.CachedTyping > ( ) ;
2016-10-28 00:50:21 +02:00
private readonly missingTypingsSet : Map < true > = createMap < true > ( ) ;
private readonly knownCachesSet : Map < true > = createMap < true > ( ) ;
private readonly projectWatchers : Map < FileWatcher [ ] > = createMap < FileWatcher [ ] > ( ) ;
2017-07-27 19:54:47 +02:00
private safeList : JsTyping.SafeList | undefined ;
2016-09-20 23:14:51 +02:00
readonly pendingRunRequests : PendingRequest [ ] = [ ] ;
2017-11-28 05:29:10 +01:00
private typeDeclarationTimestamps : MapLike < number > = { } ;
2016-09-20 23:14:51 +02:00
2016-09-19 22:56:30 +02:00
private installRunCount = 1 ;
2016-09-20 23:14:51 +02:00
private inFlightRequestCount = 0 ;
2016-08-16 23:21:09 +02:00
2016-11-07 22:36:08 +01:00
abstract readonly typesRegistry : Map < void > ;
2016-08-15 20:48:28 +02:00
2016-09-20 23:14:51 +02:00
constructor (
2017-07-14 23:26:13 +02:00
protected readonly installTypingHost : InstallTypingHost ,
private readonly globalCachePath : string ,
private readonly safeListPath : Path ,
2017-07-28 01:07:50 +02:00
private readonly typesMapLocation : Path ,
2017-07-14 23:26:13 +02:00
private readonly throttleLimit : number ,
2016-09-20 23:14:51 +02:00
protected readonly log = nullLog ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2017-07-28 01:07:50 +02:00
this . log . writeLine ( ` Global cache location ' ${ globalCachePath } ', safe file path ' ${ safeListPath } ', types map path ${ typesMapLocation } ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-08-15 20:48:28 +02:00
this . processCacheLocation ( this . globalCachePath ) ;
2016-08-12 20:04:43 +02:00
}
2016-08-16 23:21:09 +02:00
closeProject ( req : CloseProject ) {
this . closeWatchers ( req . projectName ) ;
}
2016-08-17 22:01:48 +02:00
private closeWatchers ( projectName : string ) : void {
2016-08-16 23:21:09 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Closing file watchers for project ' ${ projectName } ' ` ) ;
}
2016-12-05 23:13:32 +01:00
const watchers = this . projectWatchers . get ( projectName ) ;
2016-08-16 23:21:09 +02:00
if ( ! watchers ) {
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` No watchers are registered for project ' ${ projectName } ' ` ) ;
}
2016-08-17 22:01:48 +02:00
return ;
2016-08-16 23:21:09 +02:00
}
for ( const w of watchers ) {
w . close ( ) ;
}
2016-12-05 23:13:32 +01:00
this . projectWatchers . delete ( projectName ) ;
2016-08-16 23:21:09 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Closing file watchers for project ' ${ projectName } ' - done. ` ) ;
}
}
install ( req : DiscoverTypings ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Got install request ${ JSON . stringify ( req ) } ` ) ;
}
2016-08-12 20:04:43 +02:00
2016-09-26 20:33:25 +02:00
// load existing typing information from the cache
2016-08-15 20:48:28 +02:00
if ( req . cachePath ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Request specifies cache path ' ${ req . cachePath } ', loading cached information... ` ) ;
}
2016-08-15 20:48:28 +02:00
this . processCacheLocation ( req . cachePath ) ;
}
2017-07-27 19:54:47 +02:00
if ( this . safeList === undefined ) {
2017-07-28 01:07:50 +02:00
this . initializeSafeList ( ) ;
2017-07-27 19:54:47 +02:00
}
2016-08-12 20:04:43 +02:00
const discoverTypingsResult = JsTyping . discoverTypings (
2016-08-15 20:48:28 +02:00
this . installTypingHost ,
2017-08-23 22:01:14 +02:00
this . log . isEnabled ( ) ? ( s = > this . log . writeLine ( s ) ) : undefined ,
2016-08-12 20:04:43 +02:00
req . fileNames ,
req . projectRootPath ,
2017-07-27 19:54:47 +02:00
this . safeList ,
2016-08-15 20:48:28 +02:00
this . packageNameToTypingLocation ,
2016-11-19 02:46:06 +01:00
req . typeAcquisition ,
2016-10-26 00:24:21 +02:00
req . unresolvedImports ) ;
2016-08-17 23:47:54 +02:00
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Finished typings discovery: ${ JSON . stringify ( discoverTypingsResult ) } ` ) ;
}
2016-08-12 20:04:43 +02:00
2016-08-12 21:14:25 +02:00
// start watching files
2016-08-16 23:49:45 +02:00
this . watchFiles ( req . projectName , discoverTypingsResult . filesToWatch ) ;
2016-08-15 20:48:28 +02:00
2016-08-16 00:59:31 +02:00
// install typings
2016-08-17 22:01:48 +02:00
if ( discoverTypingsResult . newTypingNames . length ) {
this . installTypings ( req , req . cachePath || this . globalCachePath , discoverTypingsResult . cachedTypingPaths , discoverTypingsResult . newTypingNames ) ;
}
else {
2017-11-16 21:37:40 +01:00
this . sendResponse ( this . createSetTypings ( req , discoverTypingsResult . cachedTypingPaths ) ) ;
2016-08-17 22:01:48 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` No new typings were requested as a result of typings discovery ` ) ;
}
}
2016-08-12 20:04:43 +02:00
}
2017-07-28 01:07:50 +02:00
private initializeSafeList() {
// Prefer the safe list from the types map if it exists
if ( this . typesMapLocation ) {
const safeListFromMap = JsTyping . loadTypesMap ( this . installTypingHost , this . typesMapLocation ) ;
if ( safeListFromMap ) {
this . log . writeLine ( ` Loaded safelist from types map file ' ${ this . typesMapLocation } ' ` ) ;
this . safeList = safeListFromMap ;
return ;
}
this . log . writeLine ( ` Failed to load safelist from types map file ' ${ this . typesMapLocation } ' ` ) ;
}
this . safeList = JsTyping . loadSafeList ( this . installTypingHost , this . safeListPath ) ;
}
2016-08-15 20:48:28 +02:00
private processCacheLocation ( cacheLocation : string ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Processing cache location ' ${ cacheLocation } ' ` ) ;
}
2016-12-05 23:13:32 +01:00
if ( this . knownCachesSet . get ( cacheLocation ) ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-08-17 23:47:54 +02:00
this . log . writeLine ( ` Cache location was already processed... ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-08-15 20:48:28 +02:00
return ;
}
2017-11-21 01:43:02 +01:00
const timestampJson = combinePaths ( cacheLocation , "timestamps.json" ) ;
this . typeDeclarationTimestamps = loadTypeDeclarationTimestampFile ( timestampJson , this . installTypingHost , this . log ) ;
2016-08-26 01:25:34 +02:00
const packageJson = combinePaths ( cacheLocation , "package.json" ) ;
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-08-26 01:25:34 +02:00
this . log . writeLine ( ` Trying to find ' ${ packageJson } '... ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-08-26 01:25:34 +02:00
if ( this . installTypingHost . fileExists ( packageJson ) ) {
const npmConfig = < NpmConfig > JSON . parse ( this . installTypingHost . readFile ( packageJson ) ) ;
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-09-22 23:34:08 +02:00
this . log . writeLine ( ` Loaded content of ' ${ packageJson } ': ${ JSON . stringify ( npmConfig ) } ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-08-26 01:25:34 +02:00
if ( npmConfig . devDependencies ) {
for ( const key in npmConfig . devDependencies ) {
// key is @types/<package name>
2016-08-27 01:37:31 +02:00
const packageName = getBaseFileName ( key ) ;
2016-08-15 20:48:28 +02:00
if ( ! packageName ) {
continue ;
}
2016-11-15 21:53:46 +01:00
const typingFile = typingToFileName ( cacheLocation , packageName , this . installTypingHost , this . log ) ;
2016-08-26 01:25:34 +02:00
if ( ! typingFile ) {
2016-12-05 23:13:32 +01:00
this . missingTypingsSet . set ( packageName , true ) ;
2016-08-26 01:25:34 +02:00
continue ;
}
2016-12-05 23:13:32 +01:00
const existingTypingFile = this . packageNameToTypingLocation . get ( packageName ) ;
2016-08-15 20:48:28 +02:00
if ( existingTypingFile ) {
2017-12-29 23:21:55 +01:00
if ( existingTypingFile . typingLocation === typingFile ) {
continue ;
}
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` New typing for package ${ packageName } from ' ${ typingFile } ' conflicts with existing typing file ' ${ existingTypingFile } ' ` ) ;
}
}
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Adding entry into typings cache: ' ${ packageName } ' => ' ${ typingFile } ' ` ) ;
2016-08-15 20:48:28 +02:00
}
2017-11-28 05:29:10 +01:00
if ( getProperty ( this . typeDeclarationTimestamps , key ) === undefined ) {
2017-11-21 01:43:02 +01:00
// getModifiedTime is only undefined if we were to use the ChakraHost, but we never do in this scenario
// defaults to old behavior of never updating if we ever use a host without getModifiedTime in the future
const timestamp = this . installTypingHost . getModifiedTime === undefined ? Date . now ( ) : this . installTypingHost . getModifiedTime ( typingFile ) . getTime ( ) ;
2017-11-28 05:29:10 +01:00
this . typeDeclarationTimestamps [ key ] = timestamp ;
2017-11-21 01:43:02 +01:00
}
// timestamp guaranteed to not be undefined by above check
2017-11-28 05:29:10 +01:00
const newTyping : JsTyping.CachedTyping = { typingLocation : typingFile , timestamp : getProperty ( this . typeDeclarationTimestamps , key ) } ;
2017-11-21 01:43:02 +01:00
this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
2016-08-15 20:48:28 +02:00
}
}
}
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Finished processing cache location ' ${ cacheLocation } ' ` ) ;
}
2016-12-05 23:13:32 +01:00
this . knownCachesSet . set ( cacheLocation , true ) ;
2016-08-15 20:48:28 +02:00
}
2017-12-04 22:36:01 +01:00
private filterTypings ( typingsToInstall : ReadonlyArray < string > ) : ReadonlyArray < string > {
return typingsToInstall . filter ( typing = > {
if ( this . missingTypingsSet . get ( typing ) ) {
if ( this . log . isEnabled ( ) ) this . log . writeLine ( ` ' ${ typing } ' is in missingTypingsSet - skipping... ` ) ;
return false ;
2016-10-01 20:56:51 +02:00
}
2017-11-21 01:43:02 +01:00
if ( this . packageNameToTypingLocation . get ( typing ) && ! JsTyping . isTypingExpired ( this . packageNameToTypingLocation . get ( typing ) ) ) {
2017-12-04 22:36:01 +01:00
if ( this . log . isEnabled ( ) ) this . log . writeLine ( ` ' ${ typing } ' already has a typing - skipping... ` ) ;
return false ;
2016-10-01 20:56:51 +02:00
}
2017-12-04 22:36:01 +01:00
const validationResult = JsTyping . validatePackageName ( typing ) ;
if ( validationResult !== JsTyping . PackageNameValidationResult . Ok ) {
2016-10-01 20:56:51 +02:00
// add typing name to missing set so we won't process it again
2016-12-05 23:13:32 +01:00
this . missingTypingsSet . set ( typing , true ) ;
2017-12-04 22:36:01 +01:00
if ( this . log . isEnabled ( ) ) this . log . writeLine ( JsTyping . renderPackageNameValidationFailure ( validationResult , typing ) ) ;
return false ;
2016-10-01 20:56:51 +02:00
}
2017-12-04 22:36:01 +01:00
if ( ! this . typesRegistry . has ( typing ) ) {
if ( this . log . isEnabled ( ) ) this . log . writeLine ( ` Entry for package ' ${ typing } ' does not exist in local types registry - skipping... ` ) ;
return false ;
}
return true ;
} ) ;
2016-10-01 20:56:51 +02:00
}
2016-11-07 22:36:08 +01:00
protected ensurePackageDirectoryExists ( directory : string ) {
const npmConfigPath = combinePaths ( directory , "package.json" ) ;
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-11-07 22:36:08 +01:00
this . log . writeLine ( ` Npm config file: ${ npmConfigPath } ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-11-07 22:36:08 +01:00
if ( ! this . installTypingHost . fileExists ( npmConfigPath ) ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-11-07 22:36:08 +01:00
this . log . writeLine ( ` Npm config file: ' ${ npmConfigPath } ' is missing, creating new one... ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-11-07 22:36:08 +01:00
this . ensureDirectoryExists ( directory , this . installTypingHost ) ;
2017-11-06 18:45:52 +01:00
this . installTypingHost . writeFile ( npmConfigPath , '{ "private": true }' ) ;
2016-08-12 21:14:25 +02:00
}
2016-11-07 22:36:08 +01:00
}
2016-08-12 21:14:25 +02:00
2016-11-07 22:36:08 +01:00
private installTypings ( req : DiscoverTypings , cachePath : string , currentlyCachedTypings : string [ ] , typingsToInstall : string [ ] ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2016-11-07 22:36:08 +01:00
this . log . writeLine ( ` Installing typings ${ JSON . stringify ( typingsToInstall ) } ` ) ;
2016-08-16 00:59:31 +02:00
}
2016-11-11 01:52:36 +01:00
const filteredTypings = this . filterTypings ( typingsToInstall ) ;
2017-01-30 16:49:17 +01:00
if ( filteredTypings . length === 0 ) {
2016-08-16 00:59:31 +02:00
if ( this . log . isEnabled ( ) ) {
2017-11-20 23:43:25 +01:00
this . log . writeLine ( ` All typings are known to be missing or invalid - no need to install more typings ` ) ;
2016-08-16 00:59:31 +02:00
}
2017-11-20 23:43:25 +01:00
this . sendResponse ( this . createSetTypings ( req , currentlyCachedTypings ) ) ;
2016-11-07 22:36:08 +01:00
return ;
2016-08-12 20:04:43 +02:00
}
2016-08-12 21:14:25 +02:00
2016-11-07 22:36:08 +01:00
this . ensurePackageDirectoryExists ( cachePath ) ;
const requestId = this . installRunCount ;
this . installRunCount ++ ;
2016-11-29 19:14:22 +01:00
// send progress event
this . sendResponse ( < BeginInstallTypes > {
kind : EventBeginInstallTypes ,
eventId : requestId ,
typingsInstallerVersion : ts.version , // qualified explicitly to prevent occasional shadowing
projectName : req.projectName
} ) ;
2017-01-30 16:49:17 +01:00
const scopedTypings = filteredTypings . map ( typingsName ) ;
2016-11-07 22:36:08 +01:00
this . installTypingsAsync ( requestId , scopedTypings , cachePath , ok = > {
2016-11-29 19:14:22 +01:00
try {
if ( ! ok ) {
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` install request failed, marking packages as missing to prevent repeated requests: ${ JSON . stringify ( filteredTypings ) } ` ) ;
}
for ( const typing of filteredTypings ) {
2016-12-05 23:13:32 +01:00
this . missingTypingsSet . set ( typing , true ) ;
2016-11-29 19:14:22 +01:00
}
return ;
}
2016-11-07 22:36:08 +01:00
2016-11-29 19:14:22 +01:00
// TODO: watch project directory
2016-11-11 01:52:36 +01:00
if ( this . log . isEnabled ( ) ) {
2016-11-29 19:14:22 +01:00
this . log . writeLine ( ` Installed typings ${ JSON . stringify ( scopedTypings ) } ` ) ;
2016-11-11 01:52:36 +01:00
}
2016-11-29 19:14:22 +01:00
const installedTypingFiles : string [ ] = [ ] ;
for ( const packageName of filteredTypings ) {
const typingFile = typingToFileName ( cachePath , packageName , this . installTypingHost , this . log ) ;
if ( ! typingFile ) {
2016-12-05 23:13:32 +01:00
this . missingTypingsSet . set ( packageName , true ) ;
2016-11-29 19:14:22 +01:00
continue ;
}
2017-11-28 05:29:10 +01:00
const newTimestamp = Date . now ( ) ;
const newTyping : JsTyping.CachedTyping = { typingLocation : typingFile , timestamp : newTimestamp } ;
this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
this . typeDeclarationTimestamps [ packageName ] = newTimestamp ;
2016-11-29 19:14:22 +01:00
installedTypingFiles . push ( typingFile ) ;
2016-08-26 01:25:34 +02:00
}
2016-11-29 19:14:22 +01:00
if ( this . log . isEnabled ( ) ) {
this . log . writeLine ( ` Installed typing files ${ JSON . stringify ( installedTypingFiles ) } ` ) ;
2016-09-01 06:14:24 +02:00
}
2016-11-29 19:14:22 +01:00
2017-11-21 01:43:02 +01:00
const newFileContents : TypeDeclarationTimestampFile = { entries : this.typeDeclarationTimestamps } ;
writeTypeDeclarationTimestampFile ( cachePath , newFileContents , this . installTypingHost , this . log ) ; // WRONG PATH
2016-11-29 19:14:22 +01:00
this . sendResponse ( this . createSetTypings ( req , currentlyCachedTypings . concat ( installedTypingFiles ) ) ) ;
2016-08-16 00:59:31 +02:00
}
2016-11-29 19:14:22 +01:00
finally {
2017-08-10 21:52:15 +02:00
const response : EndInstallTypes = {
2016-11-29 19:14:22 +01:00
kind : EventEndInstallTypes ,
eventId : requestId ,
projectName : req.projectName ,
packagesToInstall : scopedTypings ,
installSuccess : ok ,
typingsInstallerVersion : ts.version // qualified explicitly to prevent occasional shadowing
2017-08-10 21:52:15 +02:00
} ;
this . sendResponse ( response ) ;
2016-08-15 20:48:28 +02:00
}
2016-08-12 21:14:25 +02:00
} ) ;
2016-08-12 20:04:43 +02:00
}
2016-08-13 08:04:17 +02:00
private ensureDirectoryExists ( directory : string , host : InstallTypingHost ) : void {
const directoryName = getDirectoryPath ( directory ) ;
if ( ! host . directoryExists ( directoryName ) ) {
this . ensureDirectoryExists ( directoryName , host ) ;
}
if ( ! host . directoryExists ( directory ) ) {
host . createDirectory ( directory ) ;
}
}
2016-08-16 23:49:45 +02:00
private watchFiles ( projectName : string , files : string [ ] ) {
2016-08-16 23:21:09 +02:00
if ( ! files . length ) {
return ;
}
2016-08-17 22:01:48 +02:00
// shut down existing watchers
this . closeWatchers ( projectName ) ;
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
let isInvoked = false ;
2016-08-16 23:21:09 +02:00
const watchers : FileWatcher [ ] = [ ] ;
for ( const file of files ) {
const w = this . installTypingHost . watchFile ( file , f = > {
if ( this . log . isEnabled ( ) ) {
2016-08-17 22:01:48 +02:00
this . log . writeLine ( ` Got FS notification for ${ f } , handler is already invoked ' ${ isInvoked } ' ` ) ;
2016-08-16 23:21:09 +02:00
}
2016-10-21 06:15:47 +02:00
if ( ! isInvoked ) {
2017-07-07 16:26:58 +02:00
this . sendResponse ( { projectName , kind : server.ActionInvalidate } ) ;
2016-10-21 06:15:47 +02:00
isInvoked = true ;
}
2016-11-08 06:13:11 +01:00
} , /*pollingInterval*/ 2000 ) ;
2016-08-16 23:21:09 +02:00
watchers . push ( w ) ;
}
2016-12-05 23:13:32 +01:00
this . projectWatchers . set ( projectName , watchers ) ;
2016-08-12 20:04:43 +02:00
}
2016-08-16 23:21:09 +02:00
private createSetTypings ( request : DiscoverTypings , typings : string [ ] ) : SetTypings {
2016-08-12 20:04:43 +02:00
return {
projectName : request.projectName ,
2016-11-19 02:46:06 +01:00
typeAcquisition : request.typeAcquisition ,
2016-08-12 20:04:43 +02:00
compilerOptions : request.compilerOptions ,
2016-08-16 23:21:09 +02:00
typings ,
2016-10-26 00:24:21 +02:00
unresolvedImports : request.unresolvedImports ,
2016-11-10 23:28:34 +01:00
kind : ActionSet
2016-08-12 20:04:43 +02:00
} ;
}
2017-10-18 00:04:09 +02:00
private installTypingsAsync ( requestId : number , packageNames : string [ ] , cwd : string , onRequestCompleted : RequestCompletedAction ) : void {
this . pendingRunRequests . unshift ( { requestId , packageNames , cwd , onRequestCompleted } ) ;
2016-09-20 23:14:51 +02:00
this . executeWithThrottling ( ) ;
}
private executeWithThrottling() {
while ( this . inFlightRequestCount < this . throttleLimit && this . pendingRunRequests . length ) {
this . inFlightRequestCount ++ ;
const request = this . pendingRunRequests . pop ( ) ;
2017-10-18 00:04:09 +02:00
this . installWorker ( request . requestId , request . packageNames , request . cwd , ok = > {
2016-09-20 23:14:51 +02:00
this . inFlightRequestCount -- ;
2016-10-13 19:19:18 +02:00
request . onRequestCompleted ( ok ) ;
2016-09-20 23:14:51 +02:00
this . executeWithThrottling ( ) ;
} ) ;
}
}
2017-10-18 00:04:09 +02:00
protected abstract installWorker ( requestId : number , packageNames : string [ ] , cwd : string , onRequestCompleted : RequestCompletedAction ) : void ;
2016-11-29 19:14:22 +01:00
protected abstract sendResponse ( response : SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes ) : void ;
2016-08-12 20:04:43 +02:00
}
2017-01-30 16:49:17 +01:00
/* @internal */
export function typingsName ( packageName : string ) : string {
return ` @types/ ${ packageName } @ts ${ versionMajorMinor } ` ;
}
2016-08-12 20:04:43 +02:00
}