#15756 prompt users to migrate from old extension to main prerelease extension
This commit is contained in:
parent
4aad18d229
commit
503a9bcd16
|
@ -5,6 +5,7 @@
|
|||
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { getOrDefault } from 'vs/base/common/objects';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
|
@ -440,7 +441,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
|||
|
||||
interface IRawExtensionsControlManifest {
|
||||
malicious: string[];
|
||||
slow: string[];
|
||||
unsupported: IStringDictionary<boolean | { preReleaseExtension: { id: string, displayName: string } }>;
|
||||
}
|
||||
|
||||
abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
@ -958,14 +959,23 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
|
||||
const result = await asJson<IRawExtensionsControlManifest>(context);
|
||||
const malicious: IExtensionIdentifier[] = [];
|
||||
const unsupportedPreReleaseExtensions: IStringDictionary<{ id: string, displayName: string }> = {};
|
||||
|
||||
if (result) {
|
||||
for (const id of result.malicious) {
|
||||
malicious.push({ id });
|
||||
}
|
||||
if (result.unsupported) {
|
||||
for (const extensionId of Object.keys(result.unsupported)) {
|
||||
const value = result.unsupported[extensionId];
|
||||
if (!isBoolean(value)) {
|
||||
unsupportedPreReleaseExtensions[extensionId.toLowerCase()] = value.preReleaseExtension;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { malicious };
|
||||
return { malicious, unsupportedPreReleaseExtensions };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
|
@ -312,6 +313,7 @@ export const enum StatisticType {
|
|||
|
||||
export interface IExtensionsControlManifest {
|
||||
malicious: IExtensionIdentifier[];
|
||||
unsupportedPreReleaseExtensions?: IStringDictionary<{ id: string, displayName: string }>;
|
||||
}
|
||||
|
||||
export const enum InstallOperation {
|
||||
|
|
|
@ -74,6 +74,7 @@ import { ExtensionsCompletionItemsProvider } from 'vs/workbench/contrib/extensio
|
|||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
||||
import { UnsupportedPreReleaseExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/unsupportedPreReleaseExtensionsChecker';
|
||||
|
||||
// Singletons
|
||||
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
|
||||
|
@ -1461,6 +1462,7 @@ const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Workbench
|
|||
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting);
|
||||
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored);
|
||||
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
|
||||
workbenchRegistry.registerWorkbenchContribution(UnsupportedPreReleaseExtensionsChecker, LifecyclePhase.Eventually);
|
||||
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
|
||||
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting);
|
||||
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
|||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
|
||||
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
|
@ -1125,6 +1125,40 @@ export class SwitchToReleasedVersionAction extends ExtensionAction {
|
|||
}
|
||||
}
|
||||
|
||||
export class SwitchUnsupportedExtensionToPreReleaseExtensionAction extends Action {
|
||||
|
||||
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`;
|
||||
|
||||
constructor(
|
||||
private readonly local: ILocalExtension,
|
||||
private readonly gallery: IGalleryExtension,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IWorkbenchExtensionEnablementService private readonly workbenchExtensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super('workbench.extensions.action.switchUnsupportedExtensionToPreReleaseExtension', localize('switchUnsupportedExtensionToPreReleaseExtension', "Switch to '{0}' Pre-Release version", gallery.displayName), SwitchUnsupportedExtensionToPreReleaseExtensionAction.Class);
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
await Promise.all([
|
||||
this.extensionManagementService.uninstall(this.local),
|
||||
this.extensionManagementService.installFromGallery(this.gallery, { installPreReleaseVersion: true, isMachineScoped: this.local.isMachineScoped })
|
||||
.then(local => this.workbenchExtensionEnablementService.setEnablement([this.local], EnablementState.EnabledGlobally)),
|
||||
]);
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
localize('SwitchToAnotherReleaseExtension.successReload', "Please reload {0} to complete switching to the '{1}' extension.", this.productService.nameLong, this.gallery.displayName),
|
||||
[{
|
||||
label: localize('reloadNow', "Reload Now"),
|
||||
run: () => this.hostService.reload()
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallAnotherVersionAction extends ExtensionAction {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.install.anotherVersion';
|
||||
|
|
|
@ -14,10 +14,10 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import {
|
||||
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
|
||||
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier
|
||||
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier, IExtensionsControlManifest
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
|
@ -418,28 +418,32 @@ class Extensions extends Disposable {
|
|||
return this.local;
|
||||
}
|
||||
|
||||
async syncLocalWithGalleryExtension(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): Promise<boolean> {
|
||||
async syncLocalWithGalleryExtension(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): Promise<boolean> {
|
||||
const extension = this.getInstalledExtensionMatchingGallery(gallery);
|
||||
if (!extension) {
|
||||
if (!extension?.local) {
|
||||
return false;
|
||||
}
|
||||
if (maliciousExtensionSet.has(extension.identifier.id)) {
|
||||
extension.isMalicious = true;
|
||||
|
||||
let hasChanged: boolean = false;
|
||||
|
||||
const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier));
|
||||
if (extension.isMalicious !== isMalicious) {
|
||||
extension.isMalicious = isMalicious;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
const compatible = await this.getCompatibleExtension(gallery, !!extension.local?.isPreReleaseVersion);
|
||||
if (!compatible) {
|
||||
return false;
|
||||
}
|
||||
// Sync the local extension with gallery extension if local extension doesnot has metadata
|
||||
if (extension.local) {
|
||||
if (compatible) {
|
||||
const local = extension.local.identifier.uuid ? extension.local : await this.server.extensionManagementService.updateMetadata(extension.local, { id: compatible.identifier.uuid, publisherDisplayName: compatible.publisherDisplayName, publisherId: compatible.publisherId });
|
||||
extension.local = local;
|
||||
extension.gallery = compatible;
|
||||
this._onChange.fire({ extension });
|
||||
return true;
|
||||
hasChanged = true;
|
||||
}
|
||||
return false;
|
||||
|
||||
if (hasChanged) {
|
||||
this._onChange.fire({ extension });
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
private async getCompatibleExtension(extensionOrIdentifier: IGalleryExtension | IExtensionIdentifier, includePreRelease: boolean): Promise<IGalleryExtension | null> {
|
||||
|
@ -749,11 +753,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
options.text = options.text ? this.resolveQueryText(options.text) : options.text;
|
||||
options.includePreRelease = isUndefined(options.includePreRelease) ? this.preferPreReleases : options.includePreRelease;
|
||||
|
||||
const report = await this.extensionManagementService.getExtensionsControlManifest();
|
||||
const maliciousSet = getMaliciousExtensionsSet(report);
|
||||
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
|
||||
try {
|
||||
const result = await this.galleryService.query(options, token);
|
||||
return mapPager(result, gallery => this.fromGallery(gallery, maliciousSet));
|
||||
return mapPager(result, gallery => this.fromGallery(gallery, extensionsControlManifest));
|
||||
} catch (error) {
|
||||
if (/No extension gallery service configured/.test(error.message)) {
|
||||
return Promise.resolve(singlePagePager([]));
|
||||
|
@ -890,11 +893,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
return extension || extensions[0];
|
||||
}
|
||||
|
||||
private fromGallery(gallery: IGalleryExtension, maliciousExtensionSet: Set<string>): IExtension {
|
||||
private fromGallery(gallery: IGalleryExtension, extensionsControlManifest: IExtensionsControlManifest): IExtension {
|
||||
Promise.all([
|
||||
this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false),
|
||||
this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false),
|
||||
this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, maliciousExtensionSet) : Promise.resolve(false)
|
||||
this.localExtensions ? this.localExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false),
|
||||
this.remoteExtensions ? this.remoteExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false),
|
||||
this.webExtensions ? this.webExtensions.syncLocalWithGalleryExtension(gallery, extensionsControlManifest) : Promise.resolve(false)
|
||||
])
|
||||
.then(result => {
|
||||
if (result[0] || result[1] || result[2]) {
|
||||
|
@ -907,7 +910,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
return installed;
|
||||
}
|
||||
const extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), undefined, undefined, gallery);
|
||||
if (maliciousExtensionSet.has(extension.identifier.id)) {
|
||||
if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) {
|
||||
extension.isMalicious = true;
|
||||
}
|
||||
return extension;
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { SwitchUnsupportedExtensionToPreReleaseExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
|
||||
export class UnsupportedPreReleaseExtensionsChecker implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
this.notifyUnsupportedPreReleaseExtensions();
|
||||
}
|
||||
|
||||
private async notifyUnsupportedPreReleaseExtensions(): Promise<void> {
|
||||
const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest();
|
||||
if (!extensionsControlManifest.unsupportedPreReleaseExtensions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const unsupportedLocalExtensionsWithIdentifiers: [ILocalExtension, IExtensionIdentifier][] = [];
|
||||
for (const extension of installed) {
|
||||
const preReleaseExtension = extensionsControlManifest.unsupportedPreReleaseExtensions[extension.identifier.id.toLowerCase()];
|
||||
if (preReleaseExtension) {
|
||||
unsupportedLocalExtensionsWithIdentifiers.push([extension, { id: preReleaseExtension.id }]);
|
||||
}
|
||||
}
|
||||
if (!unsupportedLocalExtensionsWithIdentifiers.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsupportedPreReleaseExtensions: [ILocalExtension, IGalleryExtension][] = [];
|
||||
const galleryExensions = await this.extensionGalleryService.getExtensions(unsupportedLocalExtensionsWithIdentifiers.map(([, identifier]) => identifier), true, CancellationToken.None);
|
||||
for (const gallery of galleryExensions) {
|
||||
const unsupportedLocalExtension = unsupportedLocalExtensionsWithIdentifiers.find(([, identifier]) => areSameExtensions(identifier, gallery.identifier));
|
||||
if (unsupportedLocalExtension) {
|
||||
unsupportedPreReleaseExtensions.push([unsupportedLocalExtension[0], gallery]);
|
||||
}
|
||||
}
|
||||
if (!unsupportedPreReleaseExtensions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (unsupportedPreReleaseExtensions.length === 1) {
|
||||
const [local, gallery] = unsupportedPreReleaseExtensions[0];
|
||||
const action = this.instantiationService.createInstance(SwitchUnsupportedExtensionToPreReleaseExtensionAction, unsupportedPreReleaseExtensions[0][0], unsupportedPreReleaseExtensions[0][1]);
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: localize('unsupported prerelease message', "'{0}' extension is now part of the '{1}' extension as a pre-release version and it is no longer supported. Would you like to switch to '{2}' extension?", local.manifest.displayName || local.identifier.id, gallery.displayName, gallery.displayName),
|
||||
actions: {
|
||||
primary: [action]
|
||||
},
|
||||
sticky: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue