1
1
/// <reference path="../../compiler/core.ts" />
2
2
/// <reference path="../../compiler/moduleNameResolver.ts" />
3
3
/// <reference path="../../services/jsTyping.ts"/>
4
+ /// <reference path="../../services/semver.ts"/>
4
5
/// <reference path="../types.ts"/>
5
6
/// <reference path="../shared.ts"/>
6
7
@@ -9,6 +10,10 @@ namespace ts.server.typingsInstaller {
9
10
devDependencies : MapLike < any > ;
10
11
}
11
12
13
+ interface NpmLock {
14
+ dependencies : { [ packageName : string ] : { version : string } } ;
15
+ }
16
+
12
17
export interface Log {
13
18
isEnabled ( ) : boolean ;
14
19
writeLine ( text : string ) : void ;
@@ -42,7 +47,7 @@ namespace ts.server.typingsInstaller {
42
47
}
43
48
44
49
export abstract class TypingsInstaller {
45
- private readonly packageNameToTypingLocation : Map < string > = createMap < string > ( ) ;
50
+ private readonly packageNameToTypingLocation : Map < JsTyping . CachedTyping > = createMap < JsTyping . CachedTyping > ( ) ;
46
51
private readonly missingTypingsSet : Map < true > = createMap < true > ( ) ;
47
52
private readonly knownCachesSet : Map < true > = createMap < true > ( ) ;
48
53
private readonly projectWatchers : Map < FileWatcher [ ] > = createMap < FileWatcher [ ] > ( ) ;
@@ -52,7 +57,7 @@ namespace ts.server.typingsInstaller {
52
57
private installRunCount = 1 ;
53
58
private inFlightRequestCount = 0 ;
54
59
55
- abstract readonly typesRegistry : Map < void > ;
60
+ abstract readonly typesRegistry : Map < MapLike < string > > ;
56
61
57
62
constructor (
58
63
protected readonly installTypingHost : InstallTypingHost ,
@@ -117,7 +122,8 @@ namespace ts.server.typingsInstaller {
117
122
this . safeList ,
118
123
this . packageNameToTypingLocation ,
119
124
req . typeAcquisition ,
120
- req . unresolvedImports ) ;
125
+ req . unresolvedImports ,
126
+ this . typesRegistry ) ;
121
127
122
128
if ( this . log . isEnabled ( ) ) {
123
129
this . log . writeLine ( `Finished typings discovery: ${ JSON . stringify ( discoverTypingsResult ) } ` ) ;
@@ -156,23 +162,30 @@ namespace ts.server.typingsInstaller {
156
162
if ( this . log . isEnabled ( ) ) {
157
163
this . log . writeLine ( `Processing cache location '${ cacheLocation } '` ) ;
158
164
}
159
- if ( this . knownCachesSet . get ( cacheLocation ) ) {
165
+ if ( this . knownCachesSet . has ( cacheLocation ) ) {
160
166
if ( this . log . isEnabled ( ) ) {
161
167
this . log . writeLine ( `Cache location was already processed...` ) ;
162
168
}
163
169
return ;
164
170
}
165
171
const packageJson = combinePaths ( cacheLocation , "package.json" ) ;
172
+ const packageLockJson = combinePaths ( cacheLocation , "package-lock.json" ) ;
166
173
if ( this . log . isEnabled ( ) ) {
167
174
this . log . writeLine ( `Trying to find '${ packageJson } '...` ) ;
168
175
}
169
- if ( this . installTypingHost . fileExists ( packageJson ) ) {
176
+ if ( this . installTypingHost . fileExists ( packageJson ) && this . installTypingHost . fileExists ( packageLockJson ) ) {
170
177
const npmConfig = < NpmConfig > JSON . parse ( this . installTypingHost . readFile ( packageJson ) ) ;
178
+ const npmLock = < NpmLock > JSON . parse ( this . installTypingHost . readFile ( packageLockJson ) ) ;
171
179
if ( this . log . isEnabled ( ) ) {
172
180
this . log . writeLine ( `Loaded content of '${ packageJson } ': ${ JSON . stringify ( npmConfig ) } ` ) ;
181
+ this . log . writeLine ( `Loaded content of '${ packageLockJson } '` ) ;
173
182
}
174
- if ( npmConfig . devDependencies ) {
183
+ if ( npmConfig . devDependencies && npmLock . dependencies ) {
175
184
for ( const key in npmConfig . devDependencies ) {
185
+ if ( ! hasProperty ( npmLock . dependencies , key ) ) {
186
+ // if package in package.json but not package-lock.json, skip adding to cache so it is reinstalled on next use
187
+ continue ;
188
+ }
176
189
// key is @types /<package name>
177
190
const packageName = getBaseFileName ( key ) ;
178
191
if ( ! packageName ) {
@@ -184,18 +197,23 @@ namespace ts.server.typingsInstaller {
184
197
continue ;
185
198
}
186
199
const existingTypingFile = this . packageNameToTypingLocation . get ( packageName ) ;
187
- if ( existingTypingFile === typingFile ) {
188
- continue ;
189
- }
190
200
if ( existingTypingFile ) {
201
+ if ( existingTypingFile . typingLocation === typingFile ) {
202
+ continue ;
203
+ }
204
+
191
205
if ( this . log . isEnabled ( ) ) {
192
206
this . log . writeLine ( `New typing for package ${ packageName } from '${ typingFile } ' conflicts with existing typing file '${ existingTypingFile } '` ) ;
193
207
}
194
208
}
195
209
if ( this . log . isEnabled ( ) ) {
196
210
this . log . writeLine ( `Adding entry into typings cache: '${ packageName } ' => '${ typingFile } '` ) ;
197
211
}
198
- this . packageNameToTypingLocation . set ( packageName , typingFile ) ;
212
+ const info = getProperty ( npmLock . dependencies , key ) ;
213
+ const version = info && info . version ;
214
+ const semver = Semver . parse ( version ) ;
215
+ const newTyping : JsTyping . CachedTyping = { typingLocation : typingFile , version : semver } ;
216
+ this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
199
217
}
200
218
}
201
219
}
@@ -211,10 +229,6 @@ namespace ts.server.typingsInstaller {
211
229
if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' is in missingTypingsSet - skipping...` ) ;
212
230
return false ;
213
231
}
214
- if ( this . packageNameToTypingLocation . get ( typing ) ) {
215
- if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' already has a typing - skipping...` ) ;
216
- return false ;
217
- }
218
232
const validationResult = JsTyping . validatePackageName ( typing ) ;
219
233
if ( validationResult !== JsTyping . PackageNameValidationResult . Ok ) {
220
234
// add typing name to missing set so we won't process it again
@@ -226,6 +240,10 @@ namespace ts.server.typingsInstaller {
226
240
if ( this . log . isEnabled ( ) ) this . log . writeLine ( `Entry for package '${ typing } ' does not exist in local types registry - skipping...` ) ;
227
241
return false ;
228
242
}
243
+ if ( this . packageNameToTypingLocation . get ( typing ) && JsTyping . isTypingUpToDate ( this . packageNameToTypingLocation . get ( typing ) , this . typesRegistry . get ( typing ) ) ) {
244
+ if ( this . log . isEnabled ( ) ) this . log . writeLine ( `'${ typing } ' already has an up-to-date typing - skipping...` ) ;
245
+ return false ;
246
+ }
229
247
return true ;
230
248
} ) ;
231
249
}
@@ -294,9 +312,12 @@ namespace ts.server.typingsInstaller {
294
312
this . missingTypingsSet . set ( packageName , true ) ;
295
313
continue ;
296
314
}
297
- if ( ! this . packageNameToTypingLocation . has ( packageName ) ) {
298
- this . packageNameToTypingLocation . set ( packageName , typingFile ) ;
299
- }
315
+
316
+ // packageName is guaranteed to exist in typesRegistry by filterTypings
317
+ const distTags = this . typesRegistry . get ( packageName ) ;
318
+ const newVersion = Semver . parse ( distTags [ `ts${ ts . versionMajorMinor } ` ] || distTags [ latestDistTag ] ) ;
319
+ const newTyping : JsTyping . CachedTyping = { typingLocation : typingFile , version : newVersion } ;
320
+ this . packageNameToTypingLocation . set ( packageName , newTyping ) ;
300
321
installedTypingFiles . push ( typingFile ) ;
301
322
}
302
323
if ( this . log . isEnabled ( ) ) {
@@ -390,4 +411,6 @@ namespace ts.server.typingsInstaller {
390
411
export function typingsName ( packageName : string ) : string {
391
412
return `@types/${ packageName } @ts${ versionMajorMinor } ` ;
392
413
}
414
+
415
+ const latestDistTag = "latest" ;
393
416
}
0 commit comments