mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)
* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 * Fix breaks * Extension management fixes * Fix breaks in windows bundling * Fix/skip failing tests * Update distro * Add clear to nuget.config * Add hygiene task * Bump distro * Fix hygiene issue * Add build to hygiene exclusion * Update distro * Update hygiene * Hygiene exclusions * Update tsconfig * Bump distro for server breaks * Update build config * Update darwin path * Add done calls to notebook tests * Skip failing tests * Disable smoke tests
This commit is contained in:
@@ -4,15 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { rename } from 'vs/base/node/pfs';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'semver-umd';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
@@ -23,7 +25,7 @@ export class ExtensionsDownloader extends Disposable {
|
||||
private readonly cleanUpPromise: Promise<void>;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@@ -36,8 +38,21 @@ export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
const vsixName = this.getName(extension);
|
||||
const location = joinPath(this.extensionsDownloadDir, vsixName);
|
||||
|
||||
// Download only if vsix does not exist
|
||||
if (!await this.fileService.exists(location)) {
|
||||
// Download to temporary location first only if vsix does not exist
|
||||
const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`);
|
||||
if (!await this.fileService.exists(tempLocation)) {
|
||||
await this.extensionGalleryService.download(extension, tempLocation, operation);
|
||||
}
|
||||
|
||||
// Rename temp location to original
|
||||
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -45,9 +60,15 @@ export class ExtensionsDownloader extends Disposable {
|
||||
// noop as caching is enabled always
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
|
||||
try {
|
||||
await rename(from.fsPath, to.fsPath);
|
||||
} catch (error) {
|
||||
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
|
||||
this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`);
|
||||
return this.rename(from, to, retryUntil);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +88,7 @@ export class ExtensionsDownloader extends Disposable {
|
||||
all.push([extension, stat]);
|
||||
}
|
||||
}
|
||||
const byExtension = groupByExtension(all, ([extension]) => extension.identifier);
|
||||
const byExtension = groupByExtension(all, ([extension]) => extension);
|
||||
const distinct: IFileStatWithMetadata[] = [];
|
||||
for (const p of byExtension) {
|
||||
p.sort((a, b) => semver.rcompare(a[0].version, b[0].version));
|
||||
|
||||
@@ -18,13 +18,15 @@ import {
|
||||
InstallOperation,
|
||||
INSTALL_ERROR_MALICIOUS,
|
||||
INSTALL_ERROR_INCOMPATIBLE,
|
||||
ExtensionManagementError
|
||||
ExtensionManagementError,
|
||||
InstallOptions,
|
||||
UninstallOptions
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as semver from 'semver-umd';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
@@ -33,7 +35,7 @@ import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/ex
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { tmpdir } from 'os';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { optional, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -42,7 +44,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
|
||||
import { ExtensionsScanner, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner';
|
||||
import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner';
|
||||
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
|
||||
|
||||
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
|
||||
@@ -82,7 +84,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@optional(IDownloadService) private downloadService: IDownloadService,
|
||||
@@ -106,7 +108,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
async zip(extension: ILocalExtension): Promise<URI> {
|
||||
this.logService.trace('ExtensionManagementService#zip', extension.identifier.id);
|
||||
const files = await this.collectFiles(extension);
|
||||
const location = await zip(path.join(tmpdir(), generateUuid()), files);
|
||||
const location = await zip(joinPath(this.environmentService.tmpDir, generateUuid()).fsPath, files);
|
||||
return URI.file(location);
|
||||
}
|
||||
|
||||
@@ -147,10 +149,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));
|
||||
}
|
||||
|
||||
async install(vsix: URI, isMachineScoped?: boolean): Promise<ILocalExtension> {
|
||||
async install(vsix: URI, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
// {{SQL CARBON EDIT}}
|
||||
let startTime = new Date().getTime();
|
||||
|
||||
this.logService.trace('ExtensionManagementService#install', vsix.toString());
|
||||
return createCancelablePromise(async token => {
|
||||
|
||||
@@ -172,7 +173,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
const installedExtensions = await this.getInstalled(ExtensionType.User);
|
||||
const existing = installedExtensions.find(i => areSameExtensions(identifier, i.identifier));
|
||||
if (existing) {
|
||||
isMachineScoped = isMachineScoped || existing.isMachineScoped;
|
||||
options.isMachineScoped = options.isMachineScoped || existing.isMachineScoped;
|
||||
options.isBuiltin = options.isBuiltin || existing.isBuiltin;
|
||||
// operation = InstallOperation.Update; {{ SQL CARBON EDIT }}
|
||||
if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) {
|
||||
try {
|
||||
@@ -201,6 +203,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
|
||||
let isMachineScoped = options.isMachineScoped;
|
||||
return this.installExtension({ zipPath, identifierWithVersion, metadata: { isMachineScoped } }, token)
|
||||
.then(
|
||||
local => {
|
||||
@@ -234,7 +237,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
throw new Error('Download service is not available');
|
||||
}
|
||||
|
||||
const downloadedLocation = URI.file(path.join(tmpdir(), generateUuid()));
|
||||
const downloadedLocation = joinPath(this.environmentService.tmpDir, generateUuid());
|
||||
await this.downloadService.download(vsix, downloadedLocation);
|
||||
return downloadedLocation;
|
||||
}
|
||||
@@ -244,7 +247,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
try {
|
||||
const local = await this.installExtension({ zipPath, identifierWithVersion, metadata }, token);
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, undefined);
|
||||
await this.installDependenciesAndPackExtensions(local, undefined, options);
|
||||
} catch (error) {
|
||||
if (isNonEmptyArray(local.manifest.extensionDependencies)) {
|
||||
this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message);
|
||||
@@ -253,10 +256,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
this.logService.warn(`Cannot install packed extensions of extension:`, local.identifier.id, error.message);
|
||||
}
|
||||
}
|
||||
this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation });
|
||||
this._onDidInstallExtension.fire({ identifier: identifierWithVersion, zipPath, local, operation });
|
||||
return local;
|
||||
} catch (error) {
|
||||
this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error });
|
||||
this._onDidInstallExtension.fire({ identifier: identifierWithVersion, zipPath, operation, error });
|
||||
throw error;
|
||||
}
|
||||
}*/
|
||||
@@ -265,7 +268,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return true;
|
||||
}
|
||||
|
||||
async installFromGallery(extension: IGalleryExtension, isMachineScoped?: boolean): Promise<ILocalExtension> {
|
||||
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
|
||||
}
|
||||
@@ -285,7 +288,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key();
|
||||
let cancellablePromise = this.installingExtensions.get(key);
|
||||
if (!cancellablePromise) {
|
||||
cancellablePromise = createCancelablePromise(token => this.doInstallFromGallery(extension, !!isMachineScoped, token));
|
||||
cancellablePromise = createCancelablePromise(token => this.doInstallFromGallery(extension, options, token));
|
||||
this.installingExtensions.set(key, cancellablePromise);
|
||||
cancellablePromise.finally(() => this.installingExtensions.delete(key));
|
||||
}
|
||||
@@ -293,7 +296,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return cancellablePromise;
|
||||
}
|
||||
|
||||
private async doInstallFromGallery(extension: IGalleryExtension, isMachineScoped: boolean, token: CancellationToken): Promise<ILocalExtension> {
|
||||
private async doInstallFromGallery(extension: IGalleryExtension, options: InstallOptions, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const startTime = new Date().getTime();
|
||||
let operation: InstallOperation = InstallOperation.Install;
|
||||
this.logService.info('Installing extension:', extension.identifier.id);
|
||||
@@ -307,16 +310,19 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
const installableExtension = await this.downloadInstallableExtension(extension, operation);
|
||||
installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped;
|
||||
installableExtension.metadata.isMachineScoped = options.isMachineScoped || existingExtension?.isMachineScoped;
|
||||
installableExtension.metadata.isBuiltin = options.isBuiltin || existingExtension?.isBuiltin;
|
||||
const local = await this.installExtension(installableExtension, token);
|
||||
|
||||
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
|
||||
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
if (!options.donotIncludePackAndDependencies) {
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
|
||||
@@ -412,7 +418,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
try {
|
||||
const local = await this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion);
|
||||
if (local) {
|
||||
return local;
|
||||
return installableExtension.metadata ? this.extensionsScanner.saveMetadataForLocalExtension(local, installableExtension.metadata) : local;
|
||||
}
|
||||
} catch (e) {
|
||||
if (isMacintosh) {
|
||||
@@ -430,26 +436,25 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return null;
|
||||
}
|
||||
|
||||
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id);
|
||||
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.id);
|
||||
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
|
||||
await this.unsetUninstalled(identifierWithVersion);
|
||||
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id);
|
||||
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.id);
|
||||
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
return installed.find(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion)) || null;
|
||||
}
|
||||
|
||||
private async extractAndInstall({ zipPath, identifierWithVersion, metadata }: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const { identifier } = identifierWithVersion;
|
||||
let local = await this.extensionsScanner.extractUserExtension(identifierWithVersion, zipPath, token);
|
||||
this.logService.info('Installation completed.', identifier.id);
|
||||
this.logService.info('Installation completed.', identifierWithVersion.id);
|
||||
if (metadata) {
|
||||
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, metadata);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined): Promise<void> {
|
||||
private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined, options: InstallOptions): Promise<void> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -472,7 +477,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None);
|
||||
const extensionsToInstall = galleryResult.firstPage;
|
||||
try {
|
||||
await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)));
|
||||
await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e, options)));
|
||||
} catch (error) {
|
||||
try { await this.rollback(extensionsToInstall); } catch (e) { /* ignore */ }
|
||||
throw error;
|
||||
@@ -487,7 +492,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await Promise.all(extensionsToUninstall.map(local => this.uninstall(local)));
|
||||
}
|
||||
|
||||
async uninstall(extension: ILocalExtension): Promise<void> {
|
||||
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
|
||||
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
|
||||
@@ -496,7 +501,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
try {
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed);
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options);
|
||||
} catch (error) {
|
||||
throw this.joinErrors(error);
|
||||
}
|
||||
@@ -504,7 +509,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
|
||||
this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id);
|
||||
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...metadata, isMachineScoped: local.isMachineScoped });
|
||||
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), ...metadata });
|
||||
this.manifestCache.invalidate();
|
||||
return local;
|
||||
}
|
||||
|
||||
async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension> {
|
||||
this.logService.trace('ExtensionManagementService#updateExtensionScope', local.identifier.id);
|
||||
local = await this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), isMachineScoped });
|
||||
this.manifestCache.invalidate();
|
||||
return local;
|
||||
}
|
||||
@@ -543,15 +555,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}, new Error(''));
|
||||
}
|
||||
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
try {
|
||||
await this.preUninstallExtension(extension);
|
||||
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
if (packedExtensions.length) {
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed);
|
||||
} else {
|
||||
await this.uninstallExtensions(extension, [], installed);
|
||||
}
|
||||
const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed, options);
|
||||
} catch (error) {
|
||||
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
|
||||
throw error;
|
||||
@@ -559,10 +567,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await this.postUninstallExtension(extension);
|
||||
}
|
||||
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
const extensionsToUninstall = [extension, ...otherExtensionsToUninstall];
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
if (!options.donotCheckDependents) {
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
}
|
||||
}
|
||||
await Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]);
|
||||
}
|
||||
@@ -613,7 +623,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
checked.push(extension);
|
||||
const extensionsPack = extension.manifest.extensionPack ? extension.manifest.extensionPack : [];
|
||||
if (extensionsPack.length) {
|
||||
const packedExtensions = installed.filter(i => extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
|
||||
const packedExtensions = installed.filter(i => !i.isBuiltin && extensionsPack.some(id => areSameExtensions({ id }, i.identifier)));
|
||||
const packOfPackedExtensions: ILocalExtension[] = [];
|
||||
for (const packedExtension of packedExtensions) {
|
||||
packOfPackedExtensions.push(...this.getAllPackExtensionsToUninstall(packedExtension, installed, checked));
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class ExtensionUrlTrustService implements IExtensionUrlTrustService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private trustedExtensionUrlPublicKeys = new Map<string, (crypto.KeyObject | string | null)[]>();
|
||||
|
||||
constructor(
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
async isExtensionUrlTrusted(extensionId: string, url: string): Promise<boolean> {
|
||||
if (!this.productService.trustedExtensionUrlPublicKeys) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'There are no configured trusted keys');
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = /^(.*)#([^#]+)$/.exec(url);
|
||||
|
||||
if (!match) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri has no fragment', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, originalUrl, fragment] = match;
|
||||
|
||||
let keys = this.trustedExtensionUrlPublicKeys.get(extensionId);
|
||||
|
||||
if (!keys) {
|
||||
keys = this.productService.trustedExtensionUrlPublicKeys[extensionId];
|
||||
|
||||
if (!keys || keys.length === 0) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Extension doesn\'t have any trusted keys', extensionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.trustedExtensionUrlPublicKeys.set(extensionId, [...keys]);
|
||||
}
|
||||
|
||||
const fragmentBuffer = Buffer.from(decodeURIComponent(fragment), 'base64');
|
||||
|
||||
if (fragmentBuffer.length <= 6) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri fragment is not a signature', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const timestampBuffer = fragmentBuffer.slice(0, 6);
|
||||
const timestamp = fragmentBuffer.readUIntBE(0, 6);
|
||||
const diff = Date.now() - timestamp;
|
||||
|
||||
if (diff < 0 || diff > 3_600_000) { // 1 hour
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri has expired', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const signatureBuffer = fragmentBuffer.slice(6);
|
||||
const verify = crypto.createVerify('SHA256');
|
||||
verify.write(timestampBuffer);
|
||||
verify.write(Buffer.from(originalUrl));
|
||||
verify.end();
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
|
||||
if (key === null) { // failed to be parsed before
|
||||
continue;
|
||||
} else if (typeof key === 'string') { // needs to be parsed
|
||||
try {
|
||||
key = crypto.createPublicKey({ key: Buffer.from(key, 'base64'), format: 'der', type: 'spki' });
|
||||
keys[i] = key;
|
||||
} catch (err) {
|
||||
this.logService.warn('ExtensionUrlTrustService#isExtensionUrlTrusted', `Failed to parse trusted extension uri public key #${i + 1} for ${extensionId}:`, err);
|
||||
keys[i] = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (verify.verify(key, signatureBuffer)) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri is valid', url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri could not be verified', url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as semver from 'semver-umd';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as path from 'vs/base/common/path';
|
||||
@@ -13,8 +13,7 @@ import { ExtensionType, IExtensionManifest, IExtensionIdentifier } from 'vs/plat
|
||||
import { areSameExtensions, ExtensionIdentifierWithVersion, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { Limiter, Queue } from 'vs/base/common/async';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -23,6 +22,7 @@ import { extract, ExtractError } from 'vs/base/node/zip';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
|
||||
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
|
||||
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
|
||||
@@ -30,8 +30,9 @@ const INSTALL_ERROR_EXTRACTING = 'extracting';
|
||||
const INSTALL_ERROR_DELETING = 'deleting';
|
||||
const INSTALL_ERROR_RENAMING = 'renaming';
|
||||
|
||||
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; }>;
|
||||
type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata };
|
||||
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean }>;
|
||||
export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata };
|
||||
type IRelaxedLocalExtension = Omit<ILocalExtension, 'isBuiltin'> & { isBuiltin: boolean };
|
||||
|
||||
export class ExtensionsScanner extends Disposable {
|
||||
|
||||
@@ -43,12 +44,12 @@ export class ExtensionsScanner extends Disposable {
|
||||
constructor(
|
||||
private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
this.extensionsPath = environmentService.extensionsPath!;
|
||||
this.extensionsPath = environmentService.extensionsPath;
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
}
|
||||
@@ -94,7 +95,6 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const { identifier } = identifierWithVersion;
|
||||
const folderName = identifierWithVersion.key();
|
||||
const tempPath = path.join(this.extensionsPath, `.${folderName}`);
|
||||
const extensionPath = path.join(this.extensionsPath, folderName);
|
||||
@@ -105,12 +105,12 @@ export class ExtensionsScanner extends Disposable {
|
||||
try {
|
||||
await pfs.rimraf(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, identifier.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), INSTALL_ERROR_DELETING);
|
||||
}
|
||||
|
||||
await this.extractAtLocation(identifier, zipPath, tempPath, token);
|
||||
await this.extractAtLocation(identifierWithVersion, zipPath, tempPath, token);
|
||||
try {
|
||||
await this.rename(identifier, tempPath, extensionPath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
await this.rename(identifierWithVersion, tempPath, extensionPath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
this.logService.info('Renamed to', extensionPath);
|
||||
} catch (error) {
|
||||
this.logService.info('Rename failed. Deleting from extracted location', tempPath);
|
||||
@@ -136,6 +136,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
// unset if false
|
||||
metadata.isMachineScoped = metadata.isMachineScoped || undefined;
|
||||
metadata.isBuiltin = metadata.isBuiltin || undefined;
|
||||
const manifestPath = path.join(local.location.fsPath, 'package.json');
|
||||
const raw = await pfs.readFile(manifestPath, 'utf8');
|
||||
const { manifest } = await this.parseManifest(raw);
|
||||
@@ -253,7 +254,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
|
||||
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : undefined;
|
||||
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
|
||||
const local = <ILocalExtension>{ type, identifier, manifest, location: URI.file(extensionPath), readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false };
|
||||
const local = <ILocalExtension>{ type, identifier, manifest, location: URI.file(extensionPath), readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System };
|
||||
if (metadata) {
|
||||
this.setMetadata(local, metadata);
|
||||
}
|
||||
@@ -281,11 +282,12 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private setMetadata(local: ILocalExtension, metadata: IMetadata): void {
|
||||
private setMetadata(local: IRelaxedLocalExtension, metadata: IMetadata): void {
|
||||
local.publisherDisplayName = metadata.publisherDisplayName || null;
|
||||
local.publisherId = metadata.publisherId || null;
|
||||
local.identifier.uuid = metadata.id;
|
||||
local.isMachineScoped = !!metadata.isMachineScoped;
|
||||
local.isBuiltin = local.type === ExtensionType.System || !!metadata.isBuiltin;
|
||||
}
|
||||
|
||||
private async removeUninstalledExtensions(): Promise<void> {
|
||||
@@ -336,7 +338,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
private _devSystemExtensionsPath: string | null = null;
|
||||
private get devSystemExtensionsPath(): string {
|
||||
if (!this._devSystemExtensionsPath) {
|
||||
this._devSystemExtensionsPath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions'));
|
||||
this._devSystemExtensionsPath = path.normalize(path.join(FileAccess.asFileUri('', require).fsPath, '..', '.build', 'builtInExtensions'));
|
||||
}
|
||||
return this._devSystemExtensionsPath;
|
||||
}
|
||||
@@ -362,7 +364,6 @@ export class ExtensionsScanner extends Disposable {
|
||||
try {
|
||||
const manifest = JSON.parse(raw);
|
||||
const metadata = manifest.__metadata || null;
|
||||
delete manifest.__metadata;
|
||||
c({ manifest, metadata });
|
||||
} catch (err) {
|
||||
e(new Error(localize('invalidManifest', "Extension invalid: package.json is not a JSON file.")));
|
||||
|
||||
Reference in New Issue
Block a user