diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index ddb495b523348a2ea072da4678bb9023a2424f5b..d13358368ccc338bf0508c8e0c44c6eac0665c94 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -7,6 +7,7 @@ import { localize } from 'vs/nls'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; import * as path from 'path'; +import * as semver from 'semver'; import { TPromise } from 'vs/base/common/winjs.base'; import { sequence } from 'vs/base/common/async'; @@ -35,10 +36,7 @@ import { StateService } from 'vs/platform/state/node/stateService'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService'; -import { areSameExtensions, getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import Severity from 'vs/base/common/severity'; +import { areSameExtensions, getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { URI } from 'vs/base/common/uri'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; @@ -72,8 +70,7 @@ class Main { constructor( @IEnvironmentService private environmentService: IEnvironmentService, @IExtensionManagementService private extensionManagementService: IExtensionManagementService, - @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService, - @IDialogService private dialogService: IDialogService + @IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService ) { } run(argv: ParsedArgs): TPromise { @@ -111,17 +108,22 @@ class Main { .filter(e => /\.vsix$/i.test(e)) .map(id => () => { const extension = path.isAbsolute(id) ? id : path.join(process.cwd(), id); - - return this.extensionManagementService.install(URI.file(extension)).then(() => { - console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); - }, error => { - if (isPromiseCanceledError(error)) { - console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension))); + return this.validate(extension, force) + .then(valid => { + if (valid) { + return this.extensionManagementService.install(URI.file(extension)).then(() => { + console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); + }, error => { + if (isPromiseCanceledError(error)) { + console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension))); + return null; + } else { + return TPromise.wrapError(error); + } + }); + } return null; - } else { - return TPromise.wrapError(error); - } - }); + }); }); const galleryTasks: Task[] = extensions @@ -153,15 +155,8 @@ class Main { console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version)); return this.installFromGallery(id, extension); } else { - const updateMessage = localize('updateConfirmationMessage', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Would you like to update?", id, installedExtension.manifest.version, extension.version); - return this.dialogService.show(Severity.Info, updateMessage, [localize('yes', "Yes"), localize('no', "No")]) - .then(option => { - if (option === 0) { - return this.installFromGallery(id, extension); - } - console.log(localize('cancelInstall', "Cancelled installing Extension '{0}'.", id)); - return TPromise.as(null); - }); + console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version)); + return Promise.resolve(null); } } else { console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); @@ -178,6 +173,26 @@ class Main { return sequence([...vsixTasks, ...galleryTasks]); } + private validate(vsix: string, force: boolean): Thenable { + return getManifest(vsix) + .then(manifest => { + if (manifest) { + const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; + return this.extensionManagementService.getInstalled(LocalExtensionType.User) + .then(installedExtensions => { + const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0]; + if (newer && !force) { + console.log(localize('forceDowngrade', "A newer version of this extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.galleryIdentifier.id, newer.manifest.version, manifest.version)); + return false; + } + return true; + }); + } else { + return Promise.reject(new Error('Invalid vsix')); + } + }); + } + private installFromGallery(id: string, extension: IGalleryExtension): TPromise { console.log(localize('installing', "Installing...")); return this.extensionManagementService.installFromGallery(extension) @@ -252,7 +267,6 @@ export function main(argv: ParsedArgs): TPromise { services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - services.set(IDialogService, new SyncDescriptor(CommandLineDialogService)); const appenders: AppInsightsAppender[] = []; if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 573f75e164ebbd2b6390fc22118ff67133c1f6e9..badf838c96c8fe6286a5d99262ae07ae144572d2 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import * as path from 'path'; import * as pfs from 'vs/base/node/pfs'; -import * as errors from 'vs/base/common/errors'; import { assign } from 'vs/base/common/objects'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { flatten } from 'vs/base/common/arrays'; @@ -34,8 +33,6 @@ import pkg from 'vs/platform/node/package'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import Severity from 'vs/base/common/severity'; import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -131,7 +128,6 @@ export class ExtensionManagementService extends Disposable implements IExtension constructor( @IEnvironmentService private environmentService: IEnvironmentService, - @IDialogService private dialogService: IDialogService, @IExtensionGalleryService private galleryService: IExtensionGalleryService, @ILogService private logService: ILogService, @optional(IDownloadService) private downloadService: IDownloadService, @@ -206,9 +202,14 @@ export class ExtensionManagementService extends Disposable implements IExtension } return this.removeIfExists(identifier.id) .then( - () => this.checkOutdated(manifest) - .then(validated => { - if (validated) { + () => { + const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; + return this.getInstalled(LocalExtensionType.User) + .then(installedExtensions => { + const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0]; + return newer ? this.uninstall(newer, true) : null; + }) + .then(() => { this.logService.info('Installing the extension:', identifier.id); this._onInstallExtension.fire({ identifier, zipPath }); return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) @@ -221,9 +222,8 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.error('Failed to install the extension:', identifier.id, e.message); return Promise.reject(e); }); - } - return null; - }), + }); + }, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name)))); }); }); @@ -247,29 +247,6 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(existing => existing ? this.removeExtension(existing, 'existing') : null); } - private checkOutdated(manifest: IExtensionManifest): Promise { - const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - return this.getInstalled(LocalExtensionType.User) - .then(installedExtensions => { - const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0]; - if (newer) { - const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?"); - const buttons = [ - nls.localize('override', "Override"), - nls.localize('cancel', "Cancel") - ]; - return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 }) - .then(value => { - if (value === 0) { - return this.uninstall(newer, true).then(() => true); - } - return Promise.reject(errors.canceled()); - }); - } - return true; - }); - } - private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, type: LocalExtensionType, token: CancellationToken): Promise { return this.toNonCancellablePromise(this.getInstalled() .then(installed => {