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:
Karl Burtram
2022-07-11 14:09:32 -07:00
committed by GitHub
parent fa0fcef303
commit 26455e9113
1876 changed files with 72050 additions and 37997 deletions

View File

@@ -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 },

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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) {

View File

@@ -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));
}

View File

@@ -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>();

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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;
}
});