mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 7eaf220cafb9d9e901370ffce02229171cbf3ea6
This commit is contained in:
committed by
Anthony Dresser
parent
39d9eed585
commit
a63578e6f7
@@ -12,13 +12,15 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionType, IExtension, isAuthenticaionProviderExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { StorageManager } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
const SOURCE = 'IWorkbenchExtensionEnablementService';
|
||||
|
||||
@@ -40,6 +42,8 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IUserDataAutoSyncService private readonly userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
|
||||
) {
|
||||
super();
|
||||
this.storageManger = this._register(new StorageManager(storageService));
|
||||
@@ -66,7 +70,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||
}
|
||||
|
||||
canChangeEnablement(extension: IExtension): boolean {
|
||||
if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
|
||||
try {
|
||||
this.throwErrorIfCannotChangeEnablement(extension);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
const enablementState = this.getEnablementState(extension);
|
||||
@@ -76,11 +82,47 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||
return true;
|
||||
}
|
||||
|
||||
private throwErrorIfCannotChangeEnablement(extension: IExtension): void {
|
||||
if (isLanguagePackExtension(extension.manifest)) {
|
||||
throw new Error(localize('cannot disable language pack extension', "Cannot disable {0} extension because it contributes language packs.", extension.manifest.displayName || extension.identifier.id));
|
||||
}
|
||||
|
||||
if (this.userDataAutoSyncService.isEnabled() && this.userDataSyncAccountService.account &&
|
||||
isAuthenticaionProviderExtension(extension.manifest) && extension.manifest.contributes!.authentication!.some(a => a.id === this.userDataSyncAccountService.account!.authenticationProviderId)) {
|
||||
throw new Error(localize('cannot disable auth extension', "Cannot disable {0} extension because Settings Sync depends on it.", extension.manifest.displayName || extension.identifier.id));
|
||||
}
|
||||
}
|
||||
|
||||
canChangeWorkspaceEnablement(extension: IExtension): boolean {
|
||||
if (!this.canChangeEnablement(extension)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private throwErrorIfCannotChangeWorkspaceEnablement(extension: IExtension): void {
|
||||
if (!this.hasWorkspace) {
|
||||
throw new Error(localize('noWorkspace', "No workspace."));
|
||||
}
|
||||
if (isAuthenticaionProviderExtension(extension.manifest)) {
|
||||
throw new Error(localize('cannot disable auth extension in workspace', "Cannot disable {0} extension in workspace because it contributes authentication providers", extension.manifest.displayName || extension.identifier.id));
|
||||
}
|
||||
}
|
||||
|
||||
async setEnablement(extensions: IExtension[], newState: EnablementState): Promise<boolean[]> {
|
||||
|
||||
const workspace = newState === EnablementState.DisabledWorkspace || newState === EnablementState.EnabledWorkspace;
|
||||
if (workspace && !this.hasWorkspace) {
|
||||
return Promise.reject(new Error(localize('noWorkspace', "No workspace.")));
|
||||
for (const extension of extensions) {
|
||||
if (workspace) {
|
||||
this.throwErrorIfCannotChangeWorkspaceEnablement(extension);
|
||||
} else {
|
||||
this.throwErrorIfCannotChangeEnablement(extension);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await Promise.all(extensions.map(e => this._setEnablement(e, newState)));
|
||||
@@ -316,4 +358,4 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService, true);
|
||||
registerSingleton(IWorkbenchExtensionEnablementService, ExtensionEnablementService);
|
||||
|
||||
@@ -58,6 +58,11 @@ export interface IWorkbenchExtensionEnablementService {
|
||||
*/
|
||||
canChangeEnablement(extension: IExtension): boolean;
|
||||
|
||||
/**
|
||||
* Returns `true` if the enablement can be changed.
|
||||
*/
|
||||
canChangeWorkspaceEnablement(extension: IExtension): boolean;
|
||||
|
||||
/**
|
||||
* Returns `true` if the given extension identifier is enabled.
|
||||
*/
|
||||
@@ -127,10 +132,11 @@ export interface IExtensionRecommendationsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getAllRecommendationsWithReason(): IStringDictionary<IExtensionRecommendationReson>;
|
||||
getFileBasedRecommendations(): IExtensionRecommendation[];
|
||||
getImportantRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getConfigBasedRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getFileBasedRecommendations(): IExtensionRecommendation[];
|
||||
getExeBasedRecommendations(exe?: string): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }>;
|
||||
getConfigBasedRecommendations(): Promise<{ important: IExtensionRecommendation[], others: IExtensionRecommendation[] }>;
|
||||
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
|
||||
getKeymapRecommendations(): IExtensionRecommendation[];
|
||||
|
||||
@@ -147,6 +153,7 @@ export interface IWebExtensionsScannerService {
|
||||
readonly _serviceBrand: undefined;
|
||||
scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]>;
|
||||
scanAndTranslateExtensions(type?: ExtensionType): Promise<ITranslatedScannedExtension[]>;
|
||||
canAddExtension(galleryExtension: IGalleryExtension): Promise<boolean>;
|
||||
addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension>;
|
||||
removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
@@ -15,6 +14,10 @@ import { isWeb } from 'vs/base/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService';
|
||||
import { IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class ExtensionManagementServerService implements IExtensionManagementServerService {
|
||||
|
||||
@@ -27,11 +30,14 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
const remoteAgentConnection = remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection!.getChannel<IChannel>('extensions'));
|
||||
const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel<IChannel>('extensions'), galleryService, configurationService, productService);
|
||||
this.remoteExtensionManagementServer = {
|
||||
id: 'remote',
|
||||
extensionManagementService,
|
||||
|
||||
@@ -189,6 +189,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return Promise.reject('No Servers');
|
||||
}
|
||||
|
||||
async canInstall(gallery: IGalleryExtension): Promise<boolean> {
|
||||
for (const server of this.servers) {
|
||||
if (await server.extensionManagementService.canInstall(gallery)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
|
||||
|
||||
// Only local server, install without any checks
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionManagementService, IGalleryExtension, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { canExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
|
||||
export class WebRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IExtensionManagementService {
|
||||
|
||||
constructor(
|
||||
channel: IChannel,
|
||||
@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
@IProductService protected readonly productService: IProductService
|
||||
) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
async canInstall(extension: IGalleryExtension): Promise<boolean> {
|
||||
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
|
||||
return !!manifest && canExecuteOnWorkspace(manifest, this.productService, this.configurationService);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata, InstallOperation, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class WebExtensionManagementService extends Disposable implements IExtensionManagementService {
|
||||
|
||||
@@ -40,16 +41,25 @@ export class WebExtensionManagementService extends Disposable implements IExtens
|
||||
return Promise.all(extensions.map(e => this.toLocalExtension(e)));
|
||||
}
|
||||
|
||||
async canInstall(gallery: IGalleryExtension): Promise<boolean> {
|
||||
return this.webExtensionsScannerService.canAddExtension(gallery);
|
||||
}
|
||||
|
||||
async installFromGallery(gallery: IGalleryExtension): Promise<ILocalExtension> {
|
||||
if (!(await this.canInstall(gallery))) {
|
||||
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", gallery.displayName || gallery.name));
|
||||
error.name = INSTALL_ERROR_NOT_SUPPORTED;
|
||||
throw error;
|
||||
}
|
||||
this.logService.info('Installing extension:', gallery.identifier.id);
|
||||
this._onInstallExtension.fire({ identifier: gallery.identifier, gallery });
|
||||
try {
|
||||
const existingExtension = await this.getUserExtension(gallery.identifier);
|
||||
const scannedExtension = await this.webExtensionsScannerService.addExtension(gallery);
|
||||
const local = await this.toLocalExtension(scannedExtension);
|
||||
if (existingExtension && existingExtension.manifest.version !== gallery.version) {
|
||||
await this.webExtensionsScannerService.removeExtension(existingExtension.identifier, existingExtension.manifest.version);
|
||||
}
|
||||
const scannedExtension = await this.webExtensionsScannerService.addExtension(gallery);
|
||||
const local = await this.toLocalExtension(scannedExtension);
|
||||
this._onDidInstallExtension.fire({ local, identifier: gallery.identifier, operation: InstallOperation.Install, gallery });
|
||||
return local;
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as semver from 'semver-umd';
|
||||
import { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType, IExtensionIdentifier, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
@@ -17,18 +16,19 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { asText, isSuccess, IRequestService } from 'vs/platform/request/common/request';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IGalleryExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStaticExtension } from 'vs/workbench/workbench.web.api';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
interface IUserExtension {
|
||||
identifier: IExtensionIdentifier;
|
||||
version: string;
|
||||
uri: URI;
|
||||
location: URI;
|
||||
readmeUri?: URI;
|
||||
changelogUri?: URI;
|
||||
packageNLSUri?: URI;
|
||||
@@ -37,16 +37,12 @@ interface IUserExtension {
|
||||
interface IStoredUserExtension {
|
||||
identifier: IExtensionIdentifier;
|
||||
version: string;
|
||||
uri: UriComponents;
|
||||
location: UriComponents;
|
||||
readmeUri?: UriComponents;
|
||||
changelogUri?: UriComponents;
|
||||
packageNLSUri?: UriComponents;
|
||||
}
|
||||
|
||||
const AssetTypeWebResource = 'Microsoft.VisualStudio.Code.WebResources';
|
||||
|
||||
function getExtensionLocation(assetUri: URI): URI { return joinPath(assetUri, AssetTypeWebResource, 'extension'); }
|
||||
|
||||
export class WebExtensionsScannerService extends Disposable implements IWebExtensionsScannerService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
@@ -87,29 +83,45 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
*/
|
||||
private getStaticExtensions(builtin: boolean): IScannedExtension[] {
|
||||
const staticExtensions = this.environmentService.options && Array.isArray(this.environmentService.options.staticExtensions) ? this.environmentService.options.staticExtensions : [];
|
||||
return (
|
||||
staticExtensions
|
||||
.filter(e => Boolean(e.isBuiltin) === builtin)
|
||||
.map(e => ({
|
||||
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
|
||||
location: e.extensionLocation,
|
||||
type: e.isBuiltin ? ExtensionType.System : ExtensionType.User,
|
||||
packageJSON: e.packageJSON,
|
||||
}))
|
||||
);
|
||||
const result: IScannedExtension[] = [];
|
||||
for (const e of staticExtensions) {
|
||||
if (Boolean(e.isBuiltin) === builtin) {
|
||||
const scannedExtension = this.parseStaticExtension(e, builtin);
|
||||
if (scannedExtension) {
|
||||
result.push(scannedExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async readDefaultExtensions(): Promise<IScannedExtension[]> {
|
||||
const defaultUserWebExtensions = await this.readDefaultUserWebExtensions();
|
||||
const extensions = defaultUserWebExtensions.map(e => ({
|
||||
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
|
||||
location: e.extensionLocation,
|
||||
type: ExtensionType.User,
|
||||
packageJSON: e.packageJSON,
|
||||
}));
|
||||
const extensions: IScannedExtension[] = [];
|
||||
for (const e of defaultUserWebExtensions) {
|
||||
const scannedExtension = this.parseStaticExtension(e, false);
|
||||
if (scannedExtension) {
|
||||
extensions.push(scannedExtension);
|
||||
}
|
||||
}
|
||||
return extensions.concat(this.getStaticExtensions(false));
|
||||
}
|
||||
|
||||
private parseStaticExtension(e: IStaticExtension, builtin: boolean): IScannedExtension | null {
|
||||
try {
|
||||
return {
|
||||
identifier: { id: getGalleryExtensionId(e.packageJSON.publisher, e.packageJSON.name) },
|
||||
location: e.extensionLocation,
|
||||
type: builtin ? ExtensionType.System : ExtensionType.User,
|
||||
packageJSON: e.packageJSON,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logService.error(`Error while parsing extension ${e.extensionLocation.toString()}`);
|
||||
this.logService.error(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async readDefaultUserWebExtensions(): Promise<IStaticExtension[]> {
|
||||
const result: IStaticExtension[] = [];
|
||||
const defaultUserWebExtensions = this.configurationService.getValue<{ location: string }[]>('_extensions.defaultUserWebExtensions') || [];
|
||||
@@ -190,12 +202,19 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
};
|
||||
}
|
||||
|
||||
async canAddExtension(galleryExtension: IGalleryExtension): Promise<boolean> {
|
||||
return !!galleryExtension.properties.webExtension && !!galleryExtension.webResource;
|
||||
}
|
||||
|
||||
async addExtension(galleryExtension: IGalleryExtension): Promise<IScannedExtension> {
|
||||
if (!galleryExtension.assetTypes.some(type => type.startsWith(AssetTypeWebResource))) {
|
||||
throw new Error(`Missing ${AssetTypeWebResource} asset type`);
|
||||
if (!(await this.canAddExtension(galleryExtension))) {
|
||||
const error = new Error(localize('cannot be installed', "Cannot install '{0}' because this extension is not a web extension.", galleryExtension.displayName || galleryExtension.name));
|
||||
error.name = INSTALL_ERROR_NOT_SUPPORTED;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const packageNLSUri = joinPath(getExtensionLocation(galleryExtension.assetUri), 'package.nls.json');
|
||||
const extensionLocation = galleryExtension.webResource!;
|
||||
const packageNLSUri = joinPath(extensionLocation, 'package.nls.json');
|
||||
const context = await this.requestService.request({ type: 'GET', url: packageNLSUri.toString() }, CancellationToken.None);
|
||||
const packageNLSExists = isSuccess(context);
|
||||
|
||||
@@ -203,7 +222,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
const userExtension: IUserExtension = {
|
||||
identifier: galleryExtension.identifier,
|
||||
version: galleryExtension.version,
|
||||
uri: galleryExtension.assetUri,
|
||||
location: extensionLocation,
|
||||
readmeUri: galleryExtension.assets.readme ? URI.parse(galleryExtension.assets.readme.uri) : undefined,
|
||||
changelogUri: galleryExtension.assets.changelog ? URI.parse(galleryExtension.assets.changelog.uri) : undefined,
|
||||
packageNLSUri: packageNLSExists ? packageNLSUri : undefined
|
||||
@@ -225,6 +244,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
}
|
||||
|
||||
private async scanUserExtensions(): Promise<IScannedExtension[]> {
|
||||
const semver = await import('semver-umd');
|
||||
let userExtensions = await this.readUserExtensions();
|
||||
const byExtension: IUserExtension[][] = groupByExtension(userExtensions, e => e.identifier);
|
||||
userExtensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]);
|
||||
@@ -243,14 +263,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
}
|
||||
|
||||
private async toScannedExtension(userExtension: IUserExtension): Promise<IScannedExtension | null> {
|
||||
const context = await this.requestService.request({ type: 'GET', url: joinPath(userExtension.uri, 'Microsoft.VisualStudio.Code.Manifest').toString() }, CancellationToken.None);
|
||||
const context = await this.requestService.request({ type: 'GET', url: joinPath(userExtension.location, 'package.json').toString() }, CancellationToken.None);
|
||||
if (isSuccess(context)) {
|
||||
const content = await asText(context);
|
||||
if (content) {
|
||||
const packageJSON = JSON.parse(content);
|
||||
return {
|
||||
identifier: userExtension.identifier,
|
||||
location: getExtensionLocation(userExtension.uri),
|
||||
location: userExtension.location,
|
||||
packageJSON,
|
||||
type: ExtensionType.User,
|
||||
readmeUrl: userExtension.readmeUri,
|
||||
@@ -269,11 +289,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
return this.userExtensionsResourceLimiter.queue(async () => {
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.extensionsResource!);
|
||||
const storedUserExtensions: IStoredUserExtension[] = JSON.parse(content.value.toString());
|
||||
const storedUserExtensions: IStoredUserExtension[] = this.parseExtensions(content.value.toString());
|
||||
return storedUserExtensions.map(e => ({
|
||||
identifier: e.identifier,
|
||||
version: e.version,
|
||||
uri: URI.revive(e.uri),
|
||||
location: URI.revive(e.location),
|
||||
readmeUri: URI.revive(e.readmeUri),
|
||||
changelogUri: URI.revive(e.changelogUri),
|
||||
packageNLSUri: URI.revive(e.packageNLSUri),
|
||||
@@ -291,7 +311,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
const storedUserExtensions: IStoredUserExtension[] = userExtensions.map(e => ({
|
||||
identifier: e.identifier,
|
||||
version: e.version,
|
||||
uri: e.uri.toJSON(),
|
||||
location: e.location.toJSON(),
|
||||
readmeUri: e.readmeUri?.toJSON(),
|
||||
changelogUri: e.changelogUri?.toJSON(),
|
||||
packageNLSUri: e.packageNLSUri?.toJSON(),
|
||||
@@ -302,6 +322,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
|
||||
});
|
||||
}
|
||||
|
||||
private parseExtensions(content: string): IStoredUserExtension[] {
|
||||
const storedUserExtensions: (IStoredUserExtension & { uri?: UriComponents })[] = JSON.parse(content.toString());
|
||||
return storedUserExtensions.map(e => {
|
||||
const location = e.uri ? joinPath(URI.revive(e.uri), 'Microsoft.VisualStudio.Code.WebResources', 'extension') : e.location;
|
||||
return { ...e, location };
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IWebExtensionsScannerService, WebExtensionsScannerService);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
@@ -13,12 +12,13 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { RemoteExtensionManagementChannelClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { NativeRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IExtension } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class ExtensionManagementServerService implements IExtensionManagementServerService {
|
||||
|
||||
@@ -32,18 +32,18 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
|
||||
constructor(
|
||||
@ISharedProcessService sharedProcessService: ISharedProcessService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IProductService productService: IProductService,
|
||||
@ILogService logService: ILogService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ILogService logService: ILogService,
|
||||
) {
|
||||
const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions'));
|
||||
|
||||
this._localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") };
|
||||
const remoteAgentConnection = remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
const extensionManagementService = new RemoteExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer.extensionManagementService, galleryService, logService, configurationService, productService);
|
||||
const extensionManagementService = new NativeRemoteExtensionManagementService(remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer, logService, galleryService, configurationService, productService);
|
||||
this.remoteExtensionManagementServer = {
|
||||
id: 'remote',
|
||||
extensionManagementService,
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { prefersExecuteOnUI } from 'vs/workbench/services/extensions/common/extensionsUtil';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService';
|
||||
import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
|
||||
export class NativeRemoteExtensionManagementService extends WebRemoteExtensionManagementService implements IExtensionManagementService {
|
||||
|
||||
private readonly localExtensionManagementService: IExtensionManagementService;
|
||||
|
||||
constructor(
|
||||
channel: IChannel,
|
||||
localExtensionManagementServer: IExtensionManagementServer,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
super(channel, galleryService, configurationService, productService);
|
||||
this.localExtensionManagementService = localExtensionManagementServer.extensionManagementService;
|
||||
}
|
||||
|
||||
async install(vsix: URI): Promise<ILocalExtension> {
|
||||
const local = await super.install(vsix);
|
||||
await this.installUIDependenciesAndPackedExtensions(local);
|
||||
return local;
|
||||
}
|
||||
|
||||
async installFromGallery(extension: IGalleryExtension): Promise<ILocalExtension> {
|
||||
const local = await this.doInstallFromGallery(extension);
|
||||
await this.installUIDependenciesAndPackedExtensions(local);
|
||||
return local;
|
||||
}
|
||||
|
||||
private async doInstallFromGallery(extension: IGalleryExtension): Promise<ILocalExtension> {
|
||||
if (this.configurationService.getValue<boolean>('remote.downloadExtensionsLocally')) {
|
||||
this.logService.trace(`Download '${extension.identifier.id}' extension locally and install`);
|
||||
return this.downloadCompatibleAndInstall(extension);
|
||||
}
|
||||
try {
|
||||
const local = await super.installFromGallery(extension);
|
||||
return local;
|
||||
} catch (error) {
|
||||
try {
|
||||
this.logService.error(`Error while installing '${extension.identifier.id}' extension in the remote server.`, toErrorMessage(error));
|
||||
this.logService.info(`Trying to download '${extension.identifier.id}' extension locally and install`);
|
||||
const local = await this.downloadCompatibleAndInstall(extension);
|
||||
this.logService.info(`Successfully installed '${extension.identifier.id}' extension`);
|
||||
return local;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadCompatibleAndInstall(extension: IGalleryExtension): Promise<ILocalExtension> {
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
const compatible = await this.galleryService.getCompatibleExtension(extension);
|
||||
if (!compatible) {
|
||||
return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version)));
|
||||
}
|
||||
const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None);
|
||||
if (manifest) {
|
||||
const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(manifest, CancellationToken.None);
|
||||
await Promise.all(workspaceExtensions.map(e => this.downloadAndInstall(e, installed)));
|
||||
}
|
||||
return this.downloadAndInstall(extension, installed);
|
||||
}
|
||||
|
||||
private async downloadAndInstall(extension: IGalleryExtension, installed: ILocalExtension[]): Promise<ILocalExtension> {
|
||||
const location = joinPath(URI.file(tmpdir()), generateUuid());
|
||||
await this.galleryService.download(extension, location, installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install);
|
||||
return super.install(location);
|
||||
}
|
||||
|
||||
private async installUIDependenciesAndPackedExtensions(local: ILocalExtension): Promise<void> {
|
||||
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(local.manifest, CancellationToken.None);
|
||||
const installed = await this.localExtensionManagementService.getInstalled();
|
||||
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
|
||||
await Promise.all(toInstall.map(d => this.localExtensionManagementService.installFromGallery(d)));
|
||||
}
|
||||
|
||||
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const result = new Map<string, IGalleryExtension>();
|
||||
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
|
||||
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token);
|
||||
return [...result.values()];
|
||||
}
|
||||
|
||||
private async getAllWorkspaceDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const result = new Map<string, IGalleryExtension>();
|
||||
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
|
||||
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token);
|
||||
return [...result.values()];
|
||||
}
|
||||
|
||||
private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, uiExtension: boolean, token: CancellationToken): Promise<void> {
|
||||
if (toGet.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const extensions = (await this.galleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
|
||||
const manifests = await Promise.all(extensions.map(e => this.galleryService.getManifest(e, token)));
|
||||
const extensionsManifests: IExtensionManifest[] = [];
|
||||
for (let idx = 0; idx < extensions.length; idx++) {
|
||||
const extension = extensions[idx];
|
||||
const manifest = manifests[idx];
|
||||
if (manifest && prefersExecuteOnUI(manifest, this.productService, this.configurationService) === uiExtension) {
|
||||
result.set(extension.identifier.id.toLowerCase(), extension);
|
||||
extensionsManifests.push(manifest);
|
||||
}
|
||||
}
|
||||
toGet = [];
|
||||
for (const extensionManifest of extensionsManifests) {
|
||||
if (isNonEmptyArray(extensionManifest.extensionDependencies)) {
|
||||
for (const id of extensionManifest.extensionDependencies) {
|
||||
if (!result.has(id.toLowerCase())) {
|
||||
toGet.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isNonEmptyArray(extensionManifest.extensionPack)) {
|
||||
for (const id of extensionManifest.extensionPack) {
|
||||
if (!result.has(id.toLowerCase())) {
|
||||
toGet.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { productService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
function createStorageService(instantiationService: TestInstantiationService): IStorageService {
|
||||
let service = instantiationService.get(IStorageService);
|
||||
@@ -51,7 +53,9 @@ export class TestExtensionEnablementService extends ExtensionEnablementService {
|
||||
extensionManagementService,
|
||||
instantiationService.get(IConfigurationService),
|
||||
extensionManagementServerService,
|
||||
productService
|
||||
productService,
|
||||
instantiationService.get(IUserDataAutoSyncService) || instantiationService.stub(IUserDataAutoSyncService, <Partial<IUserDataAutoSyncService>>{ isEnabled() { return false; } }),
|
||||
instantiationService.get(IUserDataSyncAccountService) || instantiationService.stub(IUserDataSyncAccountService, UserDataSyncAccountService)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -371,6 +375,48 @@ suite('ExtensionEnablementService Test', () => {
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false);
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return true for auth extension', () => {
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return true for auth extension when user data sync account does not depends on it', () => {
|
||||
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
|
||||
account: { authenticationProviderId: 'b' }
|
||||
});
|
||||
testObject = new TestExtensionEnablementService(instantiationService);
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return true for auth extension when user data sync account depends on it but auto sync is off', () => {
|
||||
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
|
||||
account: { authenticationProviderId: 'a' }
|
||||
});
|
||||
testObject = new TestExtensionEnablementService(instantiationService);
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true);
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return false for auth extension and user data sync account depends on it and auto sync is on', () => {
|
||||
instantiationService.stub(IUserDataAutoSyncService, <Partial<IUserDataAutoSyncService>>{ isEnabled() { return true; } });
|
||||
instantiationService.stub(IUserDataSyncAccountService, <Partial<IUserDataSyncAccountService>>{
|
||||
account: { authenticationProviderId: 'a' }
|
||||
});
|
||||
testObject = new TestExtensionEnablementService(instantiationService);
|
||||
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false);
|
||||
});
|
||||
|
||||
test('test canChangeWorkspaceEnablement return true', () => {
|
||||
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), true);
|
||||
});
|
||||
|
||||
test('test canChangeWorkspaceEnablement return false if there is no workspace', () => {
|
||||
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
|
||||
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a')), false);
|
||||
});
|
||||
|
||||
test('test canChangeWorkspaceEnablement return false for auth extension', () => {
|
||||
assert.equal(testObject.canChangeWorkspaceEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false);
|
||||
});
|
||||
|
||||
test('test canChangeEnablement return false when extensions are disabled in environment', () => {
|
||||
instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService);
|
||||
testObject = new TestExtensionEnablementService(instantiationService);
|
||||
|
||||
Reference in New Issue
Block a user