@@ -45,15 +45,6 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use
45
45
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' ;
46
46
47
47
type GalleryExtensionInfo = { readonly id : string ; preRelease ?: boolean ; migrateStorageFrom ?: string } ;
48
- interface HostedExtensionInfo {
49
- readonly location : UriComponents ;
50
- readonly preRelease ?: boolean ;
51
- readonly packageJSON ?: IExtensionManifest ;
52
- readonly defaultPackageTranslations ?: ITranslations | null ;
53
- readonly packageNLSUris ?: Map < string , UriComponents > ;
54
- readonly readmeUri ?: UriComponents ;
55
- readonly changelogUri ?: UriComponents ;
56
- }
57
48
type ExtensionInfo = { readonly id : string ; preRelease : boolean } ;
58
49
59
50
function isGalleryExtensionInfo ( obj : unknown ) : obj is GalleryExtensionInfo {
@@ -63,16 +54,6 @@ function isGalleryExtensionInfo(obj: unknown): obj is GalleryExtensionInfo {
63
54
&& ( galleryExtensionInfo . migrateStorageFrom === undefined || typeof galleryExtensionInfo . migrateStorageFrom === 'string' ) ;
64
55
}
65
56
66
- function isHostedExtensionInfo ( obj : unknown ) : obj is HostedExtensionInfo {
67
- const hostedExtensionInfo = obj as HostedExtensionInfo | undefined ;
68
- return isUriComponents ( hostedExtensionInfo ?. location )
69
- && ( hostedExtensionInfo ?. preRelease === undefined || typeof hostedExtensionInfo . preRelease === 'boolean' )
70
- && ( hostedExtensionInfo ?. packageJSON === undefined || typeof hostedExtensionInfo . packageJSON === 'object' )
71
- && ( hostedExtensionInfo ?. defaultPackageTranslations === undefined || hostedExtensionInfo ?. defaultPackageTranslations === null || typeof hostedExtensionInfo . defaultPackageTranslations === 'object' )
72
- && ( hostedExtensionInfo ?. changelogUri === undefined || isUriComponents ( hostedExtensionInfo ?. changelogUri ) )
73
- && ( hostedExtensionInfo ?. readmeUri === undefined || isUriComponents ( hostedExtensionInfo ?. readmeUri ) ) ;
74
- }
75
-
76
57
function isUriComponents ( thing : unknown ) : thing is UriComponents {
77
58
if ( ! thing ) {
78
59
return false ;
@@ -144,12 +125,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
144
125
}
145
126
}
146
127
147
- private _customBuiltinExtensionsInfoPromise : Promise < { extensions : ExtensionInfo [ ] ; extensionsToMigrate : [ string , string ] [ ] ; extensionLocations : HostedExtensionInfo [ ] } > | undefined ;
148
- private readCustomBuiltinExtensionsInfoFromEnv ( ) : Promise < { extensions : ExtensionInfo [ ] ; extensionsToMigrate : [ string , string ] [ ] ; extensionLocations : HostedExtensionInfo [ ] } > {
128
+ private _customBuiltinExtensionsInfoPromise : Promise < { extensions : ExtensionInfo [ ] ; extensionsToMigrate : [ string , string ] [ ] ; extensionLocations : URI [ ] ; extensionGalleryResources : URI [ ] } > | undefined ;
129
+ private readCustomBuiltinExtensionsInfoFromEnv ( ) : Promise < { extensions : ExtensionInfo [ ] ; extensionsToMigrate : [ string , string ] [ ] ; extensionLocations : URI [ ] ; extensionGalleryResources : URI [ ] } > {
149
130
if ( ! this . _customBuiltinExtensionsInfoPromise ) {
150
131
this . _customBuiltinExtensionsInfoPromise = ( async ( ) => {
151
132
let extensions : ExtensionInfo [ ] = [ ] ;
152
- const extensionLocations : HostedExtensionInfo [ ] = [ ] ;
133
+ const extensionLocations : URI [ ] = [ ] ;
134
+ const extensionGalleryResources : URI [ ] = [ ] ;
153
135
const extensionsToMigrate : [ string , string ] [ ] = [ ] ;
154
136
const customBuiltinExtensionsInfo = this . environmentService . options && Array . isArray ( this . environmentService . options . additionalBuiltinExtensions )
155
137
? this . environmentService . options . additionalBuiltinExtensions . map ( additionalBuiltinExtension => isString ( additionalBuiltinExtension ) ? { id : additionalBuiltinExtension } : additionalBuiltinExtension )
@@ -160,11 +142,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
160
142
if ( e . migrateStorageFrom ) {
161
143
extensionsToMigrate . push ( [ e . migrateStorageFrom , e . id ] ) ;
162
144
}
163
- } else {
164
- if ( isHostedExtensionInfo ( e ) ) {
165
- extensionLocations . push ( e ) ;
145
+ } else if ( isUriComponents ( e ) ) {
146
+ const extensionLocation = URI . revive ( e ) ;
147
+ if ( this . extensionResourceLoaderService . isExtensionGalleryResource ( extensionLocation ) ) {
148
+ extensionGalleryResources . push ( extensionLocation ) ;
166
149
} else {
167
- extensionLocations . push ( { location : e } ) ;
150
+ extensionLocations . push ( extensionLocation ) ;
168
151
}
169
152
}
170
153
}
@@ -177,7 +160,10 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
177
160
if ( extensionLocations . length ) {
178
161
this . logService . info ( 'Found additional builtin location extensions in env' , extensionLocations . map ( e => e . toString ( ) ) ) ;
179
162
}
180
- return { extensions, extensionsToMigrate, extensionLocations } ;
163
+ if ( extensionGalleryResources . length ) {
164
+ this . logService . info ( 'Found additional builtin extension gallery resources in env' , extensionGalleryResources . map ( e => e . toString ( ) ) ) ;
165
+ }
166
+ return { extensions, extensionsToMigrate, extensionLocations, extensionGalleryResources } ;
181
167
} ) ( ) ;
182
168
}
183
169
return this . _customBuiltinExtensionsInfoPromise ;
@@ -243,15 +229,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
243
229
return [ ] ;
244
230
}
245
231
const result : IScannedExtension [ ] = [ ] ;
246
- await Promise . allSettled ( extensionLocations . map ( async ( { location , preRelease , packageNLSUris , packageJSON , defaultPackageTranslations , readmeUri , changelogUri } ) => {
232
+ await Promise . allSettled ( extensionLocations . map ( async extensionLocation => {
247
233
try {
248
- const webExtension = await this . toWebExtension ( URI . revive ( location ) , undefined ,
249
- packageJSON ,
250
- packageNLSUris ? [ ...packageNLSUris . entries ( ) ] . reduce ( ( result , [ key , value ] ) => { result . set ( key , URI . revive ( value ) ) ; return result ; } , new Map < string , URI > ( ) ) : undefined ,
251
- defaultPackageTranslations ,
252
- URI . revive ( readmeUri ) ,
253
- URI . revive ( changelogUri ) ,
254
- { isPreReleaseVersion : preRelease } ) ;
234
+ const webExtension = await this . toWebExtension ( extensionLocation ) ;
255
235
const extension = await this . toScannedExtension ( webExtension , true ) ;
256
236
if ( extension . isValid || ! scanOptions ?. skipInvalidExtensions ) {
257
237
result . push ( extension ) ;
@@ -271,9 +251,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
271
251
return [ ] ;
272
252
}
273
253
const result : IScannedExtension [ ] = [ ] ;
274
- const { extensions } = await this . readCustomBuiltinExtensionsInfoFromEnv ( ) ;
254
+ const { extensions, extensionGalleryResources } = await this . readCustomBuiltinExtensionsInfoFromEnv ( ) ;
275
255
try {
276
- const useCache = this . storageService . get ( 'additionalBuiltinExtensions' , StorageScope . APPLICATION , '[]' ) === JSON . stringify ( extensions ) ;
256
+ const cacheValue = JSON . stringify ( {
257
+ extensions : extensions . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ,
258
+ extensionGalleryResources : extensionGalleryResources . map ( e => e . toString ( ) ) . sort ( )
259
+ } ) ;
260
+ const useCache = this . storageService . get ( 'additionalBuiltinExtensions' , StorageScope . APPLICATION , '{}' ) === cacheValue ;
277
261
const webExtensions = await ( useCache ? this . getCustomBuiltinExtensionsFromCache ( ) : this . updateCustomBuiltinExtensionsCache ( ) ) ;
278
262
if ( webExtensions . length ) {
279
263
await Promise . all ( webExtensions . map ( async webExtension => {
@@ -289,7 +273,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
289
273
}
290
274
} ) ) ;
291
275
}
292
- this . storageService . store ( 'additionalBuiltinExtensions' , JSON . stringify ( extensions ) , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
276
+ this . storageService . store ( 'additionalBuiltinExtensions' , cacheValue , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
293
277
} catch ( error ) {
294
278
this . logService . info ( 'Ignoring following additional builtin extensions as there is an error while fetching them from gallery' , extensions . map ( ( { id } ) => id ) , getErrorMessage ( error ) ) ;
295
279
}
@@ -366,31 +350,95 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
366
350
if ( ! this . _updateCustomBuiltinExtensionsCachePromise ) {
367
351
this . _updateCustomBuiltinExtensionsCachePromise = ( async ( ) => {
368
352
this . logService . info ( 'Updating additional builtin extensions cache' ) ;
369
- const webExtensions : IWebExtension [ ] = [ ] ;
370
- const { extensions } = await this . readCustomBuiltinExtensionsInfoFromEnv ( ) ;
371
- if ( extensions . length ) {
372
- const galleryExtensionsMap = await this . getExtensionsWithDependenciesAndPackedExtensions ( extensions ) ;
373
- const missingExtensions = extensions . filter ( ( { id } ) => ! galleryExtensionsMap . has ( id . toLowerCase ( ) ) ) ;
374
- if ( missingExtensions . length ) {
375
- this . logService . info ( 'Skipping the additional builtin extensions because their compatible versions are not found.' , missingExtensions ) ;
376
- }
377
- await Promise . all ( [ ...galleryExtensionsMap . values ( ) ] . map ( async gallery => {
378
- try {
379
- const webExtension = await this . toWebExtensionFromGallery ( gallery , { isPreReleaseVersion : gallery . properties . isPreReleaseVersion , preRelease : gallery . properties . isPreReleaseVersion , isBuiltin : true } ) ;
380
- webExtensions . push ( webExtension ) ;
381
- } catch ( error ) {
382
- this . logService . info ( `Ignoring additional builtin extension ${ gallery . identifier . id } because there is an error while converting it into web extension` , getErrorMessage ( error ) ) ;
383
- }
384
- } ) ) ;
353
+ const { extensions, extensionGalleryResources } = await this . readCustomBuiltinExtensionsInfoFromEnv ( ) ;
354
+ const [ galleryWebExtensions , extensionGalleryResourceWebExtensions ] = await Promise . all ( [
355
+ this . resolveBuiltinGalleryExtensions ( extensions ) ,
356
+ this . resolveBuiltinExtensionGalleryResources ( extensionGalleryResources )
357
+ ] ) ;
358
+ const webExtensionsMap = new Map < string , IWebExtension > ( ) ;
359
+ for ( const webExtension of [ ...galleryWebExtensions , ...extensionGalleryResourceWebExtensions ] ) {
360
+ webExtensionsMap . set ( webExtension . identifier . id . toLowerCase ( ) , webExtension ) ;
385
361
}
362
+ await this . resolveDependenciesAndPackedExtensions ( extensionGalleryResourceWebExtensions , webExtensionsMap ) ;
363
+ const webExtensions = [ ...webExtensionsMap . values ( ) ] ;
386
364
await this . writeCustomBuiltinExtensionsCache ( ( ) => webExtensions ) ;
387
365
return webExtensions ;
388
366
} ) ( ) ;
389
367
}
390
368
return this . _updateCustomBuiltinExtensionsCachePromise ;
391
369
}
392
370
393
- private async getExtensionsWithDependenciesAndPackedExtensions ( toGet : IExtensionInfo [ ] , result : Map < string , IGalleryExtension > = new Map < string , IGalleryExtension > ( ) ) : Promise < Map < string , IGalleryExtension > > {
371
+ private async resolveBuiltinExtensionGalleryResources ( extensionGalleryResources : URI [ ] ) : Promise < IWebExtension [ ] > {
372
+ if ( extensionGalleryResources . length === 0 ) {
373
+ return [ ] ;
374
+ }
375
+ const result = new Map < string , IWebExtension > ( ) ;
376
+ const extensionInfos : IExtensionInfo [ ] = [ ] ;
377
+ await Promise . all ( extensionGalleryResources . map ( async extensionGalleryResource => {
378
+ const webExtension = await this . toWebExtensionFromExtensionGalleryResource ( extensionGalleryResource ) ;
379
+ result . set ( webExtension . identifier . id . toLowerCase ( ) , webExtension ) ;
380
+ extensionInfos . push ( { id : webExtension . identifier . id , version : webExtension . version } ) ;
381
+ } ) ) ;
382
+ const galleryExtensions = await this . galleryService . getExtensions ( extensionInfos , CancellationToken . None ) ;
383
+ for ( const galleryExtension of galleryExtensions ) {
384
+ const webExtension = result . get ( galleryExtension . identifier . id . toLowerCase ( ) ) ;
385
+ if ( webExtension ) {
386
+ result . set ( galleryExtension . identifier . id . toLowerCase ( ) , {
387
+ ...webExtension ,
388
+ readmeUri : galleryExtension . assets . readme ? URI . parse ( galleryExtension . assets . readme . uri ) : undefined ,
389
+ changelogUri : galleryExtension . assets . changelog ? URI . parse ( galleryExtension . assets . changelog . uri ) : undefined ,
390
+ metadata : { isPreReleaseVersion : galleryExtension . properties . isPreReleaseVersion , preRelease : galleryExtension . properties . isPreReleaseVersion , isBuiltin : true }
391
+ } ) ;
392
+ }
393
+ }
394
+ return [ ...result . values ( ) ] ;
395
+ }
396
+
397
+ private async resolveBuiltinGalleryExtensions ( extensions : IExtensionInfo [ ] ) : Promise < IWebExtension [ ] > {
398
+ if ( extensions . length === 0 ) {
399
+ return [ ] ;
400
+ }
401
+ const webExtensions : IWebExtension [ ] = [ ] ;
402
+ const galleryExtensionsMap = await this . getExtensionsWithDependenciesAndPackedExtensions ( extensions ) ;
403
+ const missingExtensions = extensions . filter ( ( { id } ) => ! galleryExtensionsMap . has ( id . toLowerCase ( ) ) ) ;
404
+ if ( missingExtensions . length ) {
405
+ this . logService . info ( 'Skipping the additional builtin extensions because their compatible versions are not found.' , missingExtensions ) ;
406
+ }
407
+ await Promise . all ( [ ...galleryExtensionsMap . values ( ) ] . map ( async gallery => {
408
+ try {
409
+ const webExtension = await this . toWebExtensionFromGallery ( gallery , { isPreReleaseVersion : gallery . properties . isPreReleaseVersion , preRelease : gallery . properties . isPreReleaseVersion , isBuiltin : true } ) ;
410
+ webExtensions . push ( webExtension ) ;
411
+ } catch ( error ) {
412
+ this . logService . info ( `Ignoring additional builtin extension ${ gallery . identifier . id } because there is an error while converting it into web extension` , getErrorMessage ( error ) ) ;
413
+ }
414
+ } ) ) ;
415
+ return webExtensions ;
416
+ }
417
+
418
+ private async resolveDependenciesAndPackedExtensions ( webExtensions : IWebExtension [ ] , result : Map < string , IWebExtension > ) : Promise < void > {
419
+ const extensionInfos : IExtensionInfo [ ] = [ ] ;
420
+ for ( const webExtension of webExtensions ) {
421
+ for ( const e of [ ...( webExtension . manifest ?. extensionDependencies ?? [ ] ) , ...( webExtension . manifest ?. extensionPack ?? [ ] ) ] ) {
422
+ if ( ! result . has ( e . toLowerCase ( ) ) ) {
423
+ extensionInfos . push ( { id : e , version : webExtension . version } ) ;
424
+ }
425
+ }
426
+ }
427
+ if ( extensionInfos . length === 0 ) {
428
+ return ;
429
+ }
430
+ const galleryExtensions = await this . getExtensionsWithDependenciesAndPackedExtensions ( extensionInfos , new Set < string > ( [ ...result . keys ( ) ] ) ) ;
431
+ await Promise . all ( [ ...galleryExtensions . values ( ) ] . map ( async gallery => {
432
+ try {
433
+ const webExtension = await this . toWebExtensionFromGallery ( gallery , { isPreReleaseVersion : gallery . properties . isPreReleaseVersion , preRelease : gallery . properties . isPreReleaseVersion , isBuiltin : true } ) ;
434
+ result . set ( webExtension . identifier . id . toLowerCase ( ) , webExtension ) ;
435
+ } catch ( error ) {
436
+ this . logService . info ( `Ignoring additional builtin extension ${ gallery . identifier . id } because there is an error while converting it into web extension` , getErrorMessage ( error ) ) ;
437
+ }
438
+ } ) ) ;
439
+ }
440
+
441
+ private async getExtensionsWithDependenciesAndPackedExtensions ( toGet : IExtensionInfo [ ] , seen : Set < string > = new Set < string > ( ) , result : Map < string , IGalleryExtension > = new Map < string , IGalleryExtension > ( ) ) : Promise < Map < string , IGalleryExtension > > {
394
442
if ( toGet . length === 0 ) {
395
443
return result ;
396
444
}
@@ -399,13 +447,13 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
399
447
for ( const extension of extensions ) {
400
448
result . set ( extension . identifier . id . toLowerCase ( ) , extension ) ;
401
449
for ( const id of [ ...( isNonEmptyArray ( extension . properties . dependencies ) ? extension . properties . dependencies : [ ] ) , ...( isNonEmptyArray ( extension . properties . extensionPack ) ? extension . properties . extensionPack : [ ] ) ] ) {
402
- if ( ! result . has ( id . toLowerCase ( ) ) && ! packsAndDependencies . has ( id . toLowerCase ( ) ) ) {
450
+ if ( ! result . has ( id . toLowerCase ( ) ) && ! packsAndDependencies . has ( id . toLowerCase ( ) ) && ! seen . has ( id . toLowerCase ( ) ) ) {
403
451
const extensionInfo = toGet . find ( e => areSameExtensions ( e , extension . identifier ) ) ;
404
452
packsAndDependencies . set ( id . toLowerCase ( ) , { id, preRelease : extensionInfo ?. preRelease } ) ;
405
453
}
406
454
}
407
455
}
408
- return this . getExtensionsWithDependenciesAndPackedExtensions ( [ ...packsAndDependencies . values ( ) ] . filter ( ( { id } ) => ! result . has ( id . toLowerCase ( ) ) ) , result ) ;
456
+ return this . getExtensionsWithDependenciesAndPackedExtensions ( [ ...packsAndDependencies . values ( ) ] . filter ( ( { id } ) => ! result . has ( id . toLowerCase ( ) ) ) , seen , result ) ;
409
457
}
410
458
411
459
async scanSystemExtensions ( ) : Promise < IExtension [ ] > {
@@ -606,19 +654,28 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
606
654
if ( ! extensionLocation ) {
607
655
throw new Error ( 'No extension gallery service configured.' ) ;
608
656
}
657
+
658
+ return this . toWebExtensionFromExtensionGalleryResource ( extensionLocation ,
659
+ galleryExtension . identifier ,
660
+ galleryExtension . assets . readme ? URI . parse ( galleryExtension . assets . readme . uri ) : undefined ,
661
+ galleryExtension . assets . changelog ? URI . parse ( galleryExtension . assets . changelog . uri ) : undefined ,
662
+ metadata ) ;
663
+ }
664
+
665
+ private async toWebExtensionFromExtensionGalleryResource ( extensionLocation : URI , identifier ?: IExtensionIdentifier , readmeUri ?: URI , changelogUri ?: URI , metadata ?: Metadata ) : Promise < IWebExtension > {
609
666
const extensionResources = await this . listExtensionResources ( extensionLocation ) ;
610
667
const packageNLSResources = this . getPackageNLSResourceMapFromResources ( extensionResources ) ;
611
668
612
669
// The fallback, in English, will fill in any gaps missing in the localized file.
613
670
const fallbackPackageNLSResource = extensionResources . find ( e => basename ( e ) === 'package.nls.json' ) ;
614
671
return this . toWebExtension (
615
672
extensionLocation ,
616
- galleryExtension . identifier ,
673
+ identifier ,
617
674
undefined ,
618
675
packageNLSResources ,
619
676
fallbackPackageNLSResource ? URI . parse ( fallbackPackageNLSResource ) : null ,
620
- galleryExtension . assets . readme ? URI . parse ( galleryExtension . assets . readme . uri ) : undefined ,
621
- galleryExtension . assets . changelog ? URI . parse ( galleryExtension . assets . changelog . uri ) : undefined ,
677
+ readmeUri ,
678
+ changelogUri ,
622
679
metadata ) ;
623
680
}
624
681
0 commit comments