Path: blob/main/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event, EventMultiplexer } from '../../../../base/common/event.js';6import {7ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SOURCE_CONTEXT, InstallExtensionInfo,8IProductVersion,9ExtensionInstallSource,10DidUpdateExtensionMetadata,11UninstallExtensionInfo,12IAllowedExtensionsService,13EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT,14} from '../../../../platform/extensionManagement/common/extensionManagement.js';15import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IPublisherInfo, IResourceExtension, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from './extensionManagement.js';16import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';17import { URI } from '../../../../base/common/uri.js';18import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';19import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';20import { CancellationToken } from '../../../../base/common/cancellation.js';21import { areSameExtensions, computeTargetPlatform } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';22import { localize } from '../../../../nls.js';23import { IProductService } from '../../../../platform/product/common/productService.js';24import { Schemas } from '../../../../base/common/network.js';25import { IDownloadService } from '../../../../platform/download/common/download.js';26import { coalesce, distinct, isNonEmptyArray } from '../../../../base/common/arrays.js';27import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js';28import Severity from '../../../../base/common/severity.js';29import { IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js';30import { Promises } from '../../../../base/common/async.js';31import { IWorkspaceTrustRequestService, WorkspaceTrustRequestButton } from '../../../../platform/workspace/common/workspaceTrust.js';32import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';33import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';34import { ICommandService } from '../../../../platform/commands/common/commands.js';35import { isString, isUndefined } from '../../../../base/common/types.js';36import { FileChangesEvent, IFileService } from '../../../../platform/files/common/files.js';37import { ILogService } from '../../../../platform/log/common/log.js';38import { CancellationError, getErrorMessage } from '../../../../base/common/errors.js';39import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';40import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';41import { IExtensionsScannerService, IScannedExtension } from '../../../../platform/extensionManagement/common/extensionsScannerService.js';42import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';43import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';44import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';45import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';46import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';47import { verifiedPublisherIcon } from './extensionsIcons.js';48import { Codicon } from '../../../../base/common/codicons.js';49import { IStringDictionary } from '../../../../base/common/collections.js';50import { CommontExtensionManagementService } from '../../../../platform/extensionManagement/common/abstractExtensionManagementService.js';5152const TrustedPublishersStorageKey = 'extensions.trustedPublishers';5354function isGalleryExtension(extension: IResourceExtension | IGalleryExtension): extension is IGalleryExtension {55return extension.type === 'gallery';56}5758export class ExtensionManagementService extends CommontExtensionManagementService implements IWorkbenchExtensionManagementService {5960declare readonly _serviceBrand: undefined;6162private readonly defaultTrustedPublishers: readonly string[];6364private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionOnServerEvent>());65readonly onInstallExtension: Event<InstallExtensionOnServerEvent>;6667private readonly _onDidInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());68readonly onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;6970private readonly _onUninstallExtension = this._register(new Emitter<UninstallExtensionOnServerEvent>());71readonly onUninstallExtension: Event<UninstallExtensionOnServerEvent>;7273private readonly _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionOnServerEvent>());74readonly onDidUninstallExtension: Event<DidUninstallExtensionOnServerEvent>;7576readonly onDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;7778private readonly _onDidProfileAwareInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());79readonly onProfileAwareDidInstallExtensions: Event<readonly InstallExtensionResult[]>;8081private readonly _onDidProfileAwareUninstallExtension = this._register(new Emitter<DidUninstallExtensionOnServerEvent>());82readonly onProfileAwareDidUninstallExtension: Event<DidUninstallExtensionOnServerEvent>;8384readonly onProfileAwareDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;8586readonly onDidChangeProfile: Event<DidChangeProfileForServerEvent>;8788readonly onDidEnableExtensions: Event<ILocalExtension[]>;8990protected readonly servers: IExtensionManagementServer[] = [];9192private readonly workspaceExtensionManagementService: WorkspaceExtensionsManagementService;9394constructor(95@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,96@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,97@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,98@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,99@IConfigurationService protected readonly configurationService: IConfigurationService,100@IProductService productService: IProductService,101@IDownloadService protected readonly downloadService: IDownloadService,102@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,103@IDialogService private readonly dialogService: IDialogService,104@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,105@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,106@IFileService private readonly fileService: IFileService,107@ILogService private readonly logService: ILogService,108@IInstantiationService private readonly instantiationService: IInstantiationService,109@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,110@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,111@IStorageService private readonly storageService: IStorageService,112@ITelemetryService private readonly telemetryService: ITelemetryService,113) {114super(productService, allowedExtensionsService);115116this.defaultTrustedPublishers = productService.trustedExtensionPublishers ?? [];117this.workspaceExtensionManagementService = this._register(this.instantiationService.createInstance(WorkspaceExtensionsManagementService));118this.onDidEnableExtensions = this.workspaceExtensionManagementService.onDidChangeInvalidExtensions;119120if (this.extensionManagementServerService.localExtensionManagementServer) {121this.servers.push(this.extensionManagementServerService.localExtensionManagementServer);122}123if (this.extensionManagementServerService.remoteExtensionManagementServer) {124this.servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);125}126if (this.extensionManagementServerService.webExtensionManagementServer) {127this.servers.push(this.extensionManagementServerService.webExtensionManagementServer);128}129130const onInstallExtensionEventMultiplexer = this._register(new EventMultiplexer<InstallExtensionOnServerEvent>());131this._register(onInstallExtensionEventMultiplexer.add(this._onInstallExtension.event));132this.onInstallExtension = onInstallExtensionEventMultiplexer.event;133134const onDidInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer<readonly InstallExtensionResult[]>());135this._register(onDidInstallExtensionsEventMultiplexer.add(this._onDidInstallExtensions.event));136this.onDidInstallExtensions = onDidInstallExtensionsEventMultiplexer.event;137138const onDidProfileAwareInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer<readonly InstallExtensionResult[]>());139this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(this._onDidProfileAwareInstallExtensions.event));140this.onProfileAwareDidInstallExtensions = onDidProfileAwareInstallExtensionsEventMultiplexer.event;141142const onUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<UninstallExtensionOnServerEvent>());143this._register(onUninstallExtensionEventMultiplexer.add(this._onUninstallExtension.event));144this.onUninstallExtension = onUninstallExtensionEventMultiplexer.event;145146const onDidUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<DidUninstallExtensionOnServerEvent>());147this._register(onDidUninstallExtensionEventMultiplexer.add(this._onDidUninstallExtension.event));148this.onDidUninstallExtension = onDidUninstallExtensionEventMultiplexer.event;149150const onDidProfileAwareUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<DidUninstallExtensionOnServerEvent>());151this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(this._onDidProfileAwareUninstallExtension.event));152this.onProfileAwareDidUninstallExtension = onDidProfileAwareUninstallExtensionEventMultiplexer.event;153154const onDidUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer<DidUpdateExtensionMetadata>());155this.onDidUpdateExtensionMetadata = onDidUpdateExtensionMetadaEventMultiplexer.event;156157const onDidProfileAwareUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer<DidUpdateExtensionMetadata>());158this.onProfileAwareDidUpdateExtensionMetadata = onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.event;159160const onDidChangeProfileEventMultiplexer = this._register(new EventMultiplexer<DidChangeProfileForServerEvent>());161this.onDidChangeProfile = onDidChangeProfileEventMultiplexer.event;162163for (const server of this.servers) {164this._register(onInstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onInstallExtension, e => ({ ...e, server }))));165this._register(onDidInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onDidInstallExtensions));166this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidInstallExtensions));167this._register(onUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server }))));168this._register(onDidUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server }))));169this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onProfileAwareDidUninstallExtension, e => ({ ...e, server }))));170this._register(onDidUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onDidUpdateExtensionMetadata));171this._register(onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidUpdateExtensionMetadata));172this._register(onDidChangeProfileEventMultiplexer.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server }))));173}174175this._register(this.onProfileAwareDidInstallExtensions(results => {176const untrustedPublishers = new Map<string, IPublisherInfo>();177for (const result of results) {178if (result.local && result.source && !URI.isUri(result.source) && !this.isPublisherTrusted(result.source)) {179untrustedPublishers.set(result.source.publisher, { publisher: result.source.publisher, publisherDisplayName: result.source.publisherDisplayName });180}181}182if (untrustedPublishers.size) {183this.trustPublishers(...untrustedPublishers.values());184}185}));186}187188async getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {189const result: ILocalExtension[] = [];190await Promise.all(this.servers.map(async server => {191const installed = await server.extensionManagementService.getInstalled(type, profileLocation, productVersion);192if (server === this.getWorkspaceExtensionsServer()) {193const workspaceExtensions = await this.getInstalledWorkspaceExtensions(true);194installed.push(...workspaceExtensions);195}196result.push(...installed);197}));198return result;199}200201uninstall(extension: ILocalExtension, options: UninstallOptions): Promise<void> {202return this.uninstallExtensions([{ extension, options }]);203}204205async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise<void> {206const workspaceExtensions: ILocalExtension[] = [];207const groupedExtensions = new Map<IExtensionManagementServer, UninstallExtensionInfo[]>();208209const addExtensionToServer = (server: IExtensionManagementServer, extension: ILocalExtension, options?: UninstallOptions) => {210let extensions = groupedExtensions.get(server);211if (!extensions) {212groupedExtensions.set(server, extensions = []);213}214extensions.push({ extension, options });215};216217for (const { extension, options } of extensions) {218if (extension.isWorkspaceScoped) {219workspaceExtensions.push(extension);220continue;221}222223const server = this.getServer(extension);224if (!server) {225throw new Error(`Invalid location ${extension.location.toString()}`);226}227addExtensionToServer(server, extension, options);228if (this.servers.length > 1 && isLanguagePackExtension(extension.manifest)) {229const otherServers: IExtensionManagementServer[] = this.servers.filter(s => s !== server);230for (const otherServer of otherServers) {231const installed = await otherServer.extensionManagementService.getInstalled();232const extensionInOtherServer = installed.find(i => !i.isBuiltin && areSameExtensions(i.identifier, extension.identifier));233if (extensionInOtherServer) {234addExtensionToServer(otherServer, extensionInOtherServer, options);235}236}237}238}239240const promises: Promise<void>[] = [];241for (const workspaceExtension of workspaceExtensions) {242promises.push(this.uninstallExtensionFromWorkspace(workspaceExtension));243}244for (const [server, extensions] of groupedExtensions.entries()) {245promises.push(this.uninstallInServer(server, extensions));246}247248const result = await Promise.allSettled(promises);249const errors = result.filter(r => r.status === 'rejected').map(r => r.reason);250if (errors.length) {251throw new Error(errors.map(e => e.message).join('\n'));252}253}254255private async uninstallInServer(server: IExtensionManagementServer, extensions: UninstallExtensionInfo[]): Promise<void> {256if (server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {257for (const { extension } of extensions) {258const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getInstalled(ExtensionType.User);259const dependentNonUIExtensions = installedExtensions.filter(i => !this.extensionManifestPropertiesService.prefersExecuteOnUI(i.manifest)260&& i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));261if (dependentNonUIExtensions.length) {262throw (new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));263}264}265}266return server.extensionManagementService.uninstallExtensions(extensions);267}268269private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {270if (dependents.length === 1) {271return localize('singleDependentError', "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.",272extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);273}274if (dependents.length === 2) {275return localize('twoDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.",276extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);277}278return localize('multipleDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",279extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);280281}282283updateMetadata(extension: ILocalExtension, metadata: Partial<Metadata>): Promise<ILocalExtension> {284const server = this.getServer(extension);285if (server) {286const profile = extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile : this.userDataProfileService.currentProfile;287return server.extensionManagementService.updateMetadata(extension, metadata, profile.extensionsResource);288}289return Promise.reject(`Invalid location ${extension.location.toString()}`);290}291292async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void> {293await Promise.allSettled(this.servers.map(server => server.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned)));294}295296zip(extension: ILocalExtension): Promise<URI> {297const server = this.getServer(extension);298if (server) {299return server.extensionManagementService.zip(extension);300}301return Promise.reject(`Invalid location ${extension.location.toString()}`);302}303304download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI> {305if (this.extensionManagementServerService.localExtensionManagementServer) {306return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.download(extension, operation, donotVerifySignature);307}308throw new Error('Cannot download extension');309}310311async install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension> {312const manifest = await this.getManifest(vsix);313return this.installVSIX(vsix, manifest, options);314}315316async installVSIX(vsix: URI, manifest: IExtensionManifest, options?: InstallOptions): Promise<ILocalExtension> {317const serversToInstall = this.getServersToInstall(manifest);318if (serversToInstall?.length) {319await this.checkForWorkspaceTrust(manifest, false);320const [local] = await Promises.settled(serversToInstall.map(server => this.installVSIXInServer(vsix, server, options)));321return local;322}323return Promise.reject('No Servers to Install');324}325326private getServersToInstall(manifest: IExtensionManifest): IExtensionManagementServer[] | undefined {327if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {328if (isLanguagePackExtension(manifest)) {329// Install on both servers330return [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer];331}332if (this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest)) {333// Install only on local server334return [this.extensionManagementServerService.localExtensionManagementServer];335}336// Install only on remote server337return [this.extensionManagementServerService.remoteExtensionManagementServer];338}339if (this.extensionManagementServerService.localExtensionManagementServer) {340return [this.extensionManagementServerService.localExtensionManagementServer];341}342if (this.extensionManagementServerService.remoteExtensionManagementServer) {343return [this.extensionManagementServerService.remoteExtensionManagementServer];344}345return undefined;346}347348async installFromLocation(location: URI): Promise<ILocalExtension> {349if (location.scheme === Schemas.file) {350if (this.extensionManagementServerService.localExtensionManagementServer) {351return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);352}353throw new Error('Local extension management server is not found');354}355if (location.scheme === Schemas.vscodeRemote) {356if (this.extensionManagementServerService.remoteExtensionManagementServer) {357return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);358}359throw new Error('Remote extension management server is not found');360}361if (!this.extensionManagementServerService.webExtensionManagementServer) {362throw new Error('Web extension management server is not found');363}364return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);365}366367protected installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallOptions | undefined): Promise<ILocalExtension> {368return server.extensionManagementService.install(vsix, options);369}370371getManifest(vsix: URI): Promise<IExtensionManifest> {372if (vsix.scheme === Schemas.file && this.extensionManagementServerService.localExtensionManagementServer) {373return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getManifest(vsix);374}375if (vsix.scheme === Schemas.file && this.extensionManagementServerService.remoteExtensionManagementServer) {376return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix);377}378if (vsix.scheme === Schemas.vscodeRemote && this.extensionManagementServerService.remoteExtensionManagementServer) {379return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix);380}381return Promise.reject('No Servers');382}383384override async canInstall(extension: IGalleryExtension | IResourceExtension): Promise<true | IMarkdownString> {385if (isGalleryExtension(extension)) {386return this.canInstallGalleryExtension(extension);387}388return this.canInstallResourceExtension(extension);389}390391private async canInstallGalleryExtension(gallery: IGalleryExtension): Promise<true | IMarkdownString> {392if (this.extensionManagementServerService.localExtensionManagementServer393&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) {394return true;395}396const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);397if (!manifest) {398return new MarkdownString().appendText(localize('manifest is not found', "Manifest is not found"));399}400if (this.extensionManagementServerService.remoteExtensionManagementServer401&& await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.canInstall(gallery) === true402&& this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) {403return true;404}405if (this.extensionManagementServerService.webExtensionManagementServer406&& await this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.canInstall(gallery) === true407&& this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) {408return true;409}410return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name));411}412413private async canInstallResourceExtension(extension: IResourceExtension): Promise<true | IMarkdownString> {414if (this.extensionManagementServerService.localExtensionManagementServer) {415return true;416}417if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(extension.manifest)) {418return true;419}420if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) {421return true;422}423return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", extension.manifest.displayName ?? extension.identifier.id));424}425426async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {427const server = this.getServer(extension);428if (!server) {429return Promise.reject(`Invalid location ${extension.location.toString()}`);430}431432const servers: IExtensionManagementServer[] = [];433434// Update Language pack on local and remote servers435if (isLanguagePackExtension(extension.manifest)) {436servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer));437} else {438servers.push(server);439}440441installOptions = { ...(installOptions || {}), isApplicationScoped: extension.isApplicationScoped };442return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);443}444445async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {446const results = new Map<string, InstallExtensionResult>();447448const extensionsByServer = new Map<IExtensionManagementServer, InstallExtensionInfo[]>();449const manifests = await Promise.all(extensions.map(async ({ extension }) => {450const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);451if (!manifest) {452throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));453}454return manifest;455}));456457if (extensions.some(e => e.options?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true)) {458await this.checkForTrustedPublishers(extensions.map((e, index) => ({ extension: e.extension, manifest: manifests[index], checkForPackAndDependencies: !e.options?.donotIncludePackAndDependencies })));459}460461await Promise.all(extensions.map(async ({ extension, options }) => {462try {463const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);464if (!manifest) {465throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));466}467468if (options?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {469await this.checkForWorkspaceTrust(manifest, false);470471if (!options?.donotIncludePackAndDependencies) {472await this.checkInstallingExtensionOnWeb(extension, manifest);473}474}475476const servers = await this.getExtensionManagementServersToInstall(extension, manifest);477if (!options.isMachineScoped && this.isExtensionsSyncEnabled()) {478if (this.extensionManagementServerService.localExtensionManagementServer479&& !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)480&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(extension) === true) {481servers.push(this.extensionManagementServerService.localExtensionManagementServer);482}483}484for (const server of servers) {485let exensions = extensionsByServer.get(server);486if (!exensions) {487extensionsByServer.set(server, exensions = []);488}489exensions.push({ extension, options });490}491} catch (error) {492results.set(extension.identifier.id.toLowerCase(), {493identifier: extension.identifier,494source: extension, error,495operation: InstallOperation.Install,496profileLocation: options.profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource497});498}499}));500501await Promise.all([...extensionsByServer.entries()].map(async ([server, extensions]) => {502const serverResults = await server.extensionManagementService.installGalleryExtensions(extensions);503for (const result of serverResults) {504results.set(result.identifier.id.toLowerCase(), result);505}506}));507508return [...results.values()];509}510511async installFromGallery(gallery: IGalleryExtension, installOptions?: InstallOptions, servers?: IExtensionManagementServer[]): Promise<ILocalExtension> {512const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);513if (!manifest) {514throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));515}516517if (installOptions?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true) {518await this.checkForTrustedPublishers([{ extension: gallery, manifest, checkForPackAndDependencies: !installOptions?.donotIncludePackAndDependencies }],);519}520521if (installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {522523await this.checkForWorkspaceTrust(manifest, false);524525if (!installOptions?.donotIncludePackAndDependencies) {526await this.checkInstallingExtensionOnWeb(gallery, manifest);527}528}529530servers = servers?.length ? this.validServers(gallery, manifest, servers) : await this.getExtensionManagementServersToInstall(gallery, manifest);531if (!installOptions || isUndefined(installOptions.isMachineScoped)) {532const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);533installOptions = { ...(installOptions || {}), isMachineScoped };534}535536if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {537if (this.extensionManagementServerService.localExtensionManagementServer538&& !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)539&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) {540servers.push(this.extensionManagementServerService.localExtensionManagementServer);541}542}543544return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);545}546547async getExtensions(locations: URI[]): Promise<IResourceExtension[]> {548const scannedExtensions = await this.extensionsScannerService.scanMultipleExtensions(locations, ExtensionType.User, { includeInvalid: true });549const result: IResourceExtension[] = [];550await Promise.all(scannedExtensions.map(async scannedExtension => {551const workspaceExtension = await this.workspaceExtensionManagementService.toLocalWorkspaceExtension(scannedExtension);552if (workspaceExtension) {553result.push({554type: 'resource',555identifier: workspaceExtension.identifier,556location: workspaceExtension.location,557manifest: workspaceExtension.manifest,558changelogUri: workspaceExtension.changelogUrl,559readmeUri: workspaceExtension.readmeUrl,560});561}562}));563return result;564}565566getInstalledWorkspaceExtensionLocations(): URI[] {567return this.workspaceExtensionManagementService.getInstalledWorkspaceExtensionsLocations();568}569570async getInstalledWorkspaceExtensions(includeInvalid: boolean): Promise<ILocalExtension[]> {571return this.workspaceExtensionManagementService.getInstalled(includeInvalid);572}573574async installResourceExtension(extension: IResourceExtension, installOptions: InstallOptions): Promise<ILocalExtension> {575if (!this.canInstallResourceExtension(extension)) {576throw new Error('This extension cannot be installed in the current workspace.');577}578if (!installOptions.isWorkspaceScoped) {579return this.installFromLocation(extension.location);580}581582this.logService.info(`Installing the extension ${extension.identifier.id} from ${extension.location.toString()} in workspace`);583const server = this.getWorkspaceExtensionsServer();584this._onInstallExtension.fire({585identifier: extension.identifier,586source: extension.location,587server,588applicationScoped: false,589profileLocation: this.userDataProfileService.currentProfile.extensionsResource,590workspaceScoped: true591});592593try {594await this.checkForWorkspaceTrust(extension.manifest, true);595596const workspaceExtension = await this.workspaceExtensionManagementService.install(extension);597598this.logService.info(`Successfully installed the extension ${workspaceExtension.identifier.id} from ${extension.location.toString()} in the workspace`);599this._onDidInstallExtensions.fire([{600identifier: workspaceExtension.identifier,601source: extension.location,602operation: InstallOperation.Install,603applicationScoped: false,604profileLocation: this.userDataProfileService.currentProfile.extensionsResource,605local: workspaceExtension,606workspaceScoped: true607}]);608return workspaceExtension;609} catch (error) {610this.logService.error(`Failed to install the extension ${extension.identifier.id} from ${extension.location.toString()} in the workspace`, getErrorMessage(error));611this._onDidInstallExtensions.fire([{612identifier: extension.identifier,613source: extension.location,614operation: InstallOperation.Install,615applicationScoped: false,616profileLocation: this.userDataProfileService.currentProfile.extensionsResource,617error,618workspaceScoped: true619}]);620throw error;621}622}623624async getInstallableServers(gallery: IGalleryExtension): Promise<IExtensionManagementServer[]> {625const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);626if (!manifest) {627return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));628}629return this.getInstallableExtensionManagementServers(manifest);630}631632private async uninstallExtensionFromWorkspace(extension: ILocalExtension): Promise<void> {633if (!extension.isWorkspaceScoped) {634throw new Error('The extension is not a workspace extension');635}636637this.logService.info(`Uninstalling the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`);638const server = this.getWorkspaceExtensionsServer();639this._onUninstallExtension.fire({640identifier: extension.identifier,641server,642applicationScoped: false,643workspaceScoped: true,644profileLocation: this.userDataProfileService.currentProfile.extensionsResource645});646647try {648await this.workspaceExtensionManagementService.uninstall(extension);649this.logService.info(`Successfully uninstalled the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`);650this.telemetryService.publicLog2<{}, {651owner: 'sandy081';652comment: 'Uninstall workspace extension';653}>('workspaceextension:uninstall');654this._onDidUninstallExtension.fire({655identifier: extension.identifier,656server,657applicationScoped: false,658workspaceScoped: true,659profileLocation: this.userDataProfileService.currentProfile.extensionsResource660});661} catch (error) {662this.logService.error(`Failed to uninstall the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, getErrorMessage(error));663this._onDidUninstallExtension.fire({664identifier: extension.identifier,665server,666error,667applicationScoped: false,668workspaceScoped: true,669profileLocation: this.userDataProfileService.currentProfile.extensionsResource670});671throw error;672}673}674675private validServers(gallery: IGalleryExtension, manifest: IExtensionManifest, servers: IExtensionManagementServer[]): IExtensionManagementServer[] {676const installableServers = this.getInstallableExtensionManagementServers(manifest);677for (const server of servers) {678if (!installableServers.includes(server)) {679const error = new Error(localize('cannot be installed in server', "Cannot install the '{0}' extension because it is not available in the '{1}' setup.", gallery.displayName || gallery.name, server.label));680error.name = ExtensionManagementErrorCode.Unsupported;681throw error;682}683}684return servers;685}686687private async getExtensionManagementServersToInstall(gallery: IGalleryExtension, manifest: IExtensionManifest): Promise<IExtensionManagementServer[]> {688const servers: IExtensionManagementServer[] = [];689690// Language packs should be installed on both local and remote servers691if (isLanguagePackExtension(manifest)) {692servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer));693}694695else {696const [server] = this.getInstallableExtensionManagementServers(manifest);697if (server) {698servers.push(server);699}700}701702if (!servers.length) {703const error = new Error(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name));704error.name = ExtensionManagementErrorCode.Unsupported;705throw error;706}707708return servers;709}710711private getInstallableExtensionManagementServers(manifest: IExtensionManifest): IExtensionManagementServer[] {712// Only local server713if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) {714return [this.extensionManagementServerService.localExtensionManagementServer];715}716717const servers: IExtensionManagementServer[] = [];718719const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest);720for (const kind of extensionKind) {721if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) {722servers.push(this.extensionManagementServerService.localExtensionManagementServer);723}724if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) {725servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);726}727if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) {728servers.push(this.extensionManagementServerService.webExtensionManagementServer);729}730}731732// Local server can accept any extension.733if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) {734servers.push(this.extensionManagementServerService.localExtensionManagementServer);735}736737return servers;738}739740private isExtensionsSyncEnabled(): boolean {741return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);742}743744private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise<boolean> {745if (this.isExtensionsSyncEnabled()) {746const { result } = await this.dialogService.prompt<boolean>({747type: Severity.Info,748message: extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),749detail: extensions.length === 1750? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)751: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?"),752buttons: [753{754label: localize({ key: 'install', comment: ['&& denotes a mnemonic'] }, "&&Install"),755run: () => false756},757{758label: localize({ key: 'install and do no sync', comment: ['&& denotes a mnemonic'] }, "Install (Do &¬ sync)"),759run: () => true760}761],762cancelButton: {763run: () => {764throw new CancellationError();765}766}767});768769return result;770}771return false;772}773774getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {775if (this.extensionManagementServerService.localExtensionManagementServer) {776return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();777}778if (this.extensionManagementServerService.remoteExtensionManagementServer) {779return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();780}781if (this.extensionManagementServerService.webExtensionManagementServer) {782return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();783}784return this.extensionGalleryService.getExtensionsControlManifest();785}786787private getServer(extension: ILocalExtension): IExtensionManagementServer | null {788if (extension.isWorkspaceScoped) {789return this.getWorkspaceExtensionsServer();790}791return this.extensionManagementServerService.getExtensionManagementServer(extension);792}793794private getWorkspaceExtensionsServer(): IExtensionManagementServer {795if (this.extensionManagementServerService.remoteExtensionManagementServer) {796return this.extensionManagementServerService.remoteExtensionManagementServer;797}798if (this.extensionManagementServerService.localExtensionManagementServer) {799return this.extensionManagementServerService.localExtensionManagementServer;800}801if (this.extensionManagementServerService.webExtensionManagementServer) {802return this.extensionManagementServerService.webExtensionManagementServer;803}804throw new Error('No extension server found');805}806807async requestPublisherTrust(extensions: InstallExtensionInfo[]): Promise<void> {808const manifests = await Promise.all(extensions.map(async ({ extension }) => {809const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);810if (!manifest) {811throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));812}813return manifest;814}));815816await this.checkForTrustedPublishers(extensions.map((e, index) => ({ extension: e.extension, manifest: manifests[index], checkForPackAndDependencies: !e.options?.donotIncludePackAndDependencies })));817}818819private async checkForTrustedPublishers(extensions: { extension: IGalleryExtension; manifest: IExtensionManifest; checkForPackAndDependencies: boolean }[]): Promise<void> {820const untrustedExtensions: IGalleryExtension[] = [];821const untrustedExtensionManifests: IExtensionManifest[] = [];822const manifestsToGetOtherUntrustedPublishers: IExtensionManifest[] = [];823for (const { extension, manifest, checkForPackAndDependencies } of extensions) {824if (!extension.private && !this.isPublisherTrusted(extension)) {825untrustedExtensions.push(extension);826untrustedExtensionManifests.push(manifest);827if (checkForPackAndDependencies) {828manifestsToGetOtherUntrustedPublishers.push(manifest);829}830}831}832833if (!untrustedExtensions.length) {834return;835}836837const otherUntrustedPublishers = manifestsToGetOtherUntrustedPublishers.length ? await this.getOtherUntrustedPublishers(manifestsToGetOtherUntrustedPublishers) : [];838const allPublishers = [...distinct(untrustedExtensions, e => e.publisher), ...otherUntrustedPublishers];839const unverfiiedPublishers = allPublishers.filter(p => !p.publisherDomain?.verified);840const verifiedPublishers = allPublishers.filter(p => p.publisherDomain?.verified);841842type TrustPublisherClassification = {843owner: 'sandy081';844comment: 'Report the action taken by the user on the publisher trust dialog';845action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action taken by the user on the publisher trust dialog. Can be trust, learn more or cancel.' };846extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifiers of the extension for which the publisher trust dialog was shown.' };847};848type TrustPublisherEvent = {849action: string;850extensionId: string;851};852853const installButton: IPromptButton<void> = {854label: allPublishers.length > 1 ? localize({ key: 'trust publishers and install', comment: ['&& denotes a mnemonic'] }, "Trust Publishers & &&Install") : localize({ key: 'trust and install', comment: ['&& denotes a mnemonic'] }, "Trust Publisher & &&Install"),855run: () => {856this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'trust', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });857this.trustPublishers(...allPublishers.map(p => ({ publisher: p.publisher, publisherDisplayName: p.publisherDisplayName })));858}859};860861const learnMoreButton: IPromptButton<void> = {862label: localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More"),863run: () => {864this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'learn', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });865this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('vscode.open', URI.parse('https://aka.ms/vscode-extension-security')));866throw new CancellationError();867}868};869870const getPublisherLink = ({ publisherDisplayName, publisherLink }: { publisherDisplayName: string; publisherLink?: string }) => {871return publisherLink ? `[${publisherDisplayName}](${publisherLink})` : publisherDisplayName;872};873874const unverifiedLink = 'https://aka.ms/vscode-verify-publisher';875876const title = allPublishers.length === 1877? localize('checkTrustedPublisherTitle', "Do you trust the publisher \"{0}\"?", allPublishers[0].publisherDisplayName)878: allPublishers.length === 2879? localize('checkTwoTrustedPublishersTitle', "Do you trust publishers \"{0}\" and \"{1}\"?", allPublishers[0].publisherDisplayName, allPublishers[1].publisherDisplayName)880: localize('checkAllTrustedPublishersTitle', "Do you trust the publisher \"{0}\" and {1} others?", allPublishers[0].publisherDisplayName, allPublishers.length - 1);881882const customMessage = new MarkdownString('', { supportThemeIcons: true, isTrusted: true });883884if (untrustedExtensions.length === 1) {885const extension = untrustedExtensions[0];886const manifest = untrustedExtensionManifests[0];887if (otherUntrustedPublishers.length) {888customMessage.appendMarkdown(localize('extension published by message', "The extension {0} is published by {1}.", `[${extension.displayName}](${extension.detailsLink})`, getPublisherLink(extension)));889customMessage.appendMarkdown(' ');890const commandUri = URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([extension.identifier.id, manifest.extensionPack?.length ? 'extensionPack' : 'dependencies']))}`).toString();891if (otherUntrustedPublishers.length === 1) {892customMessage.appendMarkdown(localize('singleUntrustedPublisher', "Installing this extension will also install [extensions]({0}) published by {1}.", commandUri, getPublisherLink(otherUntrustedPublishers[0])));893} else {894customMessage.appendMarkdown(localize('message3', "Installing this extension will also install [extensions]({0}) published by {1} and {2}.", commandUri, otherUntrustedPublishers.slice(0, otherUntrustedPublishers.length - 1).map(p => getPublisherLink(p)).join(', '), getPublisherLink(otherUntrustedPublishers[otherUntrustedPublishers.length - 1])));895}896customMessage.appendMarkdown(' ');897customMessage.appendMarkdown(localize('firstTimeInstallingMessage', "This is the first time you're installing extensions from these publishers."));898} else {899customMessage.appendMarkdown(localize('message1', "The extension {0} is published by {1}. This is the first extension you're installing from this publisher.", `[${extension.displayName}](${extension.detailsLink})`, getPublisherLink(extension)));900}901} else {902customMessage.appendMarkdown(localize('multiInstallMessage', "This is the first time you're installing extensions from publishers {0} and {1}.", getPublisherLink(allPublishers[0]), getPublisherLink(allPublishers[allPublishers.length - 1])));903}904905if (verifiedPublishers.length || unverfiiedPublishers.length === 1) {906for (const publisher of verifiedPublishers) {907customMessage.appendText('\n');908const publisherVerifiedMessage = localize('verifiedPublisherWithName', "{0} has verified ownership of {1}.", getPublisherLink(publisher), `[$(link-external) ${URI.parse(publisher.publisherDomain!.link).authority}](${publisher.publisherDomain!.link})`);909customMessage.appendMarkdown(`$(${verifiedPublisherIcon.id}) ${publisherVerifiedMessage}`);910}911if (unverfiiedPublishers.length) {912customMessage.appendText('\n');913if (unverfiiedPublishers.length === 1) {914customMessage.appendMarkdown(`$(${Codicon.unverified.id}) ${localize('unverifiedPublisherWithName', "{0} is [**not** verified]({1}).", getPublisherLink(unverfiiedPublishers[0]), unverifiedLink)}`);915} else {916customMessage.appendMarkdown(`$(${Codicon.unverified.id}) ${localize('unverifiedPublishers', "{0} and {1} are [**not** verified]({2}).", unverfiiedPublishers.slice(0, unverfiiedPublishers.length - 1).map(p => getPublisherLink(p)).join(', '), getPublisherLink(unverfiiedPublishers[unverfiiedPublishers.length - 1]), unverifiedLink)}`);917}918}919} else {920customMessage.appendText('\n');921customMessage.appendMarkdown(`$(${Codicon.unverified.id}) ${localize('allUnverifed', "All publishers are [**not** verified]({0}).", unverifiedLink)}`);922}923924customMessage.appendText('\n');925if (allPublishers.length > 1) {926customMessage.appendMarkdown(localize('message4', "{0} has no control over the behavior of third-party extensions, including how they manage your personal data. Proceed only if you trust the publishers.", this.productService.nameLong));927} else {928customMessage.appendMarkdown(localize('message2', "{0} has no control over the behavior of third-party extensions, including how they manage your personal data. Proceed only if you trust the publisher.", this.productService.nameLong));929}930931await this.dialogService.prompt({932message: title,933type: Severity.Warning,934buttons: [installButton, learnMoreButton],935cancelButton: {936run: () => {937this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'cancel', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });938throw new CancellationError();939}940},941custom: {942markdownDetails: [{ markdown: customMessage, classes: ['extensions-management-publisher-trust-dialog'] }],943}944});945946}947948private async getOtherUntrustedPublishers(manifests: IExtensionManifest[]): Promise<{ publisher: string; publisherDisplayName: string; publisherLink?: string; publisherDomain?: { link: string; verified: boolean } }[]> {949const extensionIds = new Set<string>();950for (const manifest of manifests) {951for (const id of [...(manifest.extensionPack ?? []), ...(manifest.extensionDependencies ?? [])]) {952const [publisherId] = id.split('.');953if (publisherId.toLowerCase() === manifest.publisher.toLowerCase()) {954continue;955}956if (this.isPublisherUserTrusted(publisherId.toLowerCase())) {957continue;958}959extensionIds.add(id.toLowerCase());960}961}962if (!extensionIds.size) {963return [];964}965const extensions = new Map<string, IGalleryExtension>();966await this.getDependenciesAndPackedExtensionsRecursively([...extensionIds], extensions, CancellationToken.None);967const publishers = new Map<string, IGalleryExtension>();968for (const [, extension] of extensions) {969if (extension.private || this.isPublisherTrusted(extension)) {970continue;971}972publishers.set(extension.publisherDisplayName, extension);973}974return [...publishers.values()];975}976977private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {978if (toGet.length === 0) {979return;980}981982const extensions = await this.extensionGalleryService.getExtensions(toGet.map(id => ({ id })), token);983for (let idx = 0; idx < extensions.length; idx++) {984const extension = extensions[idx];985result.set(extension.identifier.id.toLowerCase(), extension);986}987toGet = [];988for (const extension of extensions) {989if (isNonEmptyArray(extension.properties.dependencies)) {990for (const id of extension.properties.dependencies) {991if (!result.has(id.toLowerCase())) {992toGet.push(id);993}994}995}996if (isNonEmptyArray(extension.properties.extensionPack)) {997for (const id of extension.properties.extensionPack) {998if (!result.has(id.toLowerCase())) {999toGet.push(id);1000}1001}1002}1003}1004return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, token);1005}10061007private async checkForWorkspaceTrust(manifest: IExtensionManifest, requireTrust: boolean): Promise<void> {1008if (requireTrust || this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(manifest) === false) {1009const buttons: WorkspaceTrustRequestButton[] = [];1010buttons.push({ label: localize('extensionInstallWorkspaceTrustButton', "Trust Workspace & Install"), type: 'ContinueWithTrust' });1011if (!requireTrust) {1012buttons.push({ label: localize('extensionInstallWorkspaceTrustContinueButton', "Install"), type: 'ContinueWithoutTrust' });1013}1014buttons.push({ label: localize('extensionInstallWorkspaceTrustManageButton', "Learn More"), type: 'Manage' });1015const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust({1016message: localize('extensionInstallWorkspaceTrustMessage', "Enabling this extension requires a trusted workspace."),1017buttons1018});10191020if (trustState === undefined) {1021throw new CancellationError();1022}1023}1024}10251026private async checkInstallingExtensionOnWeb(extension: IGalleryExtension, manifest: IExtensionManifest): Promise<void> {1027if (this.servers.length !== 1 || this.servers[0] !== this.extensionManagementServerService.webExtensionManagementServer) {1028return;1029}10301031const nonWebExtensions = [];1032if (manifest.extensionPack?.length) {1033const extensions = await this.extensionGalleryService.getExtensions(manifest.extensionPack.map(id => ({ id })), CancellationToken.None);1034for (const extension of extensions) {1035if (await this.servers[0].extensionManagementService.canInstall(extension) !== true) {1036nonWebExtensions.push(extension);1037}1038}1039if (nonWebExtensions.length && nonWebExtensions.length === extensions.length) {1040throw new ExtensionManagementError('Not supported in Web', ExtensionManagementErrorCode.Unsupported);1041}1042}10431044const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong);1045const virtualWorkspaceSupport = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(manifest);1046const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage(manifest.capabilities?.virtualWorkspaces);1047const hasLimitedSupport = virtualWorkspaceSupport === 'limited' || !!virtualWorkspaceSupportReason;10481049if (!nonWebExtensions.length && !hasLimitedSupport) {1050return;1051}10521053const limitedSupportMessage = localize('limited support', "'{0}' has limited functionality in {1}.", extension.displayName || extension.identifier.id, productName);1054let message: string;1055let buttons: IPromptButton<void>[] = [];1056let detail: string | undefined;10571058const installAnywayButton: IPromptButton<void> = {1059label: localize({ key: 'install anyways', comment: ['&& denotes a mnemonic'] }, "&&Install Anyway"),1060run: () => { }1061};10621063const showExtensionsButton: IPromptButton<void> = {1064label: localize({ key: 'showExtensions', comment: ['&& denotes a mnemonic'] }, "&&Show Extensions"),1065run: () => this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('extension.open', extension.identifier.id, 'extensionPack'))1066};10671068if (nonWebExtensions.length && hasLimitedSupport) {1069message = limitedSupportMessage;1070detail = `${virtualWorkspaceSupportReason ? `${virtualWorkspaceSupportReason}\n` : ''}${localize('non web extensions detail', "Contains extensions which are not supported.")}`;1071buttons = [1072installAnywayButton,1073showExtensionsButton1074];1075}10761077else if (hasLimitedSupport) {1078message = limitedSupportMessage;1079detail = virtualWorkspaceSupportReason || undefined;1080buttons = [installAnywayButton];1081}10821083else {1084message = localize('non web extensions', "'{0}' contains extensions which are not supported in {1}.", extension.displayName || extension.identifier.id, productName);1085buttons = [1086installAnywayButton,1087showExtensionsButton1088];1089}10901091await this.dialogService.prompt({1092type: Severity.Info,1093message,1094detail,1095buttons,1096cancelButton: {1097run: () => { throw new CancellationError(); }1098}1099});1100}11011102private _targetPlatformPromise: Promise<TargetPlatform> | undefined;1103getTargetPlatform(): Promise<TargetPlatform> {1104if (!this._targetPlatformPromise) {1105this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService);1106}1107return this._targetPlatformPromise;1108}11091110async cleanUp(): Promise<void> {1111await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp()));1112}11131114toggleApplicationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension> {1115const server = this.getServer(extension);1116if (server) {1117return server.extensionManagementService.toggleApplicationScope(extension, fromProfileLocation);1118}1119throw new Error('Not Supported');1120}11211122copyExtensions(from: URI, to: URI): Promise<void> {1123if (this.extensionManagementServerService.remoteExtensionManagementServer) {1124throw new Error('Not Supported');1125}1126if (this.extensionManagementServerService.localExtensionManagementServer) {1127return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.copyExtensions(from, to);1128}1129if (this.extensionManagementServerService.webExtensionManagementServer) {1130return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.copyExtensions(from, to);1131}1132return Promise.resolve();1133}11341135registerParticipant() { throw new Error('Not Supported'); }1136installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]> { throw new Error('Not Supported'); }11371138isPublisherTrusted(extension: IGalleryExtension): boolean {1139const publisher = extension.publisher.toLowerCase();1140if (this.defaultTrustedPublishers.includes(publisher) || this.defaultTrustedPublishers.includes(extension.publisherDisplayName.toLowerCase())) {1141return true;1142}11431144// Check if the extension is allowed by publisher or extension id1145if (this.allowedExtensionsService.allowedExtensionsConfigValue && this.allowedExtensionsService.isAllowed(extension)) {1146return true;1147}11481149return this.isPublisherUserTrusted(publisher);1150}11511152private isPublisherUserTrusted(publisher: string): boolean {1153const trustedPublishers = this.getTrustedPublishersFromStorage();1154return !!trustedPublishers[publisher];1155}11561157getTrustedPublishers(): IPublisherInfo[] {1158const trustedPublishers = this.getTrustedPublishersFromStorage();1159return Object.keys(trustedPublishers).map(publisher => trustedPublishers[publisher]);1160}11611162trustPublishers(...publishers: IPublisherInfo[]): void {1163const trustedPublishers = this.getTrustedPublishersFromStorage();1164for (const publisher of publishers) {1165trustedPublishers[publisher.publisher.toLowerCase()] = publisher;1166}1167this.storageService.store(TrustedPublishersStorageKey, JSON.stringify(trustedPublishers), StorageScope.APPLICATION, StorageTarget.USER);1168}11691170untrustPublishers(...publishers: string[]): void {1171const trustedPublishers = this.getTrustedPublishersFromStorage();1172for (const publisher of publishers) {1173delete trustedPublishers[publisher.toLowerCase()];1174}1175this.storageService.store(TrustedPublishersStorageKey, JSON.stringify(trustedPublishers), StorageScope.APPLICATION, StorageTarget.USER);1176}11771178private getTrustedPublishersFromStorage(): IStringDictionary<IPublisherInfo> {1179const trustedPublishers = this.storageService.getObject<IStringDictionary<IPublisherInfo>>(TrustedPublishersStorageKey, StorageScope.APPLICATION, {});1180if (Array.isArray(trustedPublishers)) {1181this.storageService.remove(TrustedPublishersStorageKey, StorageScope.APPLICATION);1182return {};1183}1184return Object.keys(trustedPublishers).reduce<IStringDictionary<IPublisherInfo>>((result, publisher) => {1185result[publisher.toLowerCase()] = trustedPublishers[publisher];1186return result;1187}, {});1188}1189}11901191class WorkspaceExtensionsManagementService extends Disposable {11921193private static readonly WORKSPACE_EXTENSIONS_KEY = 'workspaceExtensions.locations';11941195private readonly _onDidChangeInvalidExtensions = this._register(new Emitter<ILocalExtension[]>());1196readonly onDidChangeInvalidExtensions = this._onDidChangeInvalidExtensions.event;11971198private readonly extensions: ILocalExtension[] = [];1199private readonly initializePromise: Promise<void>;12001201private readonly invalidExtensionWatchers = this._register(new DisposableStore());12021203constructor(1204@IFileService private readonly fileService: IFileService,1205@ILogService private readonly logService: ILogService,1206@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,1207@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,1208@IStorageService private readonly storageService: IStorageService,1209@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,1210@ITelemetryService private readonly telemetryService: ITelemetryService,1211) {1212super();12131214this._register(Event.debounce<FileChangesEvent, FileChangesEvent[]>(this.fileService.onDidFilesChange, (last, e) => {1215(last = last ?? []).push(e);1216return last;1217}, 1000)(events => {1218const changedInvalidExtensions = this.extensions.filter(extension => !extension.isValid && events.some(e => e.affects(extension.location)));1219if (changedInvalidExtensions.length) {1220this.checkExtensionsValidity(changedInvalidExtensions);1221}1222}));12231224this.initializePromise = this.initialize();1225}12261227private async initialize(): Promise<void> {1228const existingLocations = this.getInstalledWorkspaceExtensionsLocations();1229if (!existingLocations.length) {1230return;1231}12321233await Promise.allSettled(existingLocations.map(async location => {1234if (!this.workspaceService.isInsideWorkspace(location)) {1235this.logService.info(`Removing the workspace extension ${location.toString()} as it is not inside the workspace`);1236return;1237}1238if (!(await this.fileService.exists(location))) {1239this.logService.info(`Removing the workspace extension ${location.toString()} as it does not exist`);1240return;1241}1242try {1243const extension = await this.scanWorkspaceExtension(location);1244if (extension) {1245this.extensions.push(extension);1246} else {1247this.logService.info(`Skipping workspace extension ${location.toString()} as it does not exist`);1248}1249} catch (error) {1250this.logService.error('Skipping the workspace extension', location.toString(), error);1251}1252}));12531254this.saveWorkspaceExtensions();1255}12561257private watchInvalidExtensions(): void {1258this.invalidExtensionWatchers.clear();1259for (const extension of this.extensions) {1260if (!extension.isValid) {1261this.invalidExtensionWatchers.add(this.fileService.watch(extension.location));1262}1263}1264}12651266private async checkExtensionsValidity(extensions: ILocalExtension[]): Promise<void> {1267const validExtensions: ILocalExtension[] = [];1268await Promise.all(extensions.map(async extension => {1269const newExtension = await this.scanWorkspaceExtension(extension.location);1270if (newExtension?.isValid) {1271validExtensions.push(newExtension);1272}1273}));12741275let changed = false;1276for (const extension of validExtensions) {1277const index = this.extensions.findIndex(e => this.uriIdentityService.extUri.isEqual(e.location, extension.location));1278if (index !== -1) {1279changed = true;1280this.extensions.splice(index, 1, extension);1281}1282}12831284if (changed) {1285this.saveWorkspaceExtensions();1286this._onDidChangeInvalidExtensions.fire(validExtensions);1287}1288}12891290async getInstalled(includeInvalid: boolean): Promise<ILocalExtension[]> {1291await this.initializePromise;1292return this.extensions.filter(e => includeInvalid || e.isValid);1293}12941295async install(extension: IResourceExtension): Promise<ILocalExtension> {1296await this.initializePromise;12971298const workspaceExtension = await this.scanWorkspaceExtension(extension.location);1299if (!workspaceExtension) {1300throw new Error('Cannot install the extension as it does not exist.');1301}13021303const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier));1304if (existingExtensionIndex === -1) {1305this.extensions.push(workspaceExtension);1306} else {1307this.extensions.splice(existingExtensionIndex, 1, workspaceExtension);1308}13091310this.saveWorkspaceExtensions();1311this.telemetryService.publicLog2<{}, {1312owner: 'sandy081';1313comment: 'Install workspace extension';1314}>('workspaceextension:install');13151316return workspaceExtension;1317}13181319async uninstall(extension: ILocalExtension): Promise<void> {1320await this.initializePromise;13211322const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier));1323if (existingExtensionIndex !== -1) {1324this.extensions.splice(existingExtensionIndex, 1);1325this.saveWorkspaceExtensions();1326}13271328this.telemetryService.publicLog2<{}, {1329owner: 'sandy081';1330comment: 'Uninstall workspace extension';1331}>('workspaceextension:uninstall');1332}13331334getInstalledWorkspaceExtensionsLocations(): URI[] {1335const locations: URI[] = [];1336try {1337const parsed = JSON.parse(this.storageService.get(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, StorageScope.WORKSPACE, '[]'));1338if (Array.isArray(locations)) {1339for (const location of parsed) {1340if (isString(location)) {1341if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) {1342locations.push(this.workspaceService.getWorkspace().folders[0].toResource(location));1343} else {1344this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${location}`);1345}1346} else {1347locations.push(URI.revive(location));1348}1349}1350} else {1351this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${locations}`);1352}1353} catch (error) {1354this.logService.warn(`Error parsing workspace extensions locations: ${getErrorMessage(error)}`);1355}1356return locations;1357}13581359private saveWorkspaceExtensions(): void {1360const locations = this.extensions.map(extension => extension.location);1361if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) {1362this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY,1363JSON.stringify(coalesce(locations1364.map(location => this.uriIdentityService.extUri.relativePath(this.workspaceService.getWorkspace().folders[0].uri, location)))),1365StorageScope.WORKSPACE, StorageTarget.MACHINE);1366} else {1367this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, JSON.stringify(locations), StorageScope.WORKSPACE, StorageTarget.MACHINE);1368}1369this.watchInvalidExtensions();1370}13711372async scanWorkspaceExtension(location: URI): Promise<ILocalExtension | null> {1373const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { includeInvalid: true });1374return scannedExtension ? this.toLocalWorkspaceExtension(scannedExtension) : null;1375}13761377async toLocalWorkspaceExtension(extension: IScannedExtension): Promise<ILocalExtension> {1378const stat = await this.fileService.resolve(extension.location);1379let readmeUrl: URI | undefined;1380let changelogUrl: URI | undefined;1381if (stat.children) {1382readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;1383changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;1384}1385const validations: [Severity, string][] = [...extension.validations];1386let isValid = extension.isValid;1387if (extension.manifest.main) {1388if (!(await this.fileService.exists(this.uriIdentityService.extUri.joinPath(extension.location, extension.manifest.main)))) {1389isValid = false;1390validations.push([Severity.Error, localize('main.notFound', "Cannot activate because {0} not found", extension.manifest.main)]);1391}1392}1393return {1394identifier: extension.identifier,1395type: extension.type,1396isBuiltin: extension.isBuiltin || !!extension.metadata?.isBuiltin,1397location: extension.location,1398manifest: extension.manifest,1399targetPlatform: extension.targetPlatform,1400validations,1401isValid,1402readmeUrl,1403changelogUrl,1404publisherDisplayName: extension.metadata?.publisherDisplayName,1405publisherId: extension.metadata?.publisherId || null,1406isApplicationScoped: !!extension.metadata?.isApplicationScoped,1407isMachineScoped: !!extension.metadata?.isMachineScoped,1408isPreReleaseVersion: !!extension.metadata?.isPreReleaseVersion,1409hasPreReleaseVersion: !!extension.metadata?.hasPreReleaseVersion,1410preRelease: !!extension.metadata?.preRelease,1411installedTimestamp: extension.metadata?.installedTimestamp,1412updated: !!extension.metadata?.updated,1413pinned: !!extension.metadata?.pinned,1414isWorkspaceScoped: true,1415private: false,1416source: 'resource',1417size: extension.metadata?.size ?? 0,1418};1419}1420}142114221423