This commit is contained in:
Sandeep Somavarapu
2023-06-14 10:29:39 +02:00
committed by GitHub
parent 04f7885c54
commit f3aa9a201b
12 changed files with 213 additions and 94 deletions
@@ -16,9 +16,9 @@ import * as nls from 'vs/nls';
import {
ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, ILocalExtension, InstallOperation,
IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode,
InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService
InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -26,6 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
export type ExtensionVerificationStatus = boolean | string;
export type InstallableExtension = { readonly manifest: IExtensionManifest; extension: IGalleryExtension | URI; options: InstallOptions & InstallVSIXOptions };
export type InstallExtensionTaskOptions = InstallOptions & InstallVSIXOptions & { readonly profileLocation: URI };
export interface IInstallExtensionTask {
@@ -93,19 +94,54 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
try {
if (!this.galleryService.isEnabled()) {
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
const results = await this.installGalleryExtensions([{ extension, options }]);
const result = results.find(({ identifier }) => areSameExtensions(identifier, extension.identifier));
if (result?.local) {
return result?.local;
}
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options.installGivenVersion, !!options.installPreReleaseVersion);
return await this.installExtension(compatible.manifest, compatible.extension, options);
if (result?.error) {
throw result.error;
}
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extension.identifier.id}`));
} catch (error) {
reportTelemetry(this.telemetryService, 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(extension), error });
this.logService.error(`Failed to install extension.`, extension.identifier.id);
this.logService.error(error);
throw toExtensionManagementError(error);
}
}
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
if (!this.galleryService.isEnabled()) {
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
}
const results: InstallExtensionResult[] = [];
const installableExtensions: InstallableExtension[] = [];
await Promise.allSettled(extensions.map(async ({ extension, options }) => {
try {
const compatible = await this.checkAndGetCompatibleVersion(extension, !!options?.installGivenVersion, !!options?.installPreReleaseVersion);
installableExtensions.push({ ...compatible, options });
} catch (error) {
results.push({ identifier: extension.identifier, operation: InstallOperation.Install, source: extension, error });
}
}));
if (installableExtensions.length) {
results.push(...await this.installExtensions(installableExtensions));
}
for (const result of results) {
if (result.error) {
this.logService.error(`Failed to install extension.`, result.identifier.id);
this.logService.error(result.error);
if (result.source && !URI.isUri(result.source)) {
reportTelemetry(this.telemetryService, 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(result.source), error: result.error });
}
}
}
return results;
}
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.uninstallExtension(extension, options);
@@ -126,7 +162,21 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
this.participants.push(participant);
}
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> {
const results: InstallExtensionResult[] = [];
await Promise.allSettled(extensions.map(async e => {
try {
const result = await this.installExtension(e);
results.push(...result);
} catch (error) {
results.push({ identifier: { id: getGalleryExtensionId(e.manifest.publisher, e.manifest.name) }, operation: InstallOperation.Install, source: e.extension, error });
}
}));
this._onDidInstallExtensions.fire(results);
return results;
}
private async installExtension({ manifest, extension, options }: InstallableExtension): Promise<InstallExtensionResult[]> {
const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest);
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
@@ -142,7 +192,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension));
if (installingExtension) {
this.logService.info('Extensions is already requested to install', extension.identifier.id);
return installingExtension.task.waitUntilTaskIsFinished();
await installingExtension.task.waitUntilTaskIsFinished();
return [];
}
}
@@ -272,8 +323,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id));
this._onDidInstallExtensions.fire(installResults);
return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;
return installResults;
} catch (error) {
@@ -299,8 +349,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
}
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation })));
throw error;
return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error }));
} finally {
// Finally, remove all the tasks from the cache
for (const { task } of allInstallExtensionTasks) {
@@ -678,7 +727,7 @@ export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | stri
}, new Error(''));
}
function toExtensionManagementError(error: Error): ExtensionManagementError {
export function toExtensionManagementError(error: Error): ExtensionManagementError {
if (error instanceof ExtensionManagementError) {
return error;
}
@@ -377,6 +377,7 @@ export interface InstallExtensionResult {
readonly operation: InstallOperation;
readonly source?: URI | IGalleryExtension;
readonly local?: ILocalExtension;
readonly error?: Error;
readonly context?: IStringDictionary<any>;
readonly profileLocation?: URI;
readonly applicationScoped?: boolean;
@@ -450,6 +451,8 @@ export interface IExtensionManagementParticipant {
postUninstall(local: ILocalExtension, options: UninstallOptions, token: CancellationToken): Promise<void>;
}
export type InstallExtensionInfo = { readonly extension: IGalleryExtension; readonly options: InstallOptions };
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export interface IExtensionManagementService {
readonly _serviceBrand: undefined;
@@ -466,6 +469,7 @@ export interface IExtensionManagementService {
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
canInstall(extension: IGalleryExtension): Promise<boolean>;
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]>;
installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;
installExtensionsFromProfile(extensions: IExtensionIdentifier[], fromProfileLocation: URI, toProfileLocation: URI): Promise<ILocalExtension[]>;
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
@@ -9,7 +9,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI;
@@ -128,6 +128,10 @@ export class ExtensionManagementChannel implements IServerChannel {
case 'installFromGallery': {
return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer));
}
case 'installGalleryExtensions': {
const arg: InstallExtensionInfo[] = args[0];
return this.service.installGalleryExtensions(arg.map(({ extension, options }) => ({ extension, options: transformIncomingOptions(options, uriTransformer) ?? {} })));
}
case 'uninstall': {
return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer));
}
@@ -250,6 +254,11 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
}
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
const results = await this.channel.call<InstallExtensionResult[]>('installGalleryExtensions', [extensions]);
return results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }));
}
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
return Promise.resolve(this.channel.call<void>('uninstall', [extension!, options]));
}
@@ -25,7 +25,7 @@ import { extract, ExtractError, IFile, zip } from 'vs/base/node/zip';
import * as nls from 'vs/nls';
import { IDownloadService } from 'vs/platform/download/common/download';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { AbstractExtensionManagementService, AbstractExtensionTask, ExtensionVerificationStatus, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import {
ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOperation,
Metadata, InstallVSIXOptions
@@ -148,11 +148,19 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
try {
const manifest = await getManifest(path.resolve(location.fsPath));
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) {
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", getGalleryExtensionId(manifest.publisher, manifest.name), this.productService.version));
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version));
}
return await this.installExtension(manifest, location, options);
const result = await this.installExtensions([{ manifest, extension: location, options }]);
if (result[0]?.local) {
return result[0]?.local;
}
if (result[0]?.error) {
throw result[0].error;
}
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${extensionId}`));
} finally {
await cleanup();
}
@@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension, DISABLED_EXTENSIONS_STORAGE_PATH, EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, EXTENSION_INSTALL_SYNC_CONTEXT } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, ExtensionManagementError, ExtensionManagementErrorCode, IGalleryExtension, DISABLED_EXTENSIONS_STORAGE_PATH, EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@@ -405,7 +405,8 @@ export class LocalExtensionsProvider {
async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[], profile: IUserDataProfile): Promise<ISyncExtension[]> {
const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.Extensions, profile);
const extensionsToInstall: [ISyncExtension, IGalleryExtension][] = [];
const extensionsToInstall: InstallExtensionInfo[] = [];
const syncExtensionsToInstall = new Map<string, ISyncExtension>();
const removeFromSkipped: IExtensionIdentifier[] = [];
const addToSkipped: ISyncExtension[] = [];
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource);
@@ -473,7 +474,17 @@ export class LocalExtensionsProvider {
|| (version && installedExtension.manifest.version !== version) // Install if the extension version has changed
) {
if (await this.extensionManagementService.canInstall(extension)) {
extensionsToInstall.push([e, extension]);
extensionsToInstall.push({
extension, options: {
isMachineScoped: false /* set isMachineScoped value to prevent install and sync dialog in web */,
donotIncludePackAndDependencies: true,
installGivenVersion: e.pinned && !!e.version,
installPreReleaseVersion: e.preRelease,
profileLocation: profile.extensionsResource,
context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true, [EXTENSION_INSTALL_SYNC_CONTEXT]: true }
}
});
syncExtensionsToInstall.set(extension.identifier.id.toLowerCase(), e);
} else {
this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because it cannot be installed.`, extension.displayName || extension.identifier.id);
addToSkipped.push(e);
@@ -504,26 +515,22 @@ export class LocalExtensionsProvider {
}
// 3. Install extensions at the end
for (const [e, extension] of extensionsToInstall) {
try {
this.logService.trace(`${syncResourceLogLabel}: Installing extension...`, extension.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension, {
isMachineScoped: false /* set isMachineScoped value to prevent install and sync dialog in web */,
donotIncludePackAndDependencies: true,
installGivenVersion: e.pinned && !!e.version,
installPreReleaseVersion: e.preRelease,
profileLocation: profile.extensionsResource,
context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true, [EXTENSION_INSTALL_SYNC_CONTEXT]: true }
});
this.logService.info(`${syncResourceLogLabel}: Installed extension.`, extension.identifier.id, extension.version);
removeFromSkipped.push(extension.identifier);
} catch (error) {
addToSkipped.push(e);
const results = await this.extensionManagementService.installGalleryExtensions(extensionsToInstall);
for (const { identifier, local, error, source } of results) {
const gallery = source as IGalleryExtension;
if (local) {
this.logService.info(`${syncResourceLogLabel}: Installed extension.`, identifier.id, gallery.version);
removeFromSkipped.push(identifier);
} else {
const e = syncExtensionsToInstall.get(identifier.id.toLowerCase());
if (e) {
addToSkipped.push(e);
this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension`, gallery.displayName || gallery.identifier.id);
}
if (error instanceof ExtensionManagementError && [ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatiblePreRelease, ExtensionManagementErrorCode.IncompatibleTargetPlatform].includes(error.code)) {
this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id);
} else {
this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, gallery.displayName || gallery.identifier.id);
} else if (error) {
this.logService.error(error);
this.logService.info(`${syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
}
}
}
@@ -267,17 +267,17 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec
return createCancelablePromise<RecommendationsNotificationResult>(async token => {
let accepted = false;
const choices: (IPromptChoice | IPromptChoiceWithMenu)[] = [];
const installExtensions = async (isMachineScoped?: boolean) => {
const installExtensions = async (isMachineScoped: boolean) => {
this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue));
onDidInstallRecommendedExtensions(extensions);
await Promises.settled<any>([
Promises.settled(extensions.map(extension => this.extensionsWorkbenchService.open(extension, { pinned: true }))),
this.extensionManagementService.installExtensions(extensions.map(e => e.gallery!), { isMachineScoped })
this.extensionManagementService.installGalleryExtensions(extensions.map(e => ({ extension: e.gallery!, options: { isMachineScoped } })))
]);
};
choices.push({
label: localize('install', "Install"),
run: () => installExtensions(),
run: () => installExtensions(false),
menu: this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions) ? [{
label: localize('install and do no sync', "Install (Do not sync)"),
run: () => installExtensions(true)
@@ -1447,7 +1447,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView imple
async installWorkspaceRecommendations(): Promise<void> {
const installableRecommendations = await this.getInstallableWorkspaceRecommendations();
if (installableRecommendations.length) {
await this.extensionManagementService.installExtensions(installableRecommendations.map(i => i.gallery!));
await this.extensionManagementService.installGalleryExtensions(installableRecommendations.map(i => ({ extension: i.gallery!, options: {} })));
} else {
this.notificationService.notify({
severity: Severity.Info,
@@ -58,9 +58,7 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten
installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallVSIXOptions): Promise<ILocalExtension>;
installFromLocation(location: URI): Promise<ILocalExtension>;
installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise<ILocalExtension[]>;
updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions): Promise<ILocalExtension>;
getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null;
}
export const enum EnablementState {
@@ -5,7 +5,7 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT
ILocalExtension, IGalleryExtension, IExtensionIdentifier, IExtensionsControlManifest, IExtensionGalleryService, InstallOptions, UninstallOptions, InstallVSIXOptions, InstallExtensionResult, ExtensionManagementError, ExtensionManagementErrorCode, Metadata, InstallOperation, EXTENSION_INSTALL_SYNC_CONTEXT, InstallExtensionInfo
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { DidChangeProfileForServerEvent, DidUninstallExtensionOnServerEvent, IExtensionManagementServer, IExtensionManagementServerService, InstallExtensionOnServerEvent, IWorkbenchExtensionManagementService, UninstallExtensionOnServerEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest, getWorkspaceSupportTypeMessage, TargetPlatform } from 'vs/platform/extensions/common/extensions';
@@ -296,15 +296,56 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);
}
async installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions): Promise<ILocalExtension[]> {
if (!installOptions) {
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped(extensions);
installOptions = { isMachineScoped, isBuiltin: false };
}
return Promises.settled(extensions.map(extension => this.installFromGallery(extension, installOptions)));
async installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
const results = new Map<string, InstallExtensionResult>();
const extensionsByServer = new Map<IExtensionManagementServer, InstallExtensionInfo[]>();
await Promise.all(extensions.map(async ({ extension, options }) => {
try {
const servers = await this.validateAndGetExtensionManagementServersToInstall(extension, options);
if (!options.isMachineScoped && this.isExtensionsSyncEnabled()) {
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) && (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(extension))) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
}
for (const server of servers) {
let exensions = extensionsByServer.get(server);
if (!exensions) {
extensionsByServer.set(server, exensions = []);
}
exensions.push({ extension, options });
}
} catch (error) {
results.set(extension.identifier.id.toLowerCase(), { identifier: extension.identifier, source: extension, error, operation: InstallOperation.Install });
}
}));
await Promise.all([...extensionsByServer.entries()].map(async ([server, extensions]) => {
const serverResults = await server.extensionManagementService.installGalleryExtensions(extensions);
for (const result of serverResults) {
results.set(result.identifier.id.toLowerCase(), result);
}
}));
return [...results.values()];
}
async installFromGallery(gallery: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
const servers = await this.validateAndGetExtensionManagementServersToInstall(gallery, installOptions);
if (!installOptions || isUndefined(installOptions.isMachineScoped)) {
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
installOptions = { ...(installOptions || {}), isMachineScoped };
}
if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) && (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery))) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
}
return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);
}
private async validateAndGetExtensionManagementServersToInstall(gallery: IGalleryExtension, installOptions?: InstallOptions): Promise<IExtensionManagementServer[]> {
const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None);
if (!manifest) {
@@ -323,32 +364,24 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
}
}
if (servers.length) {
if (!installOptions || isUndefined(installOptions.isMachineScoped)) {
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
installOptions = { ...(installOptions || {}), isMachineScoped };
}
if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer) && (await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.canInstall(gallery))) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
}
if (!installOptions.context?.[EXTENSION_INSTALL_SYNC_CONTEXT]) {
await this.checkForWorkspaceTrust(manifest);
}
if (!installOptions.donotIncludePackAndDependencies) {
await this.checkInstallingExtensionOnWeb(gallery, manifest);
}
return Promises.settled(servers.map(server => server.extensionManagementService.installFromGallery(gallery, installOptions))).then(([local]) => local);
if (!servers.length) {
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));
error.name = ExtensionManagementErrorCode.Unsupported;
throw error;
}
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));
error.name = ExtensionManagementErrorCode.Unsupported;
return Promise.reject(error);
if (!installOptions?.context?.[EXTENSION_INSTALL_SYNC_CONTEXT]) {
await this.checkForWorkspaceTrust(manifest);
}
if (!installOptions?.donotIncludePackAndDependencies) {
await this.checkInstallingExtensionOnWeb(gallery, manifest);
}
return servers;
}
getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null {
private getExtensionManagementServerToInstall(manifest: IExtensionManifest): IExtensionManagementServer | null {
// Only local server
if (this.servers.length === 1 && this.extensionManagementServerService.localExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer;
@@ -11,7 +11,7 @@ import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionM
import { IProfileAwareExtensionManagementService, IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, toExtensionManagementError, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -105,7 +105,14 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
if (!manifest) {
throw new Error(`Cannot find packageJSON from the location ${location.toString()}`);
}
return this.installExtension(manifest, location, options);
const result = await this.installExtensions([{ manifest, extension: location, options }]);
if (result[0]?.local) {
return result[0]?.local;
}
if (result[0]?.error) {
throw result[0].error;
}
throw toExtensionManagementError(new Error(`Unknown error while installing extension ${getGalleryExtensionId(manifest.publisher, manifest.name)}`));
}
installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension> {
@@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
@@ -143,27 +143,34 @@ export class ExtensionsResource implements IProfileResource {
}
}
if (extensionsToInstall.length) {
this.logService.info(`Importing Profile (${profile.name}): Started installing extensions.`);
const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, version: e.version, hasPreRelease: e.version ? undefined : e.preRelease })), CancellationToken.None);
const installExtensionInfos: InstallExtensionInfo[] = [];
await Promise.all(extensionsToInstall.map(async e => {
const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier));
if (!extension) {
return;
}
if (await this.extensionManagementService.canInstall(extension)) {
this.logService.trace(`Importing Profile (${profile.name}): Installing extension...`, extension.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension, {
isMachineScoped: false,/* set isMachineScoped value to prevent install and sync dialog in web */
donotIncludePackAndDependencies: true,
installGivenVersion: !!e.version,
installPreReleaseVersion: e.preRelease,
profileLocation: profile.extensionsResource,
context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true }
installExtensionInfos.push({
extension,
options: {
isMachineScoped: false,/* set isMachineScoped value to prevent install and sync dialog in web */
donotIncludePackAndDependencies: true,
installGivenVersion: !!e.version,
installPreReleaseVersion: e.preRelease,
profileLocation: profile.extensionsResource,
context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true }
}
});
this.logService.info(`Importing Profile (${profile.name}): Installed extension...`, extension.identifier.id, extension.version);
} else {
this.logService.info(`Importing Profile (${profile.name}): Skipped installing extension because it cannot be installed.`, extension.identifier.id);
}
}));
if (installExtensionInfos.length) {
await this.extensionManagementService.installGalleryExtensions(installExtensionInfos);
}
this.logService.info(`Importing Profile (${profile.name}): Finished installing extensions.`);
}
if (extensionsToUninstall.length) {
await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e)));
@@ -161,8 +161,8 @@ import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService';
import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
import { EnablementState, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata, InstallExtensionResult, InstallExtensionInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Codicon } from 'vs/base/common/codicons';
import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover';
import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner';
@@ -1993,13 +1993,10 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens
installFromLocation(location: URI): Promise<ILocalExtension> {
throw new Error('Method not implemented.');
}
installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions | undefined): Promise<ILocalExtension[]> {
installGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<InstallExtensionResult[]> {
throw new Error('Method not implemented.');
}
async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions | undefined): Promise<ILocalExtension> { return extension; }
getExtensionManagementServerToInstall(manifest: Readonly<IRelaxedExtensionManifest>): IExtensionManagementServer | null {
throw new Error('Method not implemented.');
}
zip(extension: ILocalExtension): Promise<URI> {
throw new Error('Method not implemented.');
}