Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event, EventMultiplexer } from '../../../../base/common/event.js';
7
import {
8
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SOURCE_CONTEXT, InstallExtensionInfo,
9
IProductVersion,
10
ExtensionInstallSource,
11
DidUpdateExtensionMetadata,
12
UninstallExtensionInfo,
13
IAllowedExtensionsService,
14
EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT,
15
} from '../../../../platform/extensionManagement/common/extensionManagement.js';
16
import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IPublisherInfo, IResourceExtension, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from './extensionManagement.js';
17
import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
18
import { URI } from '../../../../base/common/uri.js';
19
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
20
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
21
import { CancellationToken } from '../../../../base/common/cancellation.js';
22
import { areSameExtensions, computeTargetPlatform } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
23
import { localize } from '../../../../nls.js';
24
import { IProductService } from '../../../../platform/product/common/productService.js';
25
import { Schemas } from '../../../../base/common/network.js';
26
import { IDownloadService } from '../../../../platform/download/common/download.js';
27
import { coalesce, distinct, isNonEmptyArray } from '../../../../base/common/arrays.js';
28
import { IDialogService, IPromptButton } from '../../../../platform/dialogs/common/dialogs.js';
29
import Severity from '../../../../base/common/severity.js';
30
import { IUserDataSyncEnablementService, SyncResource } from '../../../../platform/userDataSync/common/userDataSync.js';
31
import { Promises } from '../../../../base/common/async.js';
32
import { IWorkspaceTrustRequestService, WorkspaceTrustRequestButton } from '../../../../platform/workspace/common/workspaceTrust.js';
33
import { IExtensionManifestPropertiesService } from '../../extensions/common/extensionManifestPropertiesService.js';
34
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
35
import { ICommandService } from '../../../../platform/commands/common/commands.js';
36
import { isString, isUndefined } from '../../../../base/common/types.js';
37
import { FileChangesEvent, IFileService } from '../../../../platform/files/common/files.js';
38
import { ILogService } from '../../../../platform/log/common/log.js';
39
import { CancellationError, getErrorMessage } from '../../../../base/common/errors.js';
40
import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';
41
import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
42
import { IExtensionsScannerService, IScannedExtension } from '../../../../platform/extensionManagement/common/extensionsScannerService.js';
43
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
44
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
45
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
46
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
47
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
48
import { verifiedPublisherIcon } from './extensionsIcons.js';
49
import { Codicon } from '../../../../base/common/codicons.js';
50
import { IStringDictionary } from '../../../../base/common/collections.js';
51
import { CommontExtensionManagementService } from '../../../../platform/extensionManagement/common/abstractExtensionManagementService.js';
52
53
const TrustedPublishersStorageKey = 'extensions.trustedPublishers';
54
55
function isGalleryExtension(extension: IResourceExtension | IGalleryExtension): extension is IGalleryExtension {
56
return extension.type === 'gallery';
57
}
58
59
export class ExtensionManagementService extends CommontExtensionManagementService implements IWorkbenchExtensionManagementService {
60
61
declare readonly _serviceBrand: undefined;
62
63
private readonly defaultTrustedPublishers: readonly string[];
64
65
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionOnServerEvent>());
66
readonly onInstallExtension: Event<InstallExtensionOnServerEvent>;
67
68
private readonly _onDidInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());
69
readonly onDidInstallExtensions: Event<readonly InstallExtensionResult[]>;
70
71
private readonly _onUninstallExtension = this._register(new Emitter<UninstallExtensionOnServerEvent>());
72
readonly onUninstallExtension: Event<UninstallExtensionOnServerEvent>;
73
74
private readonly _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionOnServerEvent>());
75
readonly onDidUninstallExtension: Event<DidUninstallExtensionOnServerEvent>;
76
77
readonly onDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;
78
79
private readonly _onDidProfileAwareInstallExtensions = this._register(new Emitter<readonly InstallExtensionResult[]>());
80
readonly onProfileAwareDidInstallExtensions: Event<readonly InstallExtensionResult[]>;
81
82
private readonly _onDidProfileAwareUninstallExtension = this._register(new Emitter<DidUninstallExtensionOnServerEvent>());
83
readonly onProfileAwareDidUninstallExtension: Event<DidUninstallExtensionOnServerEvent>;
84
85
readonly onProfileAwareDidUpdateExtensionMetadata: Event<DidUpdateExtensionMetadata>;
86
87
readonly onDidChangeProfile: Event<DidChangeProfileForServerEvent>;
88
89
readonly onDidEnableExtensions: Event<ILocalExtension[]>;
90
91
protected readonly servers: IExtensionManagementServer[] = [];
92
93
private readonly workspaceExtensionManagementService: WorkspaceExtensionsManagementService;
94
95
constructor(
96
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
97
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
98
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
99
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
100
@IConfigurationService protected readonly configurationService: IConfigurationService,
101
@IProductService productService: IProductService,
102
@IDownloadService protected readonly downloadService: IDownloadService,
103
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
104
@IDialogService private readonly dialogService: IDialogService,
105
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
106
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
107
@IFileService private readonly fileService: IFileService,
108
@ILogService private readonly logService: ILogService,
109
@IInstantiationService private readonly instantiationService: IInstantiationService,
110
@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,
111
@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,
112
@IStorageService private readonly storageService: IStorageService,
113
@ITelemetryService private readonly telemetryService: ITelemetryService,
114
) {
115
super(productService, allowedExtensionsService);
116
117
this.defaultTrustedPublishers = productService.trustedExtensionPublishers ?? [];
118
this.workspaceExtensionManagementService = this._register(this.instantiationService.createInstance(WorkspaceExtensionsManagementService));
119
this.onDidEnableExtensions = this.workspaceExtensionManagementService.onDidChangeInvalidExtensions;
120
121
if (this.extensionManagementServerService.localExtensionManagementServer) {
122
this.servers.push(this.extensionManagementServerService.localExtensionManagementServer);
123
}
124
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
125
this.servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
126
}
127
if (this.extensionManagementServerService.webExtensionManagementServer) {
128
this.servers.push(this.extensionManagementServerService.webExtensionManagementServer);
129
}
130
131
const onInstallExtensionEventMultiplexer = this._register(new EventMultiplexer<InstallExtensionOnServerEvent>());
132
this._register(onInstallExtensionEventMultiplexer.add(this._onInstallExtension.event));
133
this.onInstallExtension = onInstallExtensionEventMultiplexer.event;
134
135
const onDidInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer<readonly InstallExtensionResult[]>());
136
this._register(onDidInstallExtensionsEventMultiplexer.add(this._onDidInstallExtensions.event));
137
this.onDidInstallExtensions = onDidInstallExtensionsEventMultiplexer.event;
138
139
const onDidProfileAwareInstallExtensionsEventMultiplexer = this._register(new EventMultiplexer<readonly InstallExtensionResult[]>());
140
this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(this._onDidProfileAwareInstallExtensions.event));
141
this.onProfileAwareDidInstallExtensions = onDidProfileAwareInstallExtensionsEventMultiplexer.event;
142
143
const onUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<UninstallExtensionOnServerEvent>());
144
this._register(onUninstallExtensionEventMultiplexer.add(this._onUninstallExtension.event));
145
this.onUninstallExtension = onUninstallExtensionEventMultiplexer.event;
146
147
const onDidUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<DidUninstallExtensionOnServerEvent>());
148
this._register(onDidUninstallExtensionEventMultiplexer.add(this._onDidUninstallExtension.event));
149
this.onDidUninstallExtension = onDidUninstallExtensionEventMultiplexer.event;
150
151
const onDidProfileAwareUninstallExtensionEventMultiplexer = this._register(new EventMultiplexer<DidUninstallExtensionOnServerEvent>());
152
this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(this._onDidProfileAwareUninstallExtension.event));
153
this.onProfileAwareDidUninstallExtension = onDidProfileAwareUninstallExtensionEventMultiplexer.event;
154
155
const onDidUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer<DidUpdateExtensionMetadata>());
156
this.onDidUpdateExtensionMetadata = onDidUpdateExtensionMetadaEventMultiplexer.event;
157
158
const onDidProfileAwareUpdateExtensionMetadaEventMultiplexer = this._register(new EventMultiplexer<DidUpdateExtensionMetadata>());
159
this.onProfileAwareDidUpdateExtensionMetadata = onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.event;
160
161
const onDidChangeProfileEventMultiplexer = this._register(new EventMultiplexer<DidChangeProfileForServerEvent>());
162
this.onDidChangeProfile = onDidChangeProfileEventMultiplexer.event;
163
164
for (const server of this.servers) {
165
this._register(onInstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onInstallExtension, e => ({ ...e, server }))));
166
this._register(onDidInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onDidInstallExtensions));
167
this._register(onDidProfileAwareInstallExtensionsEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidInstallExtensions));
168
this._register(onUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server }))));
169
this._register(onDidUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server }))));
170
this._register(onDidProfileAwareUninstallExtensionEventMultiplexer.add(Event.map(server.extensionManagementService.onProfileAwareDidUninstallExtension, e => ({ ...e, server }))));
171
this._register(onDidUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onDidUpdateExtensionMetadata));
172
this._register(onDidProfileAwareUpdateExtensionMetadaEventMultiplexer.add(server.extensionManagementService.onProfileAwareDidUpdateExtensionMetadata));
173
this._register(onDidChangeProfileEventMultiplexer.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server }))));
174
}
175
176
this._register(this.onProfileAwareDidInstallExtensions(results => {
177
const untrustedPublishers = new Map<string, IPublisherInfo>();
178
for (const result of results) {
179
if (result.local && result.source && !URI.isUri(result.source) && !this.isPublisherTrusted(result.source)) {
180
untrustedPublishers.set(result.source.publisher, { publisher: result.source.publisher, publisherDisplayName: result.source.publisherDisplayName });
181
}
182
}
183
if (untrustedPublishers.size) {
184
this.trustPublishers(...untrustedPublishers.values());
185
}
186
}));
187
}
188
189
async getInstalled(type?: ExtensionType, profileLocation?: URI, productVersion?: IProductVersion): Promise<ILocalExtension[]> {
190
const result: ILocalExtension[] = [];
191
await Promise.all(this.servers.map(async server => {
192
const installed = await server.extensionManagementService.getInstalled(type, profileLocation, productVersion);
193
if (server === this.getWorkspaceExtensionsServer()) {
194
const workspaceExtensions = await this.getInstalledWorkspaceExtensions(true);
195
installed.push(...workspaceExtensions);
196
}
197
result.push(...installed);
198
}));
199
return result;
200
}
201
202
uninstall(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
203
return this.uninstallExtensions([{ extension, options }]);
204
}
205
206
async uninstallExtensions(extensions: UninstallExtensionInfo[]): Promise<void> {
207
const workspaceExtensions: ILocalExtension[] = [];
208
const groupedExtensions = new Map<IExtensionManagementServer, UninstallExtensionInfo[]>();
209
210
const addExtensionToServer = (server: IExtensionManagementServer, extension: ILocalExtension, options?: UninstallOptions) => {
211
let extensions = groupedExtensions.get(server);
212
if (!extensions) {
213
groupedExtensions.set(server, extensions = []);
214
}
215
extensions.push({ extension, options });
216
};
217
218
for (const { extension, options } of extensions) {
219
if (extension.isWorkspaceScoped) {
220
workspaceExtensions.push(extension);
221
continue;
222
}
223
224
const server = this.getServer(extension);
225
if (!server) {
226
throw new Error(`Invalid location ${extension.location.toString()}`);
227
}
228
addExtensionToServer(server, extension, options);
229
if (this.servers.length > 1 && isLanguagePackExtension(extension.manifest)) {
230
const otherServers: IExtensionManagementServer[] = this.servers.filter(s => s !== server);
231
for (const otherServer of otherServers) {
232
const installed = await otherServer.extensionManagementService.getInstalled();
233
const extensionInOtherServer = installed.find(i => !i.isBuiltin && areSameExtensions(i.identifier, extension.identifier));
234
if (extensionInOtherServer) {
235
addExtensionToServer(otherServer, extensionInOtherServer, options);
236
}
237
}
238
}
239
}
240
241
const promises: Promise<void>[] = [];
242
for (const workspaceExtension of workspaceExtensions) {
243
promises.push(this.uninstallExtensionFromWorkspace(workspaceExtension));
244
}
245
for (const [server, extensions] of groupedExtensions.entries()) {
246
promises.push(this.uninstallInServer(server, extensions));
247
}
248
249
const result = await Promise.allSettled(promises);
250
const errors = result.filter(r => r.status === 'rejected').map(r => r.reason);
251
if (errors.length) {
252
throw new Error(errors.map(e => e.message).join('\n'));
253
}
254
}
255
256
private async uninstallInServer(server: IExtensionManagementServer, extensions: UninstallExtensionInfo[]): Promise<void> {
257
if (server === this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
258
for (const { extension } of extensions) {
259
const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getInstalled(ExtensionType.User);
260
const dependentNonUIExtensions = installedExtensions.filter(i => !this.extensionManifestPropertiesService.prefersExecuteOnUI(i.manifest)
261
&& i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
262
if (dependentNonUIExtensions.length) {
263
throw (new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));
264
}
265
}
266
}
267
return server.extensionManagementService.uninstallExtensions(extensions);
268
}
269
270
private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
271
if (dependents.length === 1) {
272
return localize('singleDependentError', "Cannot uninstall extension '{0}'. Extension '{1}' depends on this.",
273
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name);
274
}
275
if (dependents.length === 2) {
276
return localize('twoDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}' and '{2}' depend on this.",
277
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
278
}
279
return localize('multipleDependentsError', "Cannot uninstall extension '{0}'. Extensions '{1}', '{2}' and others depend on this.",
280
extension.manifest.displayName || extension.manifest.name, dependents[0].manifest.displayName || dependents[0].manifest.name, dependents[1].manifest.displayName || dependents[1].manifest.name);
281
282
}
283
284
updateMetadata(extension: ILocalExtension, metadata: Partial<Metadata>): Promise<ILocalExtension> {
285
const server = this.getServer(extension);
286
if (server) {
287
const profile = extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile : this.userDataProfileService.currentProfile;
288
return server.extensionManagementService.updateMetadata(extension, metadata, profile.extensionsResource);
289
}
290
return Promise.reject(`Invalid location ${extension.location.toString()}`);
291
}
292
293
async resetPinnedStateForAllUserExtensions(pinned: boolean): Promise<void> {
294
await Promise.allSettled(this.servers.map(server => server.extensionManagementService.resetPinnedStateForAllUserExtensions(pinned)));
295
}
296
297
zip(extension: ILocalExtension): Promise<URI> {
298
const server = this.getServer(extension);
299
if (server) {
300
return server.extensionManagementService.zip(extension);
301
}
302
return Promise.reject(`Invalid location ${extension.location.toString()}`);
303
}
304
305
download(extension: IGalleryExtension, operation: InstallOperation, donotVerifySignature: boolean): Promise<URI> {
306
if (this.extensionManagementServerService.localExtensionManagementServer) {
307
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.download(extension, operation, donotVerifySignature);
308
}
309
throw new Error('Cannot download extension');
310
}
311
312
async install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension> {
313
const manifest = await this.getManifest(vsix);
314
return this.installVSIX(vsix, manifest, options);
315
}
316
317
async installVSIX(vsix: URI, manifest: IExtensionManifest, options?: InstallOptions): Promise<ILocalExtension> {
318
const serversToInstall = this.getServersToInstall(manifest);
319
if (serversToInstall?.length) {
320
await this.checkForWorkspaceTrust(manifest, false);
321
const [local] = await Promises.settled(serversToInstall.map(server => this.installVSIXInServer(vsix, server, options)));
322
return local;
323
}
324
return Promise.reject('No Servers to Install');
325
}
326
327
private getServersToInstall(manifest: IExtensionManifest): IExtensionManagementServer[] | undefined {
328
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
329
if (isLanguagePackExtension(manifest)) {
330
// Install on both servers
331
return [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer];
332
}
333
if (this.extensionManifestPropertiesService.prefersExecuteOnUI(manifest)) {
334
// Install only on local server
335
return [this.extensionManagementServerService.localExtensionManagementServer];
336
}
337
// Install only on remote server
338
return [this.extensionManagementServerService.remoteExtensionManagementServer];
339
}
340
if (this.extensionManagementServerService.localExtensionManagementServer) {
341
return [this.extensionManagementServerService.localExtensionManagementServer];
342
}
343
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
344
return [this.extensionManagementServerService.remoteExtensionManagementServer];
345
}
346
return undefined;
347
}
348
349
async installFromLocation(location: URI): Promise<ILocalExtension> {
350
if (location.scheme === Schemas.file) {
351
if (this.extensionManagementServerService.localExtensionManagementServer) {
352
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);
353
}
354
throw new Error('Local extension management server is not found');
355
}
356
if (location.scheme === Schemas.vscodeRemote) {
357
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
358
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);
359
}
360
throw new Error('Remote extension management server is not found');
361
}
362
if (!this.extensionManagementServerService.webExtensionManagementServer) {
363
throw new Error('Web extension management server is not found');
364
}
365
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.installFromLocation(location, this.userDataProfileService.currentProfile.extensionsResource);
366
}
367
368
protected installVSIXInServer(vsix: URI, server: IExtensionManagementServer, options: InstallOptions | undefined): Promise<ILocalExtension> {
369
return server.extensionManagementService.install(vsix, options);
370
}
371
372
getManifest(vsix: URI): Promise<IExtensionManifest> {
373
if (vsix.scheme === Schemas.file && this.extensionManagementServerService.localExtensionManagementServer) {
374
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getManifest(vsix);
375
}
376
if (vsix.scheme === Schemas.file && this.extensionManagementServerService.remoteExtensionManagementServer) {
377
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix);
378
}
379
if (vsix.scheme === Schemas.vscodeRemote && this.extensionManagementServerService.remoteExtensionManagementServer) {
380
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix);
381
}
382
return Promise.reject('No Servers');
383
}
384
385
override async canInstall(extension: IGalleryExtension | IResourceExtension): Promise<true | IMarkdownString> {
386
if (isGalleryExtension(extension)) {
387
return this.canInstallGalleryExtension(extension);
388
}
389
return this.canInstallResourceExtension(extension);
390
}
391
392
private async canInstallGalleryExtension(gallery: IGalleryExtension): Promise<true | IMarkdownString> {
393
if (this.extensionManagementServerService.localExtensionManagementServer
394
&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) {
395
return true;
396
}
397
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
398
if (!manifest) {
399
return new MarkdownString().appendText(localize('manifest is not found', "Manifest is not found"));
400
}
401
if (this.extensionManagementServerService.remoteExtensionManagementServer
402
&& await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.canInstall(gallery) === true
403
&& this.extensionManifestPropertiesService.canExecuteOnWorkspace(manifest)) {
404
return true;
405
}
406
if (this.extensionManagementServerService.webExtensionManagementServer
407
&& await this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.canInstall(gallery) === true
408
&& this.extensionManifestPropertiesService.canExecuteOnWeb(manifest)) {
409
return true;
410
}
411
return new MarkdownString().appendText(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name));
412
}
413
414
private async canInstallResourceExtension(extension: IResourceExtension): Promise<true | IMarkdownString> {
415
if (this.extensionManagementServerService.localExtensionManagementServer) {
416
return true;
417
}
418
if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWorkspace(extension.manifest)) {
419
return true;
420
}
421
if (this.extensionManagementServerService.webExtensionManagementServer && this.extensionManifestPropertiesService.canExecuteOnWeb(extension.manifest)) {
422
return true;
423
}
424
return 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));
425
}
426
427
async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
428
const server = this.getServer(extension);
429
if (!server) {
430
return Promise.reject(`Invalid location ${extension.location.toString()}`);
431
}
432
433
const servers: IExtensionManagementServer[] = [];
434
435
// Update Language pack on local and remote servers
436
if (isLanguagePackExtension(extension.manifest)) {
437
servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer));
438
} else {
439
servers.push(server);
440
}
441
442
installOptions = { ...(installOptions || {}), isApplicationScoped: extension.isApplicationScoped };
443
return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);
444
}
445
446
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
447
const results = new Map<string, InstallExtensionResult>();
448
449
const extensionsByServer = new Map<IExtensionManagementServer, InstallExtensionInfo[]>();
450
const manifests = await Promise.all(extensions.map(async ({ extension }) => {
451
const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
452
if (!manifest) {
453
throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));
454
}
455
return manifest;
456
}));
457
458
if (extensions.some(e => e.options?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true)) {
459
await this.checkForTrustedPublishers(extensions.map((e, index) => ({ extension: e.extension, manifest: manifests[index], checkForPackAndDependencies: !e.options?.donotIncludePackAndDependencies })));
460
}
461
462
await Promise.all(extensions.map(async ({ extension, options }) => {
463
try {
464
const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
465
if (!manifest) {
466
throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));
467
}
468
469
if (options?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {
470
await this.checkForWorkspaceTrust(manifest, false);
471
472
if (!options?.donotIncludePackAndDependencies) {
473
await this.checkInstallingExtensionOnWeb(extension, manifest);
474
}
475
}
476
477
const servers = await this.getExtensionManagementServersToInstall(extension, manifest);
478
if (!options.isMachineScoped && this.isExtensionsSyncEnabled()) {
479
if (this.extensionManagementServerService.localExtensionManagementServer
480
&& !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)
481
&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(extension) === true) {
482
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
483
}
484
}
485
for (const server of servers) {
486
let exensions = extensionsByServer.get(server);
487
if (!exensions) {
488
extensionsByServer.set(server, exensions = []);
489
}
490
exensions.push({ extension, options });
491
}
492
} catch (error) {
493
results.set(extension.identifier.id.toLowerCase(), {
494
identifier: extension.identifier,
495
source: extension, error,
496
operation: InstallOperation.Install,
497
profileLocation: options.profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource
498
});
499
}
500
}));
501
502
await Promise.all([...extensionsByServer.entries()].map(async ([server, extensions]) => {
503
const serverResults = await server.extensionManagementService.installGalleryExtensions(extensions);
504
for (const result of serverResults) {
505
results.set(result.identifier.id.toLowerCase(), result);
506
}
507
}));
508
509
return [...results.values()];
510
}
511
512
async installFromGallery(gallery: IGalleryExtension, installOptions?: InstallOptions, servers?: IExtensionManagementServer[]): Promise<ILocalExtension> {
513
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
514
if (!manifest) {
515
throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
516
}
517
518
if (installOptions?.context?.[EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT] !== true) {
519
await this.checkForTrustedPublishers([{ extension: gallery, manifest, checkForPackAndDependencies: !installOptions?.donotIncludePackAndDependencies }],);
520
}
521
522
if (installOptions?.context?.[EXTENSION_INSTALL_SOURCE_CONTEXT] !== ExtensionInstallSource.SETTINGS_SYNC) {
523
524
await this.checkForWorkspaceTrust(manifest, false);
525
526
if (!installOptions?.donotIncludePackAndDependencies) {
527
await this.checkInstallingExtensionOnWeb(gallery, manifest);
528
}
529
}
530
531
servers = servers?.length ? this.validServers(gallery, manifest, servers) : await this.getExtensionManagementServersToInstall(gallery, manifest);
532
if (!installOptions || isUndefined(installOptions.isMachineScoped)) {
533
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
534
installOptions = { ...(installOptions || {}), isMachineScoped };
535
}
536
537
if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {
538
if (this.extensionManagementServerService.localExtensionManagementServer
539
&& !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)
540
&& await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery) === true) {
541
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
542
}
543
}
544
545
return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);
546
}
547
548
async getExtensions(locations: URI[]): Promise<IResourceExtension[]> {
549
const scannedExtensions = await this.extensionsScannerService.scanMultipleExtensions(locations, ExtensionType.User, { includeInvalid: true });
550
const result: IResourceExtension[] = [];
551
await Promise.all(scannedExtensions.map(async scannedExtension => {
552
const workspaceExtension = await this.workspaceExtensionManagementService.toLocalWorkspaceExtension(scannedExtension);
553
if (workspaceExtension) {
554
result.push({
555
type: 'resource',
556
identifier: workspaceExtension.identifier,
557
location: workspaceExtension.location,
558
manifest: workspaceExtension.manifest,
559
changelogUri: workspaceExtension.changelogUrl,
560
readmeUri: workspaceExtension.readmeUrl,
561
});
562
}
563
}));
564
return result;
565
}
566
567
getInstalledWorkspaceExtensionLocations(): URI[] {
568
return this.workspaceExtensionManagementService.getInstalledWorkspaceExtensionsLocations();
569
}
570
571
async getInstalledWorkspaceExtensions(includeInvalid: boolean): Promise<ILocalExtension[]> {
572
return this.workspaceExtensionManagementService.getInstalled(includeInvalid);
573
}
574
575
async installResourceExtension(extension: IResourceExtension, installOptions: InstallOptions): Promise<ILocalExtension> {
576
if (!this.canInstallResourceExtension(extension)) {
577
throw new Error('This extension cannot be installed in the current workspace.');
578
}
579
if (!installOptions.isWorkspaceScoped) {
580
return this.installFromLocation(extension.location);
581
}
582
583
this.logService.info(`Installing the extension ${extension.identifier.id} from ${extension.location.toString()} in workspace`);
584
const server = this.getWorkspaceExtensionsServer();
585
this._onInstallExtension.fire({
586
identifier: extension.identifier,
587
source: extension.location,
588
server,
589
applicationScoped: false,
590
profileLocation: this.userDataProfileService.currentProfile.extensionsResource,
591
workspaceScoped: true
592
});
593
594
try {
595
await this.checkForWorkspaceTrust(extension.manifest, true);
596
597
const workspaceExtension = await this.workspaceExtensionManagementService.install(extension);
598
599
this.logService.info(`Successfully installed the extension ${workspaceExtension.identifier.id} from ${extension.location.toString()} in the workspace`);
600
this._onDidInstallExtensions.fire([{
601
identifier: workspaceExtension.identifier,
602
source: extension.location,
603
operation: InstallOperation.Install,
604
applicationScoped: false,
605
profileLocation: this.userDataProfileService.currentProfile.extensionsResource,
606
local: workspaceExtension,
607
workspaceScoped: true
608
}]);
609
return workspaceExtension;
610
} catch (error) {
611
this.logService.error(`Failed to install the extension ${extension.identifier.id} from ${extension.location.toString()} in the workspace`, getErrorMessage(error));
612
this._onDidInstallExtensions.fire([{
613
identifier: extension.identifier,
614
source: extension.location,
615
operation: InstallOperation.Install,
616
applicationScoped: false,
617
profileLocation: this.userDataProfileService.currentProfile.extensionsResource,
618
error,
619
workspaceScoped: true
620
}]);
621
throw error;
622
}
623
}
624
625
async getInstallableServers(gallery: IGalleryExtension): Promise<IExtensionManagementServer[]> {
626
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
627
if (!manifest) {
628
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
629
}
630
return this.getInstallableExtensionManagementServers(manifest);
631
}
632
633
private async uninstallExtensionFromWorkspace(extension: ILocalExtension): Promise<void> {
634
if (!extension.isWorkspaceScoped) {
635
throw new Error('The extension is not a workspace extension');
636
}
637
638
this.logService.info(`Uninstalling the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`);
639
const server = this.getWorkspaceExtensionsServer();
640
this._onUninstallExtension.fire({
641
identifier: extension.identifier,
642
server,
643
applicationScoped: false,
644
workspaceScoped: true,
645
profileLocation: this.userDataProfileService.currentProfile.extensionsResource
646
});
647
648
try {
649
await this.workspaceExtensionManagementService.uninstall(extension);
650
this.logService.info(`Successfully uninstalled the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`);
651
this.telemetryService.publicLog2<{}, {
652
owner: 'sandy081';
653
comment: 'Uninstall workspace extension';
654
}>('workspaceextension:uninstall');
655
this._onDidUninstallExtension.fire({
656
identifier: extension.identifier,
657
server,
658
applicationScoped: false,
659
workspaceScoped: true,
660
profileLocation: this.userDataProfileService.currentProfile.extensionsResource
661
});
662
} catch (error) {
663
this.logService.error(`Failed to uninstall the workspace extension ${extension.identifier.id} from ${extension.location.toString()}`, getErrorMessage(error));
664
this._onDidUninstallExtension.fire({
665
identifier: extension.identifier,
666
server,
667
error,
668
applicationScoped: false,
669
workspaceScoped: true,
670
profileLocation: this.userDataProfileService.currentProfile.extensionsResource
671
});
672
throw error;
673
}
674
}
675
676
private validServers(gallery: IGalleryExtension, manifest: IExtensionManifest, servers: IExtensionManagementServer[]): IExtensionManagementServer[] {
677
const installableServers = this.getInstallableExtensionManagementServers(manifest);
678
for (const server of servers) {
679
if (!installableServers.includes(server)) {
680
const 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));
681
error.name = ExtensionManagementErrorCode.Unsupported;
682
throw error;
683
}
684
}
685
return servers;
686
}
687
688
private async getExtensionManagementServersToInstall(gallery: IGalleryExtension, manifest: IExtensionManifest): Promise<IExtensionManagementServer[]> {
689
const servers: IExtensionManagementServer[] = [];
690
691
// Language packs should be installed on both local and remote servers
692
if (isLanguagePackExtension(manifest)) {
693
servers.push(...this.servers.filter(server => server !== this.extensionManagementServerService.webExtensionManagementServer));
694
}
695
696
else {
697
const [server] = this.getInstallableExtensionManagementServers(manifest);
698
if (server) {
699
servers.push(server);
700
}
701
}
702
703
if (!servers.length) {
704
const error = new Error(localize('cannot be installed', "Cannot install the '{0}' extension because it is not available in this setup.", gallery.displayName || gallery.name));
705
error.name = ExtensionManagementErrorCode.Unsupported;
706
throw error;
707
}
708
709
return servers;
710
}
711
712
private getInstallableExtensionManagementServers(manifest: IExtensionManifest): IExtensionManagementServer[] {
713
// Only local server
714
if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) {
715
return [this.extensionManagementServerService.localExtensionManagementServer];
716
}
717
718
const servers: IExtensionManagementServer[] = [];
719
720
const extensionKind = this.extensionManifestPropertiesService.getExtensionKind(manifest);
721
for (const kind of extensionKind) {
722
if (kind === 'ui' && this.extensionManagementServerService.localExtensionManagementServer) {
723
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
724
}
725
if (kind === 'workspace' && this.extensionManagementServerService.remoteExtensionManagementServer) {
726
servers.push(this.extensionManagementServerService.remoteExtensionManagementServer);
727
}
728
if (kind === 'web' && this.extensionManagementServerService.webExtensionManagementServer) {
729
servers.push(this.extensionManagementServerService.webExtensionManagementServer);
730
}
731
}
732
733
// Local server can accept any extension.
734
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) {
735
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
736
}
737
738
return servers;
739
}
740
741
private isExtensionsSyncEnabled(): boolean {
742
return this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);
743
}
744
745
private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise<boolean> {
746
if (this.isExtensionsSyncEnabled()) {
747
const { result } = await this.dialogService.prompt<boolean>({
748
type: Severity.Info,
749
message: extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),
750
detail: extensions.length === 1
751
? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)
752
: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?"),
753
buttons: [
754
{
755
label: localize({ key: 'install', comment: ['&& denotes a mnemonic'] }, "&&Install"),
756
run: () => false
757
},
758
{
759
label: localize({ key: 'install and do no sync', comment: ['&& denotes a mnemonic'] }, "Install (Do &&not sync)"),
760
run: () => true
761
}
762
],
763
cancelButton: {
764
run: () => {
765
throw new CancellationError();
766
}
767
}
768
});
769
770
return result;
771
}
772
return false;
773
}
774
775
getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
776
if (this.extensionManagementServerService.localExtensionManagementServer) {
777
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
778
}
779
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
780
return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
781
}
782
if (this.extensionManagementServerService.webExtensionManagementServer) {
783
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
784
}
785
return this.extensionGalleryService.getExtensionsControlManifest();
786
}
787
788
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
789
if (extension.isWorkspaceScoped) {
790
return this.getWorkspaceExtensionsServer();
791
}
792
return this.extensionManagementServerService.getExtensionManagementServer(extension);
793
}
794
795
private getWorkspaceExtensionsServer(): IExtensionManagementServer {
796
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
797
return this.extensionManagementServerService.remoteExtensionManagementServer;
798
}
799
if (this.extensionManagementServerService.localExtensionManagementServer) {
800
return this.extensionManagementServerService.localExtensionManagementServer;
801
}
802
if (this.extensionManagementServerService.webExtensionManagementServer) {
803
return this.extensionManagementServerService.webExtensionManagementServer;
804
}
805
throw new Error('No extension server found');
806
}
807
808
async requestPublisherTrust(extensions: InstallExtensionInfo[]): Promise<void> {
809
const manifests = await Promise.all(extensions.map(async ({ extension }) => {
810
const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
811
if (!manifest) {
812
throw new Error(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", extension.displayName || extension.name));
813
}
814
return manifest;
815
}));
816
817
await this.checkForTrustedPublishers(extensions.map((e, index) => ({ extension: e.extension, manifest: manifests[index], checkForPackAndDependencies: !e.options?.donotIncludePackAndDependencies })));
818
}
819
820
private async checkForTrustedPublishers(extensions: { extension: IGalleryExtension; manifest: IExtensionManifest; checkForPackAndDependencies: boolean }[]): Promise<void> {
821
const untrustedExtensions: IGalleryExtension[] = [];
822
const untrustedExtensionManifests: IExtensionManifest[] = [];
823
const manifestsToGetOtherUntrustedPublishers: IExtensionManifest[] = [];
824
for (const { extension, manifest, checkForPackAndDependencies } of extensions) {
825
if (!extension.private && !this.isPublisherTrusted(extension)) {
826
untrustedExtensions.push(extension);
827
untrustedExtensionManifests.push(manifest);
828
if (checkForPackAndDependencies) {
829
manifestsToGetOtherUntrustedPublishers.push(manifest);
830
}
831
}
832
}
833
834
if (!untrustedExtensions.length) {
835
return;
836
}
837
838
const otherUntrustedPublishers = manifestsToGetOtherUntrustedPublishers.length ? await this.getOtherUntrustedPublishers(manifestsToGetOtherUntrustedPublishers) : [];
839
const allPublishers = [...distinct(untrustedExtensions, e => e.publisher), ...otherUntrustedPublishers];
840
const unverfiiedPublishers = allPublishers.filter(p => !p.publisherDomain?.verified);
841
const verifiedPublishers = allPublishers.filter(p => p.publisherDomain?.verified);
842
843
type TrustPublisherClassification = {
844
owner: 'sandy081';
845
comment: 'Report the action taken by the user on the publisher trust dialog';
846
action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action taken by the user on the publisher trust dialog. Can be trust, learn more or cancel.' };
847
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifiers of the extension for which the publisher trust dialog was shown.' };
848
};
849
type TrustPublisherEvent = {
850
action: string;
851
extensionId: string;
852
};
853
854
const installButton: IPromptButton<void> = {
855
label: 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"),
856
run: () => {
857
this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'trust', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });
858
this.trustPublishers(...allPublishers.map(p => ({ publisher: p.publisher, publisherDisplayName: p.publisherDisplayName })));
859
}
860
};
861
862
const learnMoreButton: IPromptButton<void> = {
863
label: localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More"),
864
run: () => {
865
this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'learn', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });
866
this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('vscode.open', URI.parse('https://aka.ms/vscode-extension-security')));
867
throw new CancellationError();
868
}
869
};
870
871
const getPublisherLink = ({ publisherDisplayName, publisherLink }: { publisherDisplayName: string; publisherLink?: string }) => {
872
return publisherLink ? `[${publisherDisplayName}](${publisherLink})` : publisherDisplayName;
873
};
874
875
const unverifiedLink = 'https://aka.ms/vscode-verify-publisher';
876
877
const title = allPublishers.length === 1
878
? localize('checkTrustedPublisherTitle', "Do you trust the publisher \"{0}\"?", allPublishers[0].publisherDisplayName)
879
: allPublishers.length === 2
880
? localize('checkTwoTrustedPublishersTitle', "Do you trust publishers \"{0}\" and \"{1}\"?", allPublishers[0].publisherDisplayName, allPublishers[1].publisherDisplayName)
881
: localize('checkAllTrustedPublishersTitle', "Do you trust the publisher \"{0}\" and {1} others?", allPublishers[0].publisherDisplayName, allPublishers.length - 1);
882
883
const customMessage = new MarkdownString('', { supportThemeIcons: true, isTrusted: true });
884
885
if (untrustedExtensions.length === 1) {
886
const extension = untrustedExtensions[0];
887
const manifest = untrustedExtensionManifests[0];
888
if (otherUntrustedPublishers.length) {
889
customMessage.appendMarkdown(localize('extension published by message', "The extension {0} is published by {1}.", `[${extension.displayName}](${extension.detailsLink})`, getPublisherLink(extension)));
890
customMessage.appendMarkdown('&nbsp;');
891
const commandUri = URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([extension.identifier.id, manifest.extensionPack?.length ? 'extensionPack' : 'dependencies']))}`).toString();
892
if (otherUntrustedPublishers.length === 1) {
893
customMessage.appendMarkdown(localize('singleUntrustedPublisher', "Installing this extension will also install [extensions]({0}) published by {1}.", commandUri, getPublisherLink(otherUntrustedPublishers[0])));
894
} else {
895
customMessage.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])));
896
}
897
customMessage.appendMarkdown('&nbsp;');
898
customMessage.appendMarkdown(localize('firstTimeInstallingMessage', "This is the first time you're installing extensions from these publishers."));
899
} else {
900
customMessage.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)));
901
}
902
} else {
903
customMessage.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])));
904
}
905
906
if (verifiedPublishers.length || unverfiiedPublishers.length === 1) {
907
for (const publisher of verifiedPublishers) {
908
customMessage.appendText('\n');
909
const publisherVerifiedMessage = localize('verifiedPublisherWithName', "{0} has verified ownership of {1}.", getPublisherLink(publisher), `[$(link-external) ${URI.parse(publisher.publisherDomain!.link).authority}](${publisher.publisherDomain!.link})`);
910
customMessage.appendMarkdown(`$(${verifiedPublisherIcon.id})&nbsp;${publisherVerifiedMessage}`);
911
}
912
if (unverfiiedPublishers.length) {
913
customMessage.appendText('\n');
914
if (unverfiiedPublishers.length === 1) {
915
customMessage.appendMarkdown(`$(${Codicon.unverified.id})&nbsp;${localize('unverifiedPublisherWithName', "{0} is [**not** verified]({1}).", getPublisherLink(unverfiiedPublishers[0]), unverifiedLink)}`);
916
} else {
917
customMessage.appendMarkdown(`$(${Codicon.unverified.id})&nbsp;${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)}`);
918
}
919
}
920
} else {
921
customMessage.appendText('\n');
922
customMessage.appendMarkdown(`$(${Codicon.unverified.id})&nbsp;${localize('allUnverifed', "All publishers are [**not** verified]({0}).", unverifiedLink)}`);
923
}
924
925
customMessage.appendText('\n');
926
if (allPublishers.length > 1) {
927
customMessage.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));
928
} else {
929
customMessage.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));
930
}
931
932
await this.dialogService.prompt({
933
message: title,
934
type: Severity.Warning,
935
buttons: [installButton, learnMoreButton],
936
cancelButton: {
937
run: () => {
938
this.telemetryService.publicLog2<TrustPublisherEvent, TrustPublisherClassification>('extensions:trustPublisher', { action: 'cancel', extensionId: untrustedExtensions.map(e => e.identifier.id).join(',') });
939
throw new CancellationError();
940
}
941
},
942
custom: {
943
markdownDetails: [{ markdown: customMessage, classes: ['extensions-management-publisher-trust-dialog'] }],
944
}
945
});
946
947
}
948
949
private async getOtherUntrustedPublishers(manifests: IExtensionManifest[]): Promise<{ publisher: string; publisherDisplayName: string; publisherLink?: string; publisherDomain?: { link: string; verified: boolean } }[]> {
950
const extensionIds = new Set<string>();
951
for (const manifest of manifests) {
952
for (const id of [...(manifest.extensionPack ?? []), ...(manifest.extensionDependencies ?? [])]) {
953
const [publisherId] = id.split('.');
954
if (publisherId.toLowerCase() === manifest.publisher.toLowerCase()) {
955
continue;
956
}
957
if (this.isPublisherUserTrusted(publisherId.toLowerCase())) {
958
continue;
959
}
960
extensionIds.add(id.toLowerCase());
961
}
962
}
963
if (!extensionIds.size) {
964
return [];
965
}
966
const extensions = new Map<string, IGalleryExtension>();
967
await this.getDependenciesAndPackedExtensionsRecursively([...extensionIds], extensions, CancellationToken.None);
968
const publishers = new Map<string, IGalleryExtension>();
969
for (const [, extension] of extensions) {
970
if (extension.private || this.isPublisherTrusted(extension)) {
971
continue;
972
}
973
publishers.set(extension.publisherDisplayName, extension);
974
}
975
return [...publishers.values()];
976
}
977
978
private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {
979
if (toGet.length === 0) {
980
return;
981
}
982
983
const extensions = await this.extensionGalleryService.getExtensions(toGet.map(id => ({ id })), token);
984
for (let idx = 0; idx < extensions.length; idx++) {
985
const extension = extensions[idx];
986
result.set(extension.identifier.id.toLowerCase(), extension);
987
}
988
toGet = [];
989
for (const extension of extensions) {
990
if (isNonEmptyArray(extension.properties.dependencies)) {
991
for (const id of extension.properties.dependencies) {
992
if (!result.has(id.toLowerCase())) {
993
toGet.push(id);
994
}
995
}
996
}
997
if (isNonEmptyArray(extension.properties.extensionPack)) {
998
for (const id of extension.properties.extensionPack) {
999
if (!result.has(id.toLowerCase())) {
1000
toGet.push(id);
1001
}
1002
}
1003
}
1004
}
1005
return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, token);
1006
}
1007
1008
private async checkForWorkspaceTrust(manifest: IExtensionManifest, requireTrust: boolean): Promise<void> {
1009
if (requireTrust || this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(manifest) === false) {
1010
const buttons: WorkspaceTrustRequestButton[] = [];
1011
buttons.push({ label: localize('extensionInstallWorkspaceTrustButton', "Trust Workspace & Install"), type: 'ContinueWithTrust' });
1012
if (!requireTrust) {
1013
buttons.push({ label: localize('extensionInstallWorkspaceTrustContinueButton', "Install"), type: 'ContinueWithoutTrust' });
1014
}
1015
buttons.push({ label: localize('extensionInstallWorkspaceTrustManageButton', "Learn More"), type: 'Manage' });
1016
const trustState = await this.workspaceTrustRequestService.requestWorkspaceTrust({
1017
message: localize('extensionInstallWorkspaceTrustMessage', "Enabling this extension requires a trusted workspace."),
1018
buttons
1019
});
1020
1021
if (trustState === undefined) {
1022
throw new CancellationError();
1023
}
1024
}
1025
}
1026
1027
private async checkInstallingExtensionOnWeb(extension: IGalleryExtension, manifest: IExtensionManifest): Promise<void> {
1028
if (this.servers.length !== 1 || this.servers[0] !== this.extensionManagementServerService.webExtensionManagementServer) {
1029
return;
1030
}
1031
1032
const nonWebExtensions = [];
1033
if (manifest.extensionPack?.length) {
1034
const extensions = await this.extensionGalleryService.getExtensions(manifest.extensionPack.map(id => ({ id })), CancellationToken.None);
1035
for (const extension of extensions) {
1036
if (await this.servers[0].extensionManagementService.canInstall(extension) !== true) {
1037
nonWebExtensions.push(extension);
1038
}
1039
}
1040
if (nonWebExtensions.length && nonWebExtensions.length === extensions.length) {
1041
throw new ExtensionManagementError('Not supported in Web', ExtensionManagementErrorCode.Unsupported);
1042
}
1043
}
1044
1045
const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong);
1046
const virtualWorkspaceSupport = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(manifest);
1047
const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage(manifest.capabilities?.virtualWorkspaces);
1048
const hasLimitedSupport = virtualWorkspaceSupport === 'limited' || !!virtualWorkspaceSupportReason;
1049
1050
if (!nonWebExtensions.length && !hasLimitedSupport) {
1051
return;
1052
}
1053
1054
const limitedSupportMessage = localize('limited support', "'{0}' has limited functionality in {1}.", extension.displayName || extension.identifier.id, productName);
1055
let message: string;
1056
let buttons: IPromptButton<void>[] = [];
1057
let detail: string | undefined;
1058
1059
const installAnywayButton: IPromptButton<void> = {
1060
label: localize({ key: 'install anyways', comment: ['&& denotes a mnemonic'] }, "&&Install Anyway"),
1061
run: () => { }
1062
};
1063
1064
const showExtensionsButton: IPromptButton<void> = {
1065
label: localize({ key: 'showExtensions', comment: ['&& denotes a mnemonic'] }, "&&Show Extensions"),
1066
run: () => this.instantiationService.invokeFunction(accessor => accessor.get(ICommandService).executeCommand('extension.open', extension.identifier.id, 'extensionPack'))
1067
};
1068
1069
if (nonWebExtensions.length && hasLimitedSupport) {
1070
message = limitedSupportMessage;
1071
detail = `${virtualWorkspaceSupportReason ? `${virtualWorkspaceSupportReason}\n` : ''}${localize('non web extensions detail', "Contains extensions which are not supported.")}`;
1072
buttons = [
1073
installAnywayButton,
1074
showExtensionsButton
1075
];
1076
}
1077
1078
else if (hasLimitedSupport) {
1079
message = limitedSupportMessage;
1080
detail = virtualWorkspaceSupportReason || undefined;
1081
buttons = [installAnywayButton];
1082
}
1083
1084
else {
1085
message = localize('non web extensions', "'{0}' contains extensions which are not supported in {1}.", extension.displayName || extension.identifier.id, productName);
1086
buttons = [
1087
installAnywayButton,
1088
showExtensionsButton
1089
];
1090
}
1091
1092
await this.dialogService.prompt({
1093
type: Severity.Info,
1094
message,
1095
detail,
1096
buttons,
1097
cancelButton: {
1098
run: () => { throw new CancellationError(); }
1099
}
1100
});
1101
}
1102
1103
private _targetPlatformPromise: Promise<TargetPlatform> | undefined;
1104
getTargetPlatform(): Promise<TargetPlatform> {
1105
if (!this._targetPlatformPromise) {
1106
this._targetPlatformPromise = computeTargetPlatform(this.fileService, this.logService);
1107
}
1108
return this._targetPlatformPromise;
1109
}
1110
1111
async cleanUp(): Promise<void> {
1112
await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp()));
1113
}
1114
1115
toggleApplicationScope(extension: ILocalExtension, fromProfileLocation: URI): Promise<ILocalExtension> {
1116
const server = this.getServer(extension);
1117
if (server) {
1118
return server.extensionManagementService.toggleApplicationScope(extension, fromProfileLocation);
1119
}
1120
throw new Error('Not Supported');
1121
}
1122
1123
copyExtensions(from: URI, to: URI): Promise<void> {
1124
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
1125
throw new Error('Not Supported');
1126
}
1127
if (this.extensionManagementServerService.localExtensionManagementServer) {
1128
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.copyExtensions(from, to);
1129
}
1130
if (this.extensionManagementServerService.webExtensionManagementServer) {
1131
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.copyExtensions(from, to);
1132
}
1133
return Promise.resolve();
1134
}
1135
1136
registerParticipant() { throw new Error('Not Supported'); }
1137
installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]> { throw new Error('Not Supported'); }
1138
1139
isPublisherTrusted(extension: IGalleryExtension): boolean {
1140
const publisher = extension.publisher.toLowerCase();
1141
if (this.defaultTrustedPublishers.includes(publisher) || this.defaultTrustedPublishers.includes(extension.publisherDisplayName.toLowerCase())) {
1142
return true;
1143
}
1144
1145
// Check if the extension is allowed by publisher or extension id
1146
if (this.allowedExtensionsService.allowedExtensionsConfigValue && this.allowedExtensionsService.isAllowed(extension)) {
1147
return true;
1148
}
1149
1150
return this.isPublisherUserTrusted(publisher);
1151
}
1152
1153
private isPublisherUserTrusted(publisher: string): boolean {
1154
const trustedPublishers = this.getTrustedPublishersFromStorage();
1155
return !!trustedPublishers[publisher];
1156
}
1157
1158
getTrustedPublishers(): IPublisherInfo[] {
1159
const trustedPublishers = this.getTrustedPublishersFromStorage();
1160
return Object.keys(trustedPublishers).map(publisher => trustedPublishers[publisher]);
1161
}
1162
1163
trustPublishers(...publishers: IPublisherInfo[]): void {
1164
const trustedPublishers = this.getTrustedPublishersFromStorage();
1165
for (const publisher of publishers) {
1166
trustedPublishers[publisher.publisher.toLowerCase()] = publisher;
1167
}
1168
this.storageService.store(TrustedPublishersStorageKey, JSON.stringify(trustedPublishers), StorageScope.APPLICATION, StorageTarget.USER);
1169
}
1170
1171
untrustPublishers(...publishers: string[]): void {
1172
const trustedPublishers = this.getTrustedPublishersFromStorage();
1173
for (const publisher of publishers) {
1174
delete trustedPublishers[publisher.toLowerCase()];
1175
}
1176
this.storageService.store(TrustedPublishersStorageKey, JSON.stringify(trustedPublishers), StorageScope.APPLICATION, StorageTarget.USER);
1177
}
1178
1179
private getTrustedPublishersFromStorage(): IStringDictionary<IPublisherInfo> {
1180
const trustedPublishers = this.storageService.getObject<IStringDictionary<IPublisherInfo>>(TrustedPublishersStorageKey, StorageScope.APPLICATION, {});
1181
if (Array.isArray(trustedPublishers)) {
1182
this.storageService.remove(TrustedPublishersStorageKey, StorageScope.APPLICATION);
1183
return {};
1184
}
1185
return Object.keys(trustedPublishers).reduce<IStringDictionary<IPublisherInfo>>((result, publisher) => {
1186
result[publisher.toLowerCase()] = trustedPublishers[publisher];
1187
return result;
1188
}, {});
1189
}
1190
}
1191
1192
class WorkspaceExtensionsManagementService extends Disposable {
1193
1194
private static readonly WORKSPACE_EXTENSIONS_KEY = 'workspaceExtensions.locations';
1195
1196
private readonly _onDidChangeInvalidExtensions = this._register(new Emitter<ILocalExtension[]>());
1197
readonly onDidChangeInvalidExtensions = this._onDidChangeInvalidExtensions.event;
1198
1199
private readonly extensions: ILocalExtension[] = [];
1200
private readonly initializePromise: Promise<void>;
1201
1202
private readonly invalidExtensionWatchers = this._register(new DisposableStore());
1203
1204
constructor(
1205
@IFileService private readonly fileService: IFileService,
1206
@ILogService private readonly logService: ILogService,
1207
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
1208
@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,
1209
@IStorageService private readonly storageService: IStorageService,
1210
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
1211
@ITelemetryService private readonly telemetryService: ITelemetryService,
1212
) {
1213
super();
1214
1215
this._register(Event.debounce<FileChangesEvent, FileChangesEvent[]>(this.fileService.onDidFilesChange, (last, e) => {
1216
(last = last ?? []).push(e);
1217
return last;
1218
}, 1000)(events => {
1219
const changedInvalidExtensions = this.extensions.filter(extension => !extension.isValid && events.some(e => e.affects(extension.location)));
1220
if (changedInvalidExtensions.length) {
1221
this.checkExtensionsValidity(changedInvalidExtensions);
1222
}
1223
}));
1224
1225
this.initializePromise = this.initialize();
1226
}
1227
1228
private async initialize(): Promise<void> {
1229
const existingLocations = this.getInstalledWorkspaceExtensionsLocations();
1230
if (!existingLocations.length) {
1231
return;
1232
}
1233
1234
await Promise.allSettled(existingLocations.map(async location => {
1235
if (!this.workspaceService.isInsideWorkspace(location)) {
1236
this.logService.info(`Removing the workspace extension ${location.toString()} as it is not inside the workspace`);
1237
return;
1238
}
1239
if (!(await this.fileService.exists(location))) {
1240
this.logService.info(`Removing the workspace extension ${location.toString()} as it does not exist`);
1241
return;
1242
}
1243
try {
1244
const extension = await this.scanWorkspaceExtension(location);
1245
if (extension) {
1246
this.extensions.push(extension);
1247
} else {
1248
this.logService.info(`Skipping workspace extension ${location.toString()} as it does not exist`);
1249
}
1250
} catch (error) {
1251
this.logService.error('Skipping the workspace extension', location.toString(), error);
1252
}
1253
}));
1254
1255
this.saveWorkspaceExtensions();
1256
}
1257
1258
private watchInvalidExtensions(): void {
1259
this.invalidExtensionWatchers.clear();
1260
for (const extension of this.extensions) {
1261
if (!extension.isValid) {
1262
this.invalidExtensionWatchers.add(this.fileService.watch(extension.location));
1263
}
1264
}
1265
}
1266
1267
private async checkExtensionsValidity(extensions: ILocalExtension[]): Promise<void> {
1268
const validExtensions: ILocalExtension[] = [];
1269
await Promise.all(extensions.map(async extension => {
1270
const newExtension = await this.scanWorkspaceExtension(extension.location);
1271
if (newExtension?.isValid) {
1272
validExtensions.push(newExtension);
1273
}
1274
}));
1275
1276
let changed = false;
1277
for (const extension of validExtensions) {
1278
const index = this.extensions.findIndex(e => this.uriIdentityService.extUri.isEqual(e.location, extension.location));
1279
if (index !== -1) {
1280
changed = true;
1281
this.extensions.splice(index, 1, extension);
1282
}
1283
}
1284
1285
if (changed) {
1286
this.saveWorkspaceExtensions();
1287
this._onDidChangeInvalidExtensions.fire(validExtensions);
1288
}
1289
}
1290
1291
async getInstalled(includeInvalid: boolean): Promise<ILocalExtension[]> {
1292
await this.initializePromise;
1293
return this.extensions.filter(e => includeInvalid || e.isValid);
1294
}
1295
1296
async install(extension: IResourceExtension): Promise<ILocalExtension> {
1297
await this.initializePromise;
1298
1299
const workspaceExtension = await this.scanWorkspaceExtension(extension.location);
1300
if (!workspaceExtension) {
1301
throw new Error('Cannot install the extension as it does not exist.');
1302
}
1303
1304
const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier));
1305
if (existingExtensionIndex === -1) {
1306
this.extensions.push(workspaceExtension);
1307
} else {
1308
this.extensions.splice(existingExtensionIndex, 1, workspaceExtension);
1309
}
1310
1311
this.saveWorkspaceExtensions();
1312
this.telemetryService.publicLog2<{}, {
1313
owner: 'sandy081';
1314
comment: 'Install workspace extension';
1315
}>('workspaceextension:install');
1316
1317
return workspaceExtension;
1318
}
1319
1320
async uninstall(extension: ILocalExtension): Promise<void> {
1321
await this.initializePromise;
1322
1323
const existingExtensionIndex = this.extensions.findIndex(e => areSameExtensions(e.identifier, extension.identifier));
1324
if (existingExtensionIndex !== -1) {
1325
this.extensions.splice(existingExtensionIndex, 1);
1326
this.saveWorkspaceExtensions();
1327
}
1328
1329
this.telemetryService.publicLog2<{}, {
1330
owner: 'sandy081';
1331
comment: 'Uninstall workspace extension';
1332
}>('workspaceextension:uninstall');
1333
}
1334
1335
getInstalledWorkspaceExtensionsLocations(): URI[] {
1336
const locations: URI[] = [];
1337
try {
1338
const parsed = JSON.parse(this.storageService.get(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, StorageScope.WORKSPACE, '[]'));
1339
if (Array.isArray(locations)) {
1340
for (const location of parsed) {
1341
if (isString(location)) {
1342
if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) {
1343
locations.push(this.workspaceService.getWorkspace().folders[0].toResource(location));
1344
} else {
1345
this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${location}`);
1346
}
1347
} else {
1348
locations.push(URI.revive(location));
1349
}
1350
}
1351
} else {
1352
this.logService.warn(`Invalid value for 'extensions' in workspace storage: ${locations}`);
1353
}
1354
} catch (error) {
1355
this.logService.warn(`Error parsing workspace extensions locations: ${getErrorMessage(error)}`);
1356
}
1357
return locations;
1358
}
1359
1360
private saveWorkspaceExtensions(): void {
1361
const locations = this.extensions.map(extension => extension.location);
1362
if (this.workspaceService.getWorkbenchState() === WorkbenchState.FOLDER) {
1363
this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY,
1364
JSON.stringify(coalesce(locations
1365
.map(location => this.uriIdentityService.extUri.relativePath(this.workspaceService.getWorkspace().folders[0].uri, location)))),
1366
StorageScope.WORKSPACE, StorageTarget.MACHINE);
1367
} else {
1368
this.storageService.store(WorkspaceExtensionsManagementService.WORKSPACE_EXTENSIONS_KEY, JSON.stringify(locations), StorageScope.WORKSPACE, StorageTarget.MACHINE);
1369
}
1370
this.watchInvalidExtensions();
1371
}
1372
1373
async scanWorkspaceExtension(location: URI): Promise<ILocalExtension | null> {
1374
const scannedExtension = await this.extensionsScannerService.scanExistingExtension(location, ExtensionType.User, { includeInvalid: true });
1375
return scannedExtension ? this.toLocalWorkspaceExtension(scannedExtension) : null;
1376
}
1377
1378
async toLocalWorkspaceExtension(extension: IScannedExtension): Promise<ILocalExtension> {
1379
const stat = await this.fileService.resolve(extension.location);
1380
let readmeUrl: URI | undefined;
1381
let changelogUrl: URI | undefined;
1382
if (stat.children) {
1383
readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;
1384
changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;
1385
}
1386
const validations: [Severity, string][] = [...extension.validations];
1387
let isValid = extension.isValid;
1388
if (extension.manifest.main) {
1389
if (!(await this.fileService.exists(this.uriIdentityService.extUri.joinPath(extension.location, extension.manifest.main)))) {
1390
isValid = false;
1391
validations.push([Severity.Error, localize('main.notFound', "Cannot activate because {0} not found", extension.manifest.main)]);
1392
}
1393
}
1394
return {
1395
identifier: extension.identifier,
1396
type: extension.type,
1397
isBuiltin: extension.isBuiltin || !!extension.metadata?.isBuiltin,
1398
location: extension.location,
1399
manifest: extension.manifest,
1400
targetPlatform: extension.targetPlatform,
1401
validations,
1402
isValid,
1403
readmeUrl,
1404
changelogUrl,
1405
publisherDisplayName: extension.metadata?.publisherDisplayName,
1406
publisherId: extension.metadata?.publisherId || null,
1407
isApplicationScoped: !!extension.metadata?.isApplicationScoped,
1408
isMachineScoped: !!extension.metadata?.isMachineScoped,
1409
isPreReleaseVersion: !!extension.metadata?.isPreReleaseVersion,
1410
hasPreReleaseVersion: !!extension.metadata?.hasPreReleaseVersion,
1411
preRelease: !!extension.metadata?.preRelease,
1412
installedTimestamp: extension.metadata?.installedTimestamp,
1413
updated: !!extension.metadata?.updated,
1414
pinned: !!extension.metadata?.pinned,
1415
isWorkspaceScoped: true,
1416
private: false,
1417
source: 'resource',
1418
size: extension.metadata?.size ?? 0,
1419
};
1420
}
1421
}
1422
1423