44 *--------------------------------------------------------------------------------------------*/
55
66import { CancellationToken } from '../../../../base/common/cancellation.js' ;
7+ import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js' ;
78import { IEnvironmentService } from '../../../../platform/environment/common/environment.js' ;
89import { EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT , IExtensionGalleryService , IExtensionManagementService , InstallExtensionInfo } from '../../../../platform/extensionManagement/common/extensionManagement.js' ;
910import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js' ;
1011import { IFileService } from '../../../../platform/files/common/files.js' ;
1112import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js' ;
1213import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js' ;
1314import { ILogService } from '../../../../platform/log/common/log.js' ;
15+ import { IProductService } from '../../../../platform/product/common/productService.js' ;
16+ import { REMOTE_DEFAULT_EXTENSIONS } from '../../../../platform/remote/common/remote.js' ;
1417import { IRemoteAuthorityResolverService } from '../../../../platform/remote/common/remoteAuthorityResolver.js' ;
1518import { IRemoteExtensionsScannerService } from '../../../../platform/remote/common/remoteExtensionsScanner.js' ;
1619import { IStorageService , IS_NEW_KEY , StorageScope , StorageTarget } from '../../../../platform/storage/common/storage.js' ;
@@ -26,17 +29,74 @@ import { IExtensionManagementServerService } from '../../../services/extensionMa
2629import { IExtensionManifestPropertiesService } from '../../../services/extensions/common/extensionManifestPropertiesService.js' ;
2730import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js' ;
2831
29- export class InstallFailedRemoteExtensionsContribution implements IWorkbenchContribution {
32+ export class InstallRemoteExtensionsContribution implements IWorkbenchContribution {
3033 constructor (
3134 @IRemoteAgentService private readonly remoteAgentService : IRemoteAgentService ,
3235 @IRemoteExtensionsScannerService private readonly remoteExtensionsScannerService : IRemoteExtensionsScannerService ,
3336 @IExtensionGalleryService private readonly extensionGalleryService : IExtensionGalleryService ,
3437 @IExtensionManagementServerService private readonly extensionManagementServerService : IExtensionManagementServerService ,
35- @ILogService private readonly logService : ILogService
38+ @ILogService private readonly logService : ILogService ,
39+ @IConfigurationService private readonly configurationService : IConfigurationService ,
40+ @IProductService private readonly productService : IProductService ,
3641 ) {
42+ this . installDefaultRemoteExtensions ( ) ;
3743 this . installFailedRemoteExtensions ( ) ;
3844 }
3945
46+ private async installDefaultRemoteExtensions ( ) : Promise < void > {
47+ if ( ! this . remoteAgentService . getConnection ( ) ) {
48+ return ;
49+ }
50+
51+ if ( ! this . extensionManagementServerService . remoteExtensionManagementServer ) {
52+ this . logService . error ( 'No remote extension management server available' ) ;
53+ return ;
54+ }
55+
56+ const settingValue = this . configurationService . getValue < string [ ] > ( REMOTE_DEFAULT_EXTENSIONS ) ;
57+ if ( ! settingValue ?. length ) {
58+ return ;
59+ }
60+
61+ this . logService . info ( `Installing '${ settingValue . length } ' default remote extensions` ) ;
62+
63+ const preferPrerelease = this . productService . quality !== 'stable' ;
64+ const galleryExtensions = await this . extensionGalleryService . getExtensions ( settingValue . map ( ( id ) => ( { id } ) ) , CancellationToken . None ) ;
65+ const alreadyInstalledExtensions = await this . extensionManagementServerService . remoteExtensionManagementServer . extensionManagementService . getInstalled ( ) ;
66+
67+ const prereleaseExtensionInfo : InstallExtensionInfo [ ] = [ ] ;
68+ const extensionInfo : InstallExtensionInfo [ ] = [ ] ;
69+ for ( const id of settingValue ) {
70+ const alreadyInstalled = alreadyInstalledExtensions . some ( e => areSameExtensions ( e . identifier , { id } ) ) ;
71+ if ( alreadyInstalled ) {
72+ this . logService . trace ( `Default remote extension '${ id } ' is already installed` ) ;
73+ continue ;
74+ }
75+
76+ const extension = galleryExtensions . find ( e => areSameExtensions ( e . identifier , { id } ) ) ;
77+ if ( ! extension ) {
78+ this . logService . warn ( `Default remote extension '${ id } ' is not found` ) ;
79+ continue ;
80+ }
81+
82+ const installPreReleaseVersion = preferPrerelease && extension . hasPreReleaseVersion ;
83+ ( installPreReleaseVersion ? prereleaseExtensionInfo : extensionInfo ) . push ( {
84+ extension, options : { installPreReleaseVersion } ,
85+ } ) ;
86+ }
87+
88+ // Install pre-release extensions first to avoid a situation where:
89+ // An extension without a pre-release (A) is installed first and depends on an extension that has a pre-release version (B)
90+ // If this happens, the extension A may result in the installation of the stable version of B
91+ // A real life example of this is GitHub.copilot and GitHub.copilot-chat
92+ if ( prereleaseExtensionInfo . length ) {
93+ await Promise . allSettled ( prereleaseExtensionInfo . map ( e => this . extensionManagementServerService . remoteExtensionManagementServer ! . extensionManagementService . installFromGallery ( e . extension , e . options ) ) ) ;
94+ }
95+ if ( extensionInfo . length ) {
96+ await Promise . allSettled ( extensionInfo . map ( e => this . extensionManagementServerService . remoteExtensionManagementServer ! . extensionManagementService . installFromGallery ( e . extension , e . options ) ) ) ;
97+ }
98+ }
99+
40100 private async installFailedRemoteExtensions ( ) : Promise < void > {
41101 if ( ! this . remoteAgentService . getConnection ( ) ) {
42102 return ;
0 commit comments