mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge vscode source through 1.62 release (#19981)
* Build breaks 1 * Build breaks * Build breaks * Build breaks * More build breaks * Build breaks (#2512) * Runtime breaks * Build breaks * Fix dialog location break * Update typescript * Fix ASAR break issue * Unit test breaks * Update distro * Fix breaks in ADO builds (#2513) * Bump to node 16 * Fix hygiene errors * Bump distro * Remove reference to node type * Delete vscode specific extension * Bump to node 16 in CI yaml * Skip integration tests in CI builds (while fixing) * yarn.lock update * Bump moment dependency in remote yarn * Fix drop-down chevron style * Bump to node 16 * Remove playwrite from ci.yaml * Skip building build scripts in hygine check
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { canceled, getErrorMessage } from 'vs/base/common/errors';
|
||||
@@ -13,18 +14,14 @@ import { URI } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
import {
|
||||
DidUninstallExtensionEvent, ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOperation, InstallOptions,
|
||||
InstallVSIXOptions, INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS, IReportedExtension, StatisticType, UninstallOptions
|
||||
InstallVSIXOptions, IReportedExtension, StatisticType, UninstallOptions, TargetPlatform, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const INSTALL_ERROR_VALIDATING = 'validating';
|
||||
export const ERROR_UNKNOWN = 'unknown';
|
||||
export const INSTALL_ERROR_LOCAL = 'local';
|
||||
|
||||
export interface IInstallExtensionTask {
|
||||
readonly identifier: IExtensionIdentifier;
|
||||
readonly source: IGalleryExtension | URI;
|
||||
@@ -70,6 +67,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
|
||||
@ITelemetryService protected readonly telemetryService: ITelemetryService,
|
||||
@ILogService protected readonly logService: ILogService,
|
||||
@IProductService protected readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
this._register(toDisposable(() => {
|
||||
@@ -80,30 +78,43 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
}));
|
||||
}
|
||||
|
||||
async canInstall(extension: IGalleryExtension): Promise<boolean> {
|
||||
const currentTargetPlatform = await this.getTargetPlatform();
|
||||
return extension.allTargetPlatforms.some(targetPlatform => isTargetPlatformCompatible(targetPlatform, extension.allTargetPlatforms, currentTargetPlatform));
|
||||
}
|
||||
|
||||
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
|
||||
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
|
||||
}
|
||||
|
||||
if (!await this.canInstall(extension)) {
|
||||
const targetPlatform = await this.getTargetPlatform();
|
||||
const error = new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.Incompatible);
|
||||
this.logService.error(`Cannot install extension.`, extension.identifier.id, error.message);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
extension = await this.checkAndGetCompatibleVersion(extension);
|
||||
extension = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion);
|
||||
} catch (error) {
|
||||
this.logService.error(getErrorMessage(error));
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!await this.canInstall(extension)) {
|
||||
const error = new ExtensionManagementError(`Not supported`, INSTALL_ERROR_VALIDATING);
|
||||
this.logService.error(`Canno install extension as it is not supported.`, extension.identifier.id, error.message);
|
||||
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
|
||||
this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, INSTALL_ERROR_VALIDATING);
|
||||
this.logService.error(`Failed to install extension:`, extension.identifier.id, error.message);
|
||||
if (manifest.version !== extension.version) {
|
||||
const error = new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
|
||||
this.logService.error(error.message);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
@@ -187,9 +198,20 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
|
||||
this.logService.error(error);
|
||||
throw error;
|
||||
// Installing through VSIX
|
||||
if (URI.isUri(installExtensionTask.source)) {
|
||||
// Ignore installing dependencies and packs
|
||||
if (isNonEmptyArray(manifest.extensionDependencies)) {
|
||||
this.logService.warn(`Cannot install dependencies of extension:`, installExtensionTask.identifier.id, error.message);
|
||||
}
|
||||
if (isNonEmptyArray(manifest.extensionPack)) {
|
||||
this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message);
|
||||
}
|
||||
} else {
|
||||
this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id);
|
||||
this.logService.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +291,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source })));
|
||||
|
||||
if (error instanceof Error) {
|
||||
error.name = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
|
||||
error.name = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ExtensionManagementErrorCode.Internal;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -311,7 +333,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
|
||||
const allDependenciesAndPacks: { gallery: IGalleryExtension, manifest: IExtensionManifest }[] = [];
|
||||
const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise<void> => {
|
||||
const dependenciesAndPackExtensions: string[] = manifest.extensionDependencies || [];
|
||||
const dependecies: string[] = manifest.extensionDependencies || [];
|
||||
const dependenciesAndPackExtensions = [...dependecies];
|
||||
if (manifest.extensionPack) {
|
||||
const existing = getOnlyNewlyAddedFromExtensionPack ? installed.find(e => areSameExtensions(e.identifier, extensionIdentifier)) : undefined;
|
||||
for (const extension of manifest.extensionPack) {
|
||||
@@ -334,14 +357,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
if (identifiers.find(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
|
||||
continue;
|
||||
}
|
||||
const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension);
|
||||
if (!await this.canInstall(compatibleExtension)) {
|
||||
this.logService.info('Skipping the extension as it cannot be installed', compatibleExtension.identifier.id);
|
||||
const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier));
|
||||
if (!isDependency && !await this.canInstall(galleryExtension)) {
|
||||
this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id);
|
||||
continue;
|
||||
}
|
||||
const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension, true);
|
||||
const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, INSTALL_ERROR_VALIDATING);
|
||||
throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
|
||||
}
|
||||
allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest });
|
||||
await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest);
|
||||
@@ -355,14 +379,28 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier)));
|
||||
}
|
||||
|
||||
private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise<IGalleryExtension> {
|
||||
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise<IGalleryExtension> {
|
||||
if (await this.isMalicious(extension)) {
|
||||
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), INSTALL_ERROR_MALICIOUS);
|
||||
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious);
|
||||
}
|
||||
|
||||
const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
|
||||
const compatibleExtension = await this.getCompatibleVersion(extension, fetchCompatibleVersion);
|
||||
if (!compatibleExtension) {
|
||||
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE);
|
||||
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
|
||||
return compatibleExtension;
|
||||
}
|
||||
|
||||
protected async getCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise<IGalleryExtension | null> {
|
||||
const targetPlatform = await this.getTargetPlatform();
|
||||
let compatibleExtension: IGalleryExtension | null = null;
|
||||
if (await this.galleryService.isExtensionCompatible(extension, targetPlatform)) {
|
||||
compatibleExtension = extension;
|
||||
}
|
||||
|
||||
if (!compatibleExtension && fetchCompatibleVersion) {
|
||||
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, targetPlatform);
|
||||
}
|
||||
|
||||
return compatibleExtension;
|
||||
@@ -437,7 +475,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
}
|
||||
postUninstallExtension(task.extension);
|
||||
} catch (e) {
|
||||
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
|
||||
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ExtensionManagementErrorCode.Internal);
|
||||
postUninstallExtension(task.extension, error);
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -446,7 +484,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
}));
|
||||
|
||||
} catch (e) {
|
||||
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ERROR_UNKNOWN);
|
||||
const error = e instanceof ExtensionManagementError ? e : new ExtensionManagementError(getErrorMessage(e), ExtensionManagementErrorCode.Internal);
|
||||
for (const task of allTasks) {
|
||||
// cancel the tasks
|
||||
try { task.cancel(); } catch (error) { /* ignore */ }
|
||||
@@ -557,11 +595,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
||||
}
|
||||
}
|
||||
|
||||
abstract getTargetPlatform(): Promise<TargetPlatform>;
|
||||
abstract zip(extension: ILocalExtension): Promise<URI>;
|
||||
abstract unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
|
||||
abstract getManifest(vsix: URI): Promise<IExtensionManifest>;
|
||||
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
|
||||
abstract canInstall(extension: IGalleryExtension): Promise<boolean>;
|
||||
abstract getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
|
||||
|
||||
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
|
||||
@@ -582,7 +620,7 @@ export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | stri
|
||||
}
|
||||
|
||||
export function reportTelemetry(telemetryService: ITelemetryService, eventName: string, extensionData: any, duration?: number, error?: Error): void {
|
||||
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : undefined;
|
||||
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ExtensionManagementErrorCode.Internal : undefined;
|
||||
/* __GDPR__
|
||||
"extensionGallery:install" : {
|
||||
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
|
||||
@@ -3,27 +3,31 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { getOrDefault } from 'vs/base/common/objects';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { isWeb, platform } from 'vs/base/common/platform';
|
||||
import { arch } from 'vs/base/common/process';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}} Add import
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { DefaultIconPath, IExtensionGalleryService, IExtensionIdentifier, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, ITranslation, SortBy, SortOrder, StatisticType, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { adoptToGalleryExtensionId, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { DefaultIconPath, getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionIdentifierWithVersion, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IReportedExtension, isIExtensionIdentifier, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, TargetPlatform, toTargetPlatform, WEB_EXTENSION_TAG } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionsPolicy, ExtensionsPolicyKey, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} Add ExtensionsPolicy and ExtensionsPolicyKey
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; // {{SQL CARBON EDIT}} Remove unused
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
|
||||
const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch);
|
||||
|
||||
interface IRawGalleryExtensionFile {
|
||||
readonly assetType: string;
|
||||
@@ -35,13 +39,14 @@ interface IRawGalleryExtensionProperty {
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionVersion {
|
||||
export interface IRawGalleryExtensionVersion {
|
||||
readonly version: string;
|
||||
readonly lastUpdated: string;
|
||||
readonly assetUri: string;
|
||||
readonly fallbackAssetUri: string;
|
||||
readonly files: IRawGalleryExtensionFile[];
|
||||
readonly properties?: IRawGalleryExtensionProperty[];
|
||||
readonly targetPlatform?: string;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionStatistics {
|
||||
@@ -49,12 +54,20 @@ interface IRawGalleryExtensionStatistics {
|
||||
readonly value: number;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionPublisher {
|
||||
readonly displayName: string;
|
||||
readonly publisherId: string;
|
||||
readonly publisherName: string;
|
||||
readonly domain?: string | null;
|
||||
readonly isDomainVerified?: boolean;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtension {
|
||||
readonly extensionId: string;
|
||||
readonly extensionName: string;
|
||||
readonly displayName: string;
|
||||
readonly shortDescription: string;
|
||||
readonly publisher: { displayName: string, publisherId: string, publisherName: string; };
|
||||
readonly publisher: IRawGalleryExtensionPublisher;
|
||||
readonly versions: IRawGalleryExtensionVersion[];
|
||||
readonly statistics: IRawGalleryExtensionStatistics[];
|
||||
readonly tags: string[] | undefined;
|
||||
@@ -271,7 +284,7 @@ function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): [string
|
||||
function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null {
|
||||
if (version.properties) {
|
||||
const results = version.properties.filter(p => p.key === AssetType.Repository);
|
||||
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@\:/\\-~]+)(.git)(/)?');
|
||||
const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?');
|
||||
|
||||
const uri = results.filter(r => gitRegExp.test(r.value))[0];
|
||||
return uri ? { uri: uri.value, fallbackUri: uri.value } : null;
|
||||
@@ -288,8 +301,8 @@ function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensi
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
return {
|
||||
uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true`,
|
||||
fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}`
|
||||
uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`,
|
||||
fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -355,7 +368,66 @@ function getIsPreview(flags: string): boolean {
|
||||
return flags.indexOf('preview') !== -1;
|
||||
}
|
||||
|
||||
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
|
||||
function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform {
|
||||
return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED;
|
||||
}
|
||||
|
||||
function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] {
|
||||
const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion));
|
||||
|
||||
// Is a web extension only if it has WEB_EXTENSION_TAG
|
||||
const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG);
|
||||
|
||||
// Include Web Target Platform only if it is a web extension
|
||||
const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB);
|
||||
if (isWebExtension) {
|
||||
if (webTargetPlatformIndex === -1) {
|
||||
// Web extension but does not has web target platform -> add it
|
||||
allTargetPlatforms.push(TargetPlatform.WEB);
|
||||
}
|
||||
} else {
|
||||
if (webTargetPlatformIndex !== -1) {
|
||||
// Not a web extension but has web target platform -> remove it
|
||||
allTargetPlatforms.splice(webTargetPlatformIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return allTargetPlatforms;
|
||||
}
|
||||
|
||||
export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], preferredTargetPlatform: TargetPlatform): IRawGalleryExtensionVersion[] {
|
||||
/* It is expected that versions from Marketplace are sorted by version. So we are just sorting by preferred targetPlatform */
|
||||
const fallbackTargetPlatforms = getFallbackTargetPlarforms(preferredTargetPlatform);
|
||||
for (let index = 0; index < versions.length; index++) {
|
||||
const version = versions[index];
|
||||
if (version.version === versions[index - 1]?.version) {
|
||||
let insertionIndex = index;
|
||||
const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);
|
||||
/* put it at the beginning */
|
||||
if (versionTargetPlatform === preferredTargetPlatform) {
|
||||
while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version) { insertionIndex--; }
|
||||
}
|
||||
/* put it after version with preferred targetPlatform or at the beginning */
|
||||
else if (fallbackTargetPlatforms.includes(versionTargetPlatform)) {
|
||||
while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version && getTargetPlatformForExtensionVersion(versions[insertionIndex - 1]) !== preferredTargetPlatform) { insertionIndex--; }
|
||||
}
|
||||
if (insertionIndex !== index) {
|
||||
versions.splice(index, 1);
|
||||
versions.splice(insertionIndex, 0, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
function toExtensionWithLatestVersion(galleryExtension: IRawGalleryExtension, index: number, query: Query, querySource: string | undefined, targetPlatform: TargetPlatform): IGalleryExtension {
|
||||
const allTargetPlatforms = getAllTargetPlatforms(galleryExtension);
|
||||
let latestVersion = galleryExtension.versions[0];
|
||||
latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(version), allTargetPlatforms, targetPlatform)) || latestVersion;
|
||||
return toExtension(galleryExtension, latestVersion, allTargetPlatforms, index, query, querySource);
|
||||
}
|
||||
|
||||
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], index: number, query: Query, querySource?: string): IGalleryExtension {
|
||||
const assets = <IGalleryExtensionAssets>{
|
||||
manifest: getVersionAsset(version, AssetType.Manifest),
|
||||
readme: getVersionAsset(version, AssetType.Details),
|
||||
@@ -380,6 +452,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
||||
publisherId: galleryExtension.publisher.publisherId,
|
||||
publisher: galleryExtension.publisher.publisherName,
|
||||
publisherDisplayName: galleryExtension.publisher.displayName,
|
||||
publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined,
|
||||
description: galleryExtension.shortDescription || '',
|
||||
installCount: getStatistic(galleryExtension.statistics, 'install'),
|
||||
rating: getStatistic(galleryExtension.statistics, 'averagerating'),
|
||||
@@ -388,7 +461,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
||||
tags: galleryExtension.tags || [],
|
||||
releaseDate: Date.parse(galleryExtension.releaseDate),
|
||||
lastUpdated: Date.parse(version.lastUpdated), // {{SQL CARBON EDIT}} We don't have the lastUpdated at the top level currently
|
||||
webExtension: !!galleryExtension.tags?.includes(WEB_EXTENSION_TAG),
|
||||
allTargetPlatforms,
|
||||
assets,
|
||||
properties: {
|
||||
dependencies: getExtensions(version, PropertyType.Dependency),
|
||||
@@ -397,7 +470,9 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
||||
// {{SQL CARBON EDIT}}
|
||||
azDataEngine: getAzureDataStudioEngine(version),
|
||||
localizedLanguages: getLocalizedLanguages(version),
|
||||
targetPlatform: getTargetPlatformForExtensionVersion(version),
|
||||
},
|
||||
preview: getIsPreview(galleryExtension.flags),
|
||||
/* __GDPR__FRAGMENT__
|
||||
"GalleryExtensionTelemetryData2" : {
|
||||
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
@@ -408,7 +483,6 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
||||
index: ((query.pageNumber - 1) * query.pageSize) + index,
|
||||
querySource
|
||||
},
|
||||
preview: getIsPreview(galleryExtension.flags)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -417,7 +491,7 @@ interface IRawExtensionsReport {
|
||||
slow: string[];
|
||||
}
|
||||
|
||||
export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -427,19 +501,19 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>;
|
||||
|
||||
constructor(
|
||||
storageService: IStorageService | undefined,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IConfigurationService private configurationService: IConfigurationService, // {{SQL CARBON EDIT}}
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@optional(IStorageService) storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
const config = productService.extensionsGallery;
|
||||
this.extensionsGalleryUrl = config && config.serviceUrl;
|
||||
this.extensionsControlUrl = config && config.controlUrl;
|
||||
this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, storageService);
|
||||
this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, productService, this.environmentService, this.configurationService, this.fileService, storageService);
|
||||
}
|
||||
|
||||
private api(path = ''): string {
|
||||
@@ -451,18 +525,36 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return !!this.extensionsGalleryUrl;
|
||||
}
|
||||
|
||||
async getExtensions(names: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
async getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const result: IGalleryExtension[] = [];
|
||||
let { total, firstPage: pageResult, getPage } = await this.query({ names, pageSize: names.length }, token);
|
||||
result.push(...pageResult);
|
||||
for (let pageIndex = 1; result.length < total; pageIndex++) {
|
||||
pageResult = await getPage(pageIndex, token);
|
||||
if (pageResult.length) {
|
||||
result.push(...pageResult);
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, identifiers.length)
|
||||
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
|
||||
.withFilter(FilterType.ExtensionName, ...identifiers.map(({ id }) => id.toLowerCase()));
|
||||
|
||||
if (identifiers.every(identifier => !(<IExtensionIdentifierWithVersion>identifier).version)) {
|
||||
query = query.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.IncludeLatestVersionOnly);
|
||||
}
|
||||
|
||||
const { galleryExtensions } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, CancellationToken.None);
|
||||
for (let index = 0; index < galleryExtensions.length; index++) {
|
||||
const galleryExtension = galleryExtensions[index];
|
||||
if (!galleryExtension.versions.length) {
|
||||
continue;
|
||||
}
|
||||
const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName);
|
||||
const version = (<IExtensionIdentifierWithVersion | undefined>identifiers.find(identifier => areSameExtensions(identifier, { id })))?.version;
|
||||
if (version) {
|
||||
const versionAsset = galleryExtension.versions.find(v => v.version === version);
|
||||
if (versionAsset) {
|
||||
result.push(toExtension(galleryExtension, versionAsset, getAllTargetPlatforms(galleryExtension), index, query));
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
result.push(toExtensionWithLatestVersion(galleryExtension, index, query, undefined, CURRENT_TARGET_PLATFORM));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -492,16 +584,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
query = query.withFilter(FilterType.ExtensionName, id);
|
||||
}
|
||||
|
||||
const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None);
|
||||
const { galleryExtensions } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, CancellationToken.None);
|
||||
const [rawExtension] = galleryExtensions;
|
||||
if (!rawExtension || !rawExtension.versions.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allTargetPlatforms = getAllTargetPlatforms(rawExtension);
|
||||
|
||||
if (version) {
|
||||
const versionAsset = rawExtension.versions.filter(v => v.version === version)[0];
|
||||
if (versionAsset) {
|
||||
const extension = toExtension(rawExtension, versionAsset, 0, query);
|
||||
const extension = toExtension(rawExtension, versionAsset, allTargetPlatforms, 0, query);
|
||||
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) {
|
||||
return extension;
|
||||
}
|
||||
@@ -511,11 +605,36 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions);
|
||||
if (rawVersion) {
|
||||
return toExtension(rawExtension, rawVersion, 0, query);
|
||||
return toExtension(rawExtension, rawVersion, allTargetPlatforms, 0, query);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let engine = extension.properties.engine;
|
||||
if (!engine) {
|
||||
const manifest = await this.getManifest(extension, CancellationToken.None);
|
||||
if (!manifest) {
|
||||
throw new Error('Manifest was not found');
|
||||
}
|
||||
engine = manifest.engines.vscode;
|
||||
}
|
||||
return isEngineValid(engine, this.productService.version, this.productService.date);
|
||||
}
|
||||
|
||||
private async isRawExtensionVersionCompatible(rawExtensionVersion: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
if (!isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(rawExtensionVersion), allTargetPlatforms, targetPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const engine = await this.getEngine(rawExtensionVersion);
|
||||
return isEngineValid(engine, this.productService.version, this.productService.date);
|
||||
}
|
||||
|
||||
query(token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
async query(arg1: any, arg2?: any): Promise<IPager<IGalleryExtension>> {
|
||||
@@ -592,15 +711,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
query = query.withSortOrder(options.sortOrder);
|
||||
}
|
||||
|
||||
const { galleryExtensions, total } = await this.queryGallery(query, token);
|
||||
const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source));
|
||||
const { galleryExtensions, total } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, token);
|
||||
const extensions = galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, query, options.source, CURRENT_TARGET_PLATFORM));
|
||||
const getPage = async (pageIndex: number, ct: CancellationToken) => {
|
||||
if (ct.isCancellationRequested) {
|
||||
throw canceled();
|
||||
}
|
||||
const nextPageQuery = query.withPage(pageIndex + 1);
|
||||
const { galleryExtensions } = await this.queryGallery(nextPageQuery, ct);
|
||||
return galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source));
|
||||
const { galleryExtensions } = await this.queryGallery(nextPageQuery, CURRENT_TARGET_PLATFORM, ct);
|
||||
return galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, nextPageQuery, options.source, CURRENT_TARGET_PLATFORM));
|
||||
};
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -632,7 +751,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
filteredExtensions = filteredExtensions.filter(e => {
|
||||
// we only have 1 version for our extensions in the gallery file, so this should always be the case
|
||||
if (e.versions.length === 1) {
|
||||
const extension = toExtension(e, e.versions[0], 0, query);
|
||||
const allTargetPlatforms = getAllTargetPlatforms(e);
|
||||
const extension = toExtension(e, e.versions[0], allTargetPlatforms, 0, query);
|
||||
return extension.properties.localizedLanguages && extension.properties.localizedLanguages.length > 0;
|
||||
}
|
||||
return false;
|
||||
@@ -721,7 +841,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return a[fieldName] < b[fieldName] ? -1 : 1;
|
||||
}
|
||||
|
||||
private async queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
|
||||
private async queryGallery(query: Query, targetPlatform: TargetPlatform, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
|
||||
if (!this.isEnabled()) {
|
||||
throw new Error('No extension gallery service configured.');
|
||||
}
|
||||
@@ -758,6 +878,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
if (result) {
|
||||
const r = result.results[0];
|
||||
const galleryExtensions = r.extensions;
|
||||
galleryExtensions.forEach(e => sortExtensionVersions(e.versions, targetPlatform));
|
||||
// const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; {{SQL CARBON EDIT}} comment out for no unused
|
||||
// const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; {{SQL CARBON EDIT}} comment out for no unused
|
||||
|
||||
@@ -807,8 +928,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
// const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
|
||||
const operationParam = undefined;
|
||||
const downloadAsset = operationParam ? {
|
||||
uri: `${extension.assets.download.uri}&${operationParam}=true`,
|
||||
fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true`
|
||||
uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,
|
||||
fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`
|
||||
} : extension.assets.download;
|
||||
|
||||
const context = await this.getAsset(downloadAsset);
|
||||
@@ -834,6 +955,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async getManifestFromRawExtensionVersion(rawExtensionVersion: IRawGalleryExtensionVersion, token: CancellationToken): Promise<IExtensionManifest | null> {
|
||||
const manifestAsset = getVersionAsset(rawExtensionVersion, AssetType.Manifest);
|
||||
if (!manifestAsset) {
|
||||
throw new Error('Manifest was not found');
|
||||
}
|
||||
const headers = { 'Accept-Encoding': 'gzip' };
|
||||
const context = await this.getAsset(manifestAsset, { headers });
|
||||
return await asJson<IExtensionManifest>(context);
|
||||
}
|
||||
|
||||
async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {
|
||||
const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];
|
||||
if (asset) {
|
||||
@@ -853,7 +984,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return '';
|
||||
}
|
||||
|
||||
async getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
|
||||
async getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, 1)
|
||||
@@ -865,22 +996,23 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
query = query.withFilter(FilterType.ExtensionName, extension.identifier.id);
|
||||
}
|
||||
|
||||
const { galleryExtensions } = await this.queryGallery(query, targetPlatform, CancellationToken.None);
|
||||
if (!galleryExtensions.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);
|
||||
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, targetPlatform)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: IGalleryExtensionVersion[] = [];
|
||||
const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None);
|
||||
if (galleryExtensions.length) {
|
||||
if (compatible) {
|
||||
await Promise.all(galleryExtensions[0].versions.map(async v => {
|
||||
let engine: string | undefined;
|
||||
try {
|
||||
engine = await this.getEngine(v);
|
||||
} catch (error) { /* Ignore error and skip version */ }
|
||||
if (engine && isEngineValid(engine, this.productService.version, this.productService.date)) {
|
||||
result.push({ version: v!.version, date: v!.lastUpdated });
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
result.push(...galleryExtensions[0].versions.map(v => ({ version: v.version, date: v.lastUpdated })));
|
||||
}
|
||||
for (const version of galleryExtensions[0].versions) {
|
||||
try {
|
||||
if (result[result.length - 1]?.version !== version.version && await this.isRawExtensionVersionCompatible(version, allTargetPlatforms, targetPlatform)) {
|
||||
result.push({ version: version.version, date: version.lastUpdated });
|
||||
}
|
||||
} catch (error) { /* Ignore error and skip version */ }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -922,7 +1054,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return this.requestService.request(fallbackOptions, token);
|
||||
}
|
||||
}
|
||||
|
||||
private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion | null> {
|
||||
const version = this.getLastValidExtensionVersionFromProperties(extension, versions);
|
||||
if (version) {
|
||||
@@ -949,25 +1080,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async getEngine(version: IRawGalleryExtensionVersion): Promise<string> {
|
||||
const engine = getEngine(version);
|
||||
if (engine) {
|
||||
return engine;
|
||||
private async getEngine(rawExtensionVersion: IRawGalleryExtensionVersion): Promise<string> {
|
||||
let engine = getEngine(rawExtensionVersion);
|
||||
if (!engine) {
|
||||
const manifest = await this.getManifestFromRawExtensionVersion(rawExtensionVersion, CancellationToken.None);
|
||||
if (!manifest) {
|
||||
throw new Error('Manifest was not found');
|
||||
}
|
||||
engine = manifest.engines.vscode;
|
||||
}
|
||||
|
||||
const manifestAsset = getVersionAsset(version, AssetType.Manifest);
|
||||
if (!manifestAsset) {
|
||||
throw new Error('Manifest was not found');
|
||||
}
|
||||
|
||||
const headers = { 'Accept-Encoding': 'gzip' };
|
||||
const context = await this.getAsset(manifestAsset, { headers });
|
||||
const manifest = await asJson<IExtensionManifest>(context);
|
||||
if (manifest) {
|
||||
return manifest.engines.vscode;
|
||||
}
|
||||
|
||||
throw new Error('Error while reading manifest');
|
||||
return engine;
|
||||
}
|
||||
|
||||
private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion | null> {
|
||||
@@ -1016,7 +1138,38 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: {
|
||||
export class ExtensionGalleryService extends AbstractExtensionGalleryService {
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IRequestService requestService: IRequestService,
|
||||
@ILogService logService: ILogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService {
|
||||
|
||||
constructor(
|
||||
@IRequestService requestService: IRequestService,
|
||||
@ILogService logService: ILogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveMarketplaceHeaders(version: string, productService: IProductService, environmentService: IEnvironmentService, configurationService: IConfigurationService, fileService: IFileService, storageService: {
|
||||
get: (key: string, scope: StorageScope) => string | undefined,
|
||||
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
|
||||
} | undefined): Promise<{ [key: string]: string; }> {
|
||||
@@ -1025,6 +1178,8 @@ export async function resolveMarketplaceHeaders(version: string, environmentServ
|
||||
'User-Agent': `VSCode ${version}`
|
||||
};
|
||||
const uuid = await getServiceMachineId(environmentService, fileService, storageService);
|
||||
headers['X-Market-User-Id'] = uuid;
|
||||
if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) {
|
||||
headers['X-Market-User-Id'] = uuid;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { Platform } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ExtensionType, IExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -16,6 +17,167 @@ export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z
|
||||
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
|
||||
export const WEB_EXTENSION_TAG = '__web_extension';
|
||||
|
||||
export const enum TargetPlatform {
|
||||
WIN32_X64 = 'win32-x64',
|
||||
WIN32_IA32 = 'win32-ia32',
|
||||
WIN32_ARM64 = 'win32-arm64',
|
||||
|
||||
LINUX_X64 = 'linux-x64',
|
||||
LINUX_ARM64 = 'linux-arm64',
|
||||
LINUX_ARMHF = 'linux-armhf',
|
||||
|
||||
ALPINE_X64 = 'alpine-x64',
|
||||
ALPINE_ARM64 = 'alpine-arm64',
|
||||
|
||||
DARWIN_X64 = 'darwin-x64',
|
||||
DARWIN_ARM64 = 'darwin-arm64',
|
||||
|
||||
WEB = 'web',
|
||||
|
||||
UNIVERSAL = 'universal',
|
||||
UNKNOWN = 'unknown',
|
||||
UNDEFINED = 'undefined',
|
||||
}
|
||||
|
||||
export function TargetPlatformToString(targetPlatform: TargetPlatform) {
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.WIN32_X64: return 'Windows 64 bit';
|
||||
case TargetPlatform.WIN32_IA32: return 'Windows 32 bit';
|
||||
case TargetPlatform.WIN32_ARM64: return 'Windows ARM';
|
||||
|
||||
case TargetPlatform.LINUX_X64: return 'Linux 64 bit';
|
||||
case TargetPlatform.LINUX_ARM64: return 'Linux ARM 64';
|
||||
case TargetPlatform.LINUX_ARMHF: return 'Linux ARM';
|
||||
|
||||
case TargetPlatform.ALPINE_X64: return 'Alpine Linux 64 bit';
|
||||
case TargetPlatform.ALPINE_ARM64: return 'Alpine ARM 64';
|
||||
|
||||
case TargetPlatform.DARWIN_X64: return 'Mac';
|
||||
case TargetPlatform.DARWIN_ARM64: return 'Mac Silicon';
|
||||
|
||||
case TargetPlatform.WEB: return 'Web';
|
||||
|
||||
case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL;
|
||||
case TargetPlatform.UNKNOWN: return TargetPlatform.UNKNOWN;
|
||||
case TargetPlatform.UNDEFINED: return TargetPlatform.UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
export function toTargetPlatform(targetPlatform: string): TargetPlatform {
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.WIN32_X64: return TargetPlatform.WIN32_X64;
|
||||
case TargetPlatform.WIN32_IA32: return TargetPlatform.WIN32_IA32;
|
||||
case TargetPlatform.WIN32_ARM64: return TargetPlatform.WIN32_ARM64;
|
||||
|
||||
case TargetPlatform.LINUX_X64: return TargetPlatform.LINUX_X64;
|
||||
case TargetPlatform.LINUX_ARM64: return TargetPlatform.LINUX_ARM64;
|
||||
case TargetPlatform.LINUX_ARMHF: return TargetPlatform.LINUX_ARMHF;
|
||||
|
||||
case TargetPlatform.ALPINE_X64: return TargetPlatform.ALPINE_X64;
|
||||
case TargetPlatform.ALPINE_ARM64: return TargetPlatform.ALPINE_ARM64;
|
||||
|
||||
case TargetPlatform.DARWIN_X64: return TargetPlatform.DARWIN_X64;
|
||||
case TargetPlatform.DARWIN_ARM64: return TargetPlatform.DARWIN_ARM64;
|
||||
|
||||
case TargetPlatform.WEB: return TargetPlatform.WEB;
|
||||
|
||||
case TargetPlatform.UNIVERSAL: return TargetPlatform.UNIVERSAL;
|
||||
default: return TargetPlatform.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTargetPlatform(platform: Platform | 'alpine', arch: string | undefined): TargetPlatform {
|
||||
switch (platform) {
|
||||
case Platform.Windows:
|
||||
if (arch === 'x64') {
|
||||
return TargetPlatform.WIN32_X64;
|
||||
}
|
||||
if (arch === 'ia32') {
|
||||
return TargetPlatform.WIN32_IA32;
|
||||
}
|
||||
if (arch === 'arm64') {
|
||||
return TargetPlatform.WIN32_ARM64;
|
||||
}
|
||||
return TargetPlatform.UNKNOWN;
|
||||
|
||||
case Platform.Linux:
|
||||
if (arch === 'x64') {
|
||||
return TargetPlatform.LINUX_X64;
|
||||
}
|
||||
if (arch === 'arm64') {
|
||||
return TargetPlatform.LINUX_ARM64;
|
||||
}
|
||||
if (arch === 'arm') {
|
||||
return TargetPlatform.LINUX_ARMHF;
|
||||
}
|
||||
return TargetPlatform.UNKNOWN;
|
||||
|
||||
case 'alpine':
|
||||
if (arch === 'x64') {
|
||||
return TargetPlatform.ALPINE_X64;
|
||||
}
|
||||
if (arch === 'arm64') {
|
||||
return TargetPlatform.ALPINE_ARM64;
|
||||
}
|
||||
return TargetPlatform.UNKNOWN;
|
||||
|
||||
case Platform.Mac:
|
||||
if (arch === 'x64') {
|
||||
return TargetPlatform.DARWIN_X64;
|
||||
}
|
||||
if (arch === 'arm64') {
|
||||
return TargetPlatform.DARWIN_ARM64;
|
||||
}
|
||||
return TargetPlatform.UNKNOWN;
|
||||
|
||||
case Platform.Web: return TargetPlatform.WEB;
|
||||
}
|
||||
}
|
||||
|
||||
export function isNotWebExtensionInWebTargetPlatform(allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
|
||||
// Not a web extension in web target platform
|
||||
return productTargetPlatform === TargetPlatform.WEB && !allTargetPlatforms.includes(TargetPlatform.WEB);
|
||||
}
|
||||
|
||||
export function isTargetPlatformCompatible(extensionTargetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[], productTargetPlatform: TargetPlatform): boolean {
|
||||
// Not compatible when extension is not a web extension in web target platform
|
||||
if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, productTargetPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compatible when extension target platform is not defined
|
||||
if (extensionTargetPlatform === TargetPlatform.UNDEFINED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compatible when extension target platform is universal
|
||||
if (extensionTargetPlatform === TargetPlatform.UNIVERSAL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not compatible when extension target platform is unknown
|
||||
if (extensionTargetPlatform === TargetPlatform.UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compatible when extension and product target platforms matches
|
||||
if (extensionTargetPlatform === productTargetPlatform) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
const fallbackTargetPlatforms = getFallbackTargetPlarforms(productTargetPlatform);
|
||||
return fallbackTargetPlatforms.includes(extensionTargetPlatform);
|
||||
}
|
||||
|
||||
export function getFallbackTargetPlarforms(targetPlatform: TargetPlatform): TargetPlatform[] {
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.WIN32_X64: return [TargetPlatform.WIN32_IA32];
|
||||
case TargetPlatform.WIN32_ARM64: return [TargetPlatform.WIN32_IA32];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export interface IGalleryExtensionProperties {
|
||||
dependencies?: string[];
|
||||
extensionPack?: string[];
|
||||
@@ -23,6 +185,7 @@ export interface IGalleryExtensionProperties {
|
||||
// {{SQL CARBON EDIT}}
|
||||
azDataEngine?: string;
|
||||
localizedLanguages?: string[];
|
||||
targetPlatform: TargetPlatform;
|
||||
}
|
||||
|
||||
export interface IGalleryExtensionAsset {
|
||||
@@ -84,6 +247,7 @@ export interface IGalleryExtension {
|
||||
publisherId: string;
|
||||
publisher: string;
|
||||
publisherDisplayName: string;
|
||||
publisherDomain?: { link: string, verified: boolean };
|
||||
description: string;
|
||||
installCount: number;
|
||||
rating: number;
|
||||
@@ -92,11 +256,11 @@ export interface IGalleryExtension {
|
||||
tags: readonly string[];
|
||||
releaseDate: number;
|
||||
lastUpdated: number;
|
||||
preview: boolean;
|
||||
allTargetPlatforms: TargetPlatform[];
|
||||
assets: IGalleryExtensionAssets;
|
||||
properties: IGalleryExtensionProperties;
|
||||
telemetryData: any;
|
||||
preview: boolean;
|
||||
webExtension: boolean;
|
||||
}
|
||||
|
||||
export interface IGalleryMetadata {
|
||||
@@ -118,7 +282,7 @@ export const enum SortBy {
|
||||
Title = 2,
|
||||
PublisherName = 3,
|
||||
InstallCount = 4,
|
||||
PublishedDate = 5,
|
||||
PublishedDate = 10,
|
||||
AverageRating = 6,
|
||||
WeightedRating = 12
|
||||
}
|
||||
@@ -170,17 +334,18 @@ export interface IExtensionGalleryService {
|
||||
isEnabled(): boolean;
|
||||
query(token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
getExtensions(ids: string[], token: CancellationToken): Promise<IGalleryExtension[]>;
|
||||
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
|
||||
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
|
||||
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
getCompatibleExtension(extension: IGalleryExtension): Promise<IGalleryExtension | null>;
|
||||
getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension | null>;
|
||||
isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<boolean>;
|
||||
getCompatibleExtension(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getCompatibleExtension(id: IExtensionIdentifier, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
|
||||
}
|
||||
|
||||
export interface InstallExtensionEvent {
|
||||
@@ -200,19 +365,29 @@ export interface DidUninstallExtensionEvent {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const INSTALL_ERROR_NOT_SUPPORTED = 'notsupported';
|
||||
export const INSTALL_ERROR_MALICIOUS = 'malicious';
|
||||
export const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
|
||||
export enum ExtensionManagementErrorCode {
|
||||
Unsupported = 'Unsupported',
|
||||
Malicious = 'Malicious',
|
||||
Incompatible = 'Incompatible',
|
||||
Invalid = 'Invalid',
|
||||
Download = 'Download',
|
||||
Extract = 'Extract',
|
||||
Delete = 'Delete',
|
||||
Rename = 'Rename',
|
||||
CorruptZip = 'CorruptZip',
|
||||
IncompleteZip = 'IncompleteZip',
|
||||
Internal = 'Internal',
|
||||
}
|
||||
|
||||
export class ExtensionManagementError extends Error {
|
||||
constructor(message: string, readonly code: string) {
|
||||
constructor(message: string, readonly code: ExtensionManagementErrorCode) {
|
||||
super(message);
|
||||
this.name = code;
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean };
|
||||
export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean };
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean };
|
||||
export type InstallVSIXOptions = Omit<InstallOptions, 'installGivenVersion'> & { installOnlyNewlyAddedFromExtensionPack?: boolean };
|
||||
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
|
||||
|
||||
export interface IExtensionManagementParticipant {
|
||||
@@ -237,13 +412,14 @@ export interface IExtensionManagementService {
|
||||
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
|
||||
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
|
||||
getInstalled(type?: ExtensionType, donotIgnoreInvalidExtensions?: boolean): Promise<ILocalExtension[]>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
|
||||
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
|
||||
updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
|
||||
|
||||
registerParticipant(pariticipant: IExtensionManagementParticipant): void;
|
||||
getTargetPlatform(): Promise<TargetPlatform>;
|
||||
}
|
||||
|
||||
export const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
|
||||
|
||||
@@ -199,23 +199,11 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
|
||||
}
|
||||
|
||||
private async getGalleryExtensions(extensions: InstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> {
|
||||
const extensionIds = extensions.filter(({ version }) => version === undefined).map(({ id }) => id);
|
||||
const extensionsWithIdAndVersion = extensions.filter(({ version }) => version !== undefined);
|
||||
|
||||
const galleryExtensions = new Map<string, IGalleryExtension>();
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
const result = await this.extensionGalleryService.getExtensions(extensionIds, CancellationToken.None);
|
||||
result.forEach(extension => galleryExtensions.set(extension.identifier.id.toLowerCase(), extension));
|
||||
})(),
|
||||
Promise.all(extensionsWithIdAndVersion.map(async ({ id, version }) => {
|
||||
const extension = await this.extensionGalleryService.getCompatibleExtension({ id }, version);
|
||||
if (extension) {
|
||||
galleryExtensions.set(extension.identifier.id.toLowerCase(), extension);
|
||||
}
|
||||
}))
|
||||
]);
|
||||
|
||||
const result = await this.extensionGalleryService.getExtensions(extensions, CancellationToken.None);
|
||||
for (const extension of result) {
|
||||
galleryExtensions.set(extension.identifier.id.toLowerCase(), extension);
|
||||
}
|
||||
return galleryExtensions;
|
||||
}
|
||||
|
||||
@@ -241,7 +229,7 @@ export class ExtensionManagementCLIService implements IExtensionManagementCLISer
|
||||
output.log(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id));
|
||||
}
|
||||
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension, installOptions);
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version });
|
||||
output.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, galleryExtension.version));
|
||||
return manifest;
|
||||
} catch (error) {
|
||||
|
||||
@@ -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 { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IReportedExtension, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, IExtensionTipsService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, InstallOptions, InstallVSIXOptions, IReportedExtension, isTargetPlatformCompatible, TargetPlatform, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
|
||||
@@ -64,6 +64,7 @@ export class ExtensionManagementChannel implements IServerChannel {
|
||||
case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer));
|
||||
case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer), args[1]);
|
||||
case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer));
|
||||
case 'getTargetPlatform': return this.service.getTargetPlatform();
|
||||
case 'canInstall': return this.service.canInstall(args[0]);
|
||||
case 'installFromGallery': return this.service.installFromGallery(args[0], args[1]);
|
||||
case 'uninstall': return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), args[1]);
|
||||
@@ -112,6 +113,19 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
|
||||
typeof (<any>thing).scheme === 'string';
|
||||
}
|
||||
|
||||
private _targetPlatformPromise: Promise<TargetPlatform> | undefined;
|
||||
getTargetPlatform(): Promise<TargetPlatform> {
|
||||
if (!this._targetPlatformPromise) {
|
||||
this._targetPlatformPromise = this.channel.call<TargetPlatform>('getTargetPlatform');
|
||||
}
|
||||
return this._targetPlatformPromise;
|
||||
}
|
||||
|
||||
async canInstall(extension: IGalleryExtension): Promise<boolean> {
|
||||
const currentTargetPlatform = await this.getTargetPlatform();
|
||||
return extension.allTargetPlatforms.some(targetPlatform => isTargetPlatformCompatible(targetPlatform, extension.allTargetPlatforms, currentTargetPlatform));
|
||||
}
|
||||
|
||||
zip(extension: ILocalExtension): Promise<URI> {
|
||||
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
|
||||
}
|
||||
@@ -128,10 +142,6 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
|
||||
return Promise.resolve(this.channel.call<IExtensionManifest>('getManifest', [vsix]));
|
||||
}
|
||||
|
||||
async canInstall(extension: IGalleryExtension): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
|
||||
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
|
||||
private async getValidConfigBasedTips(folder: URI): Promise<IConfigBasedExtensionTip[]> {
|
||||
const result: IConfigBasedExtensionTip[] = [];
|
||||
for (const [configPath, tip] of this.allConfigBasedTips) {
|
||||
if (tip.configScheme && tip.configScheme !== folder.scheme) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const content = await this.fileService.readFile(joinPath(folder, configPath));
|
||||
const recommendationByRemote: Map<string, IConfigBasedExtensionTip> = new Map<string, IConfigBasedExtensionTip>();
|
||||
|
||||
@@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Promises as FSPromises } from 'vs/base/node/pfs';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation, TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
@@ -72,7 +72,8 @@ export class ExtensionsDownloader extends Disposable {
|
||||
}
|
||||
|
||||
async delete(location: URI): Promise<void> {
|
||||
// noop as caching is enabled always
|
||||
await this.cleanUpPromise;
|
||||
await this.fileService.del(location);
|
||||
}
|
||||
|
||||
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
|
||||
@@ -123,7 +124,7 @@ export class ExtensionsDownloader extends Disposable {
|
||||
}
|
||||
|
||||
private getName(extension: IGalleryExtension): string {
|
||||
return this.cache ? new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase() : generateUuid();
|
||||
return this.cache ? `${new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase()}${extension.properties.targetPlatform !== TargetPlatform.UNDEFINED ? `-${extension.properties.targetPlatform}` : ''}` : generateUuid();
|
||||
}
|
||||
|
||||
private parse(name: string): ExtensionIdentifierWithVersion | null {
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
import { extensionsWorkbenchServiceIncompatible } from 'sql/base/common/locConstants';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { isLinux, isMacintosh, platform } from 'vs/base/common/platform';
|
||||
import { arch } from 'vs/base/common/process';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -18,11 +20,10 @@ import { 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, IInstallExtensionTask, INSTALL_ERROR_VALIDATING, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import {
|
||||
ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, InstallOptions,
|
||||
InstallVSIXOptions,
|
||||
INSTALL_ERROR_INCOMPATIBLE
|
||||
ExtensionManagementError, ExtensionManagementErrorCode, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, InstallOptions,
|
||||
InstallVSIXOptions, TargetPlatform
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
|
||||
@@ -34,14 +35,11 @@ import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensio
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
|
||||
const INSTALL_ERROR_DOWNLOADING = 'downloading';
|
||||
|
||||
interface InstallableExtension {
|
||||
zipPath: string;
|
||||
identifierWithVersion: ExtensionIdentifierWithVersion;
|
||||
@@ -59,11 +57,12 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@ILogService logService: ILogService,
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@optional(IDownloadService) private downloadService: IDownloadService,
|
||||
@IDownloadService private downloadService: IDownloadService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
super(galleryService, telemetryService, logService);
|
||||
super(galleryService, telemetryService, logService, productService);
|
||||
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
|
||||
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
|
||||
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
|
||||
@@ -78,6 +77,39 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
}));
|
||||
}
|
||||
|
||||
private _targetPlatformPromise: Promise<TargetPlatform> | undefined;
|
||||
getTargetPlatform(): Promise<TargetPlatform> {
|
||||
if (!this._targetPlatformPromise) {
|
||||
this._targetPlatformPromise = (async () => {
|
||||
const isAlpineLinux = await this.isAlpineLinux();
|
||||
const targetPlatform = getTargetPlatform(isAlpineLinux ? 'alpine' : platform, arch);
|
||||
this.logService.debug('ExtensionManagementService#TargetPlatform:', targetPlatform);
|
||||
return targetPlatform;
|
||||
})();
|
||||
}
|
||||
return this._targetPlatformPromise;
|
||||
}
|
||||
|
||||
private async isAlpineLinux(): Promise<boolean> {
|
||||
if (!isLinux) {
|
||||
return false;
|
||||
}
|
||||
let content: string | undefined;
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(URI.file('/etc/os-release'));
|
||||
content = fileContent.value.toString();
|
||||
} catch (error) {
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(URI.file('/usr/lib/os-release'));
|
||||
content = fileContent.value.toString();
|
||||
} catch (error) {
|
||||
/* Ignore */
|
||||
this.logService.debug(`Error while getting the os-release file.`, getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
return !!content && (content.match(/^ID=([^\u001b\r\n]*)/m) || [])[1] === 'alpine';
|
||||
}
|
||||
|
||||
async zip(extension: ILocalExtension): Promise<URI> {
|
||||
this.logService.trace('ExtensionManagementService#zip', extension.identifier.id);
|
||||
const files = await this.collectFiles(extension);
|
||||
@@ -101,10 +133,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
return this.extensionsScanner.scanExtensions(type);
|
||||
}
|
||||
|
||||
async canInstall(extension: IGalleryExtension): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
|
||||
this.logService.trace('ExtensionManagementService#install', vsix.toString());
|
||||
|
||||
@@ -112,15 +140,15 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
const manifest = await getManifest(path.resolve(downloadLocation.fsPath));
|
||||
// {{SQL CARBON EDIT}} Do our own engine checks
|
||||
const id = getGalleryExtensionId(manifest.publisher, manifest.name);
|
||||
if (manifest.engines?.vscode && !isEngineValid(manifest.engines.vscode, product.vscodeVersion, product.date)) {
|
||||
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with the current VS Code engine version '{1}'.", id, product.vscodeVersion));
|
||||
if (manifest.engines?.vscode && !isEngineValid(manifest.engines.vscode, this.productService.vscodeVersion, this.productService.date)) {
|
||||
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with the current VS Code engine version '{1}'.", id, this.productService.vscodeVersion));
|
||||
}
|
||||
if (manifest.engines?.azdata && !isEngineValid(manifest.engines.azdata, product.version, product.date)) {
|
||||
throw new ExtensionManagementError(extensionsWorkbenchServiceIncompatible(id, manifest.version, product.version, manifest.engines.azdata), INSTALL_ERROR_INCOMPATIBLE);
|
||||
if (manifest.engines?.azdata && !isEngineValid(manifest.engines.azdata, this.productService.version, this.productService.date)) {
|
||||
throw new ExtensionManagementError(extensionsWorkbenchServiceIncompatible(id, manifest.version, this.productService.version, manifest.engines.azdata), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
/*
|
||||
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.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), product.version));
|
||||
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));
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -149,10 +177,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
if (vsix.scheme === Schemas.file) {
|
||||
return vsix;
|
||||
}
|
||||
if (!this.downloadService) {
|
||||
throw new Error('Download service is not available');
|
||||
}
|
||||
|
||||
const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid());
|
||||
await this.downloadService.download(vsix, downloadedLocation);
|
||||
return downloadedLocation;
|
||||
@@ -215,9 +239,9 @@ abstract class AbstractInstallExtensionTask extends AbstractExtensionTask<ILocal
|
||||
}
|
||||
} catch (e) {
|
||||
if (isMacintosh) {
|
||||
throw new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED);
|
||||
throw new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), ExtensionManagementErrorCode.Internal);
|
||||
} else {
|
||||
throw new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED);
|
||||
throw new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), ExtensionManagementErrorCode.Internal);
|
||||
}
|
||||
}
|
||||
return this.extract(installableExtension, token);
|
||||
@@ -243,11 +267,8 @@ abstract class AbstractInstallExtensionTask extends AbstractExtensionTask<ILocal
|
||||
}
|
||||
|
||||
private async extract({ zipPath, identifierWithVersion, metadata }: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
|
||||
let local = await this.extensionsScanner.extractUserExtension(identifierWithVersion, zipPath, token);
|
||||
let local = await this.extensionsScanner.extractUserExtension(identifierWithVersion, zipPath, metadata, token);
|
||||
this.logService.info('Extracting completed.', identifierWithVersion.id);
|
||||
if (metadata) {
|
||||
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, metadata);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
@@ -276,12 +297,25 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask {
|
||||
installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped;
|
||||
installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin;
|
||||
|
||||
const local = await this.installExtension(installableExtension, token);
|
||||
if (existingExtension && semver.neq(existingExtension.manifest.version, this.gallery.version)) {
|
||||
await this.extensionsScanner.setUninstalled(existingExtension);
|
||||
try {
|
||||
const local = await this.installExtension(installableExtension, token);
|
||||
if (existingExtension && semver.neq(existingExtension.manifest.version, this.gallery.version)) {
|
||||
await this.extensionsScanner.setUninstalled(existingExtension);
|
||||
}
|
||||
return local;
|
||||
} catch (error) {
|
||||
await this.deleteDownloadedVSIX(installableExtension.zipPath);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteDownloadedVSIX(vsix: string): Promise<void> {
|
||||
try {
|
||||
await this.extensionsDownloader.delete(URI.file(vsix));
|
||||
} catch (error) {
|
||||
/* Ignore */
|
||||
this.logService.warn('Error while deleting the downloaded vsix', vsix.toString(), getErrorMessage(error));
|
||||
}
|
||||
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
|
||||
return local;
|
||||
}
|
||||
|
||||
private async downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<Required<InstallableExtension>> {
|
||||
@@ -297,14 +331,15 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask {
|
||||
zipPath = (await this.extensionsDownloader.downloadExtension(extension, operation)).fsPath;
|
||||
this.logService.info('Downloaded extension:', extension.identifier.id, zipPath);
|
||||
} catch (error) {
|
||||
throw new ExtensionManagementError(joinErrors(error).message, INSTALL_ERROR_DOWNLOADING);
|
||||
throw new ExtensionManagementError(joinErrors(error).message, ExtensionManagementErrorCode.Download);
|
||||
}
|
||||
|
||||
try {
|
||||
const manifest = await getManifest(zipPath);
|
||||
return (<Required<InstallableExtension>>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata });
|
||||
} catch (error) {
|
||||
throw new ExtensionManagementError(joinErrors(error).message, INSTALL_ERROR_VALIDATING);
|
||||
await this.deleteDownloadedVSIX(zipPath);
|
||||
throw new ExtensionManagementError(joinErrors(error).message, ExtensionManagementErrorCode.Invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,10 +362,10 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
|
||||
const installedExtensions = await this.extensionsScanner.scanExtensions(ExtensionType.User);
|
||||
const existing = installedExtensions.find(i => areSameExtensions(this.identifier, i.identifier));
|
||||
const metadata = await this.getMetadata(this.identifier.id, token);
|
||||
metadata.isMachineScoped = this.options.isMachineScoped || existing?.isMachineScoped;
|
||||
metadata.isBuiltin = this.options.isBuiltin || existing?.isBuiltin;
|
||||
|
||||
if (existing) {
|
||||
metadata.isMachineScoped = this.options.isMachineScoped || existing.isMachineScoped;
|
||||
metadata.isBuiltin = this.options.isBuiltin || existing.isBuiltin;
|
||||
this._operation = InstallOperation.Update;
|
||||
if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) {
|
||||
try {
|
||||
|
||||
@@ -19,7 +19,7 @@ import * as pfs from 'vs/base/node/pfs';
|
||||
import { extract, ExtractError } from 'vs/base/node/zip';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ExtensionManagementError, IGalleryMetadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionManagementError, ExtensionManagementErrorCode, IGalleryMetadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
|
||||
import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
@@ -28,12 +28,6 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { CancellationToken } from 'vscode';
|
||||
|
||||
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
|
||||
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
|
||||
const INSTALL_ERROR_EXTRACTING = 'extracting';
|
||||
const INSTALL_ERROR_DELETING = 'deleting';
|
||||
const INSTALL_ERROR_RENAMING = 'renaming';
|
||||
|
||||
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean; }>;
|
||||
type IStoredMetadata = IMetadata & { installedTimestamp: number | undefined };
|
||||
export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata };
|
||||
@@ -69,11 +63,11 @@ export class ExtensionsScanner extends Disposable {
|
||||
const promises: Promise<ILocalExtension[]>[] = [];
|
||||
|
||||
if (type === null || type === ExtensionType.System) {
|
||||
promises.push(this.scanSystemExtensions().then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_SYS_EXTENSIONS))));
|
||||
promises.push(this.scanSystemExtensions().then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ExtensionManagementErrorCode.Internal))));
|
||||
}
|
||||
|
||||
if (type === null || type === ExtensionType.User) {
|
||||
promises.push(this.scanUserExtensions(true).then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS))));
|
||||
promises.push(this.scanUserExtensions(true).then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ExtensionManagementErrorCode.Internal))));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -100,7 +94,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
return this.scanExtensionsInDir(this.extensionsPath, ExtensionType.User);
|
||||
}
|
||||
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const folderName = identifierWithVersion.key();
|
||||
const tempPath = path.join(this.extensionsPath, `.${generateUuid()}`);
|
||||
const extensionPath = path.join(this.extensionsPath, folderName);
|
||||
@@ -111,7 +105,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
try {
|
||||
await pfs.Promises.rm(extensionPath);
|
||||
} catch (e) { /* ignore */ }
|
||||
throw new ExtensionManagementError(localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, identifierWithVersion.id), INSTALL_ERROR_DELETING);
|
||||
throw new ExtensionManagementError(localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, identifierWithVersion.id), ExtensionManagementErrorCode.Delete);
|
||||
}
|
||||
|
||||
await this.extractAtLocation(identifierWithVersion, zipPath, tempPath, token);
|
||||
@@ -119,7 +113,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
if (!local) {
|
||||
throw new Error(localize('cannot read', "Cannot read the extension from {0}", tempPath));
|
||||
}
|
||||
await this.storeMetadata(local, { installedTimestamp: Date.now() });
|
||||
await this.storeMetadata(local, { ...metadata, installedTimestamp: Date.now() });
|
||||
|
||||
try {
|
||||
await this.rename(identifierWithVersion, tempPath, extensionPath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
@@ -236,7 +230,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
try {
|
||||
await pfs.Promises.rm(location);
|
||||
} catch (e) {
|
||||
throw new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING);
|
||||
throw new ExtensionManagementError(this.joinErrors(e).message, ExtensionManagementErrorCode.Delete);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -244,7 +238,15 @@ export class ExtensionsScanner extends Disposable {
|
||||
this.logService.info(`Extracted extension to ${location}:`, identifier.id);
|
||||
} catch (e) {
|
||||
try { await pfs.Promises.rm(location); } catch (e) { /* Ignore */ }
|
||||
throw new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING);
|
||||
let errorCode = ExtensionManagementErrorCode.Extract;
|
||||
if (e instanceof ExtractError) {
|
||||
if (e.type === 'CorruptZip') {
|
||||
errorCode = ExtensionManagementErrorCode.CorruptZip;
|
||||
} else if (e.type === 'Incomplete') {
|
||||
errorCode = ExtensionManagementErrorCode.IncompleteZip;
|
||||
}
|
||||
}
|
||||
throw new ExtensionManagementError(e.message, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +258,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id);
|
||||
return this.rename(identifier, extractPath, renamePath, retryUntil);
|
||||
}
|
||||
throw new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING);
|
||||
throw new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || ExtensionManagementErrorCode.Rename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,26 +9,32 @@ import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isUUID } from 'vs/base/common/uuid';
|
||||
import { mock } from 'vs/base/test/common/mock';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { IRawGalleryExtensionVersion, resolveMarketplaceHeaders, sortExtensionVersions } from 'vs/platform/extensionManagement/common/extensionGalleryService';
|
||||
import { TargetPlatform } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
class EnvironmentServiceMock extends mock<IEnvironmentService>() {
|
||||
override readonly serviceMachineIdResource: URI;
|
||||
constructor(serviceMachineIdResource: URI) {
|
||||
super();
|
||||
this.serviceMachineIdResource = serviceMachineIdResource;
|
||||
this.isBuilt = true;
|
||||
}
|
||||
}
|
||||
|
||||
suite('Extension Gallery Service', () => {
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService;
|
||||
let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService, productService: IProductService, configurationService: IConfigurationService;
|
||||
|
||||
setup(() => {
|
||||
const serviceMachineIdResource = joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'machineid');
|
||||
@@ -37,14 +43,112 @@ suite('Extension Gallery Service', () => {
|
||||
const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
|
||||
fileService.registerProvider(serviceMachineIdResource.scheme, fileSystemProvider);
|
||||
storageService = new InMemoryStorageService();
|
||||
configurationService = new TestConfigurationService({ [TELEMETRY_SETTING_ID]: TelemetryConfiguration.ON });
|
||||
configurationService.updateValue(TELEMETRY_SETTING_ID, TelemetryConfiguration.ON);
|
||||
productService = { _serviceBrand: undefined, ...product, enableTelemetry: true };
|
||||
});
|
||||
|
||||
teardown(() => disposables.clear());
|
||||
|
||||
test('marketplace machine id', async () => {
|
||||
const headers = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService);
|
||||
const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService);
|
||||
assert.ok(isUUID(headers['X-Market-User-Id']));
|
||||
const headers2 = await resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService);
|
||||
const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService);
|
||||
assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']);
|
||||
});
|
||||
|
||||
test('sorting single extension version without target platform', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2')];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.DARWIN_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with preferred target platform', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.DARWIN_X64)];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.DARWIN_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with fallback target platform', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32)];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with not compatible target platform', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.DARWIN_ARM64)];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with multiple target platforms and preferred at first', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.WIN32_X64), aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32), aExtensionVersion('1.1.2')];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with multiple target platforms and preferred at first with no fallbacks', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.DARWIN_X64), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32)];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.DARWIN_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with multiple target platforms and preferred at first and fallback at last', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.WIN32_X64), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32)];
|
||||
const expected = [actual[0], actual[2], actual[1]];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with multiple target platforms and preferred is not first', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32), aExtensionVersion('1.1.2', TargetPlatform.WIN32_X64), aExtensionVersion('1.1.2')];
|
||||
const expected = [actual[1], actual[0], actual[2]];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting single extension version with multiple target platforms and preferred is at the end', async () => {
|
||||
const actual = [aExtensionVersion('1.1.2', TargetPlatform.WIN32_IA32), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.2', TargetPlatform.WIN32_X64)];
|
||||
const expected = [actual[2], actual[0], actual[1]];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_X64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting multiple extension versions without target platforms', async () => {
|
||||
const actual = [aExtensionVersion('1.2.4'), aExtensionVersion('1.1.3'), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.1')];
|
||||
const expected = [...actual];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_ARM64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting multiple extension versions with target platforms - 1', async () => {
|
||||
const actual = [aExtensionVersion('1.2.4', TargetPlatform.DARWIN_ARM64), aExtensionVersion('1.2.4', TargetPlatform.WIN32_ARM64), aExtensionVersion('1.2.4', TargetPlatform.LINUX_ARM64), aExtensionVersion('1.1.3'), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.1')];
|
||||
const expected = [actual[1], actual[0], actual[2], actual[3], actual[4], actual[5]];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_ARM64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting multiple extension versions with target platforms - 2', async () => {
|
||||
const actual = [aExtensionVersion('1.2.4'), aExtensionVersion('1.2.3', TargetPlatform.DARWIN_ARM64), aExtensionVersion('1.2.3', TargetPlatform.WIN32_ARM64), aExtensionVersion('1.2.3', TargetPlatform.LINUX_ARM64), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.1')];
|
||||
const expected = [actual[0], actual[3], actual[1], actual[2], actual[4], actual[5]];
|
||||
sortExtensionVersions(actual, TargetPlatform.LINUX_ARM64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
test('sorting multiple extension versions with target platforms - 3', async () => {
|
||||
const actual = [aExtensionVersion('1.2.4'), aExtensionVersion('1.1.2'), aExtensionVersion('1.1.1'), aExtensionVersion('1.0.0', TargetPlatform.DARWIN_ARM64), aExtensionVersion('1.0.0', TargetPlatform.WIN32_IA32), aExtensionVersion('1.0.0', TargetPlatform.WIN32_ARM64)];
|
||||
const expected = [actual[0], actual[1], actual[2], actual[5], actual[4], actual[3]];
|
||||
sortExtensionVersions(actual, TargetPlatform.WIN32_ARM64);
|
||||
assert.deepStrictEqual(actual, expected);
|
||||
});
|
||||
|
||||
function aExtensionVersion(version: string, targetPlatform?: TargetPlatform): IRawGalleryExtensionVersion {
|
||||
return { version, targetPlatform } as IRawGalleryExtensionVersion;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user