From 9dc0003b798c38d3f4895494e089646c852b88c5 Mon Sep 17 00:00:00 2001 From: DimitarTachev Date: Fri, 4 Jan 2019 17:25:48 +0200 Subject: [PATCH] fix: clean the installed extensions and inspector cache on intentional CLI uninstall --- lib/common/commands/preuninstall.ts | 31 ++++++++++++++++++++++- lib/common/declarations.d.ts | 9 ++++++- lib/common/definitions/extensibility.d.ts | 6 +++++ lib/common/file-system.ts | 8 ++++++ lib/declarations.d.ts | 1 + lib/package-installation-manager.ts | 10 +++++++- lib/services/extensibility-service.ts | 5 ++++ test/stubs.ts | 10 ++++++-- 8 files changed, 75 insertions(+), 5 deletions(-) diff --git a/lib/common/commands/preuninstall.ts b/lib/common/commands/preuninstall.ts index efd0a65a10..e5f08e14ea 100644 --- a/lib/common/commands/preuninstall.ts +++ b/lib/common/commands/preuninstall.ts @@ -5,12 +5,41 @@ export class PreUninstallCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $fs: IFileSystem, + constructor(private $extensibilityService: IExtensibilityService, + private $fs: IFileSystem, + private $packageInstallationManager: IPackageInstallationManager, private $settingsService: ISettingsService) { } public async execute(args: string[]): Promise { + if (this.isIntentionalUninstall()) { + this.handleIntentionalUninstall(); + } + this.$fs.deleteFile(path.join(this.$settingsService.getProfileDir(), "KillSwitches", "cli")); } + + private isIntentionalUninstall(): boolean { + let isIntentionalUninstall = false; + if (process.env && process.env.npm_config_argv) { + try { + const npmConfigArgv = JSON.parse(process.env.npm_config_argv); + const uninstallAliases = ["uninstall", "remove", "rm", "r", "un", "unlink"]; + if (_.intersection(npmConfigArgv.original, uninstallAliases).length > 0) { + isIntentionalUninstall = true; + } + } catch (error) { + // ignore + } + + } + + return isIntentionalUninstall; + } + + private handleIntentionalUninstall(): void { + this.$extensibilityService.removeAllExtensions(); + this.$packageInstallationManager.clearInspectorCache(); + } } $injector.registerCommand("dev-preuninstall", PreUninstallCommand); diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 39587c2024..86cc592bd7 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -289,12 +289,19 @@ interface IFileSystem { deleteFile(path: string): void; /** - * Deletes whole directory. Implementation uses shelljs. + * Deletes whole directory. * @param {string} directory Path to directory that has to be deleted. * @returns {void} */ deleteDirectory(directory: string): void; + /** + * Deletes whole directory without throwing exceptions. + * @param {string} directory Path to directory that has to be deleted. + * @returns {void} + */ + deleteDirectorySafe(directory: string): void; + /** * Returns the size of specified file. * @param {string} path Path to file. diff --git a/lib/common/definitions/extensibility.d.ts b/lib/common/definitions/extensibility.d.ts index 1036d718fc..778b36d24d 100644 --- a/lib/common/definitions/extensibility.d.ts +++ b/lib/common/definitions/extensibility.d.ts @@ -84,6 +84,12 @@ interface IExtensibilityService { */ uninstallExtension(extensionName: string): Promise; + /** + * Removes all installed extensions. + * @returns {void} + */ + removeAllExtensions(): void; + /** * Loads all extensions, so their methods and commands can be used from CLI. * For each of the extensions, a new Promise is returned. It will be rejected in case the extension cannot be loaded. However other promises will not be reflected by this failure. diff --git a/lib/common/file-system.ts b/lib/common/file-system.ts index 802bfcfce9..b69658c19b 100644 --- a/lib/common/file-system.ts +++ b/lib/common/file-system.ts @@ -130,6 +130,14 @@ export class FileSystem implements IFileSystem { } } + public deleteDirectorySafe(directory: string): void { + try { + this.deleteDirectory(directory); + } catch (e) { + return; + } + } + public getFileSize(path: string): number { const stat = this.getFsStats(path); return stat.size; diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 3bff6085fd..626be4d264 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -82,6 +82,7 @@ interface IPackageInstallationManager { getLatestCompatibleVersion(packageName: string, referenceVersion?: string): Promise; getLatestCompatibleVersionSafe(packageName: string, referenceVersion?: string): Promise; getInspectorFromCache(inspectorNpmPackageName: string, projectDir: string): Promise; + clearInspectorCache(): void; } /** diff --git a/lib/package-installation-manager.ts b/lib/package-installation-manager.ts index fcd42b493f..b2e62e66c4 100644 --- a/lib/package-installation-manager.ts +++ b/lib/package-installation-manager.ts @@ -71,7 +71,7 @@ export class PackageInstallationManager implements IPackageInstallationManager { return inspectorPath; } - const cachePath = path.join(this.$settingsService.getProfileDir(), constants.INSPECTOR_CACHE_DIRNAME); + const cachePath = this.getInspectorCachePath(); this.prepareCacheDir(cachePath); const pathToPackageInCache = path.join(cachePath, constants.NODE_MODULES_FOLDER_NAME, inspectorNpmPackageName); const iOSFrameworkNSValue = this.$projectDataService.getNSValue(projectDir, constants.TNS_IOS_RUNTIME_NAME); @@ -95,6 +95,14 @@ export class PackageInstallationManager implements IPackageInstallationManager { return pathToPackageInCache; } + public clearInspectorCache(): void { + this.$fs.deleteDirectorySafe(this.getInspectorCachePath()); + } + + private getInspectorCachePath(): string { + return path.join(this.$settingsService.getProfileDir(), constants.INSPECTOR_CACHE_DIRNAME); + } + private prepareCacheDir(cacheDirName: string): void { this.$fs.ensureDirectoryExists(cacheDirName); diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index 55c2f5f224..c26b4301bf 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -54,6 +54,11 @@ export class ExtensibilityService implements IExtensibilityService { this.$logger.trace(`Finished uninstallation of extension '${extensionName}'.`); } + public removeAllExtensions(): void { + this.$fs.deleteDirectorySafe(this.pathToExtensions); + this.$logger.info(`Removed all NativeScript CLI extensions.`); + } + public getInstalledExtensionsData(): IExtensionData[] { const installedExtensions = this.getInstalledExtensions(); return _.keys(installedExtensions).map(installedExtension => this.getInstalledExtensionData(installedExtension)); diff --git a/test/stubs.ts b/test/stubs.ts index 1a47e49c09..76daa39a7e 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -57,6 +57,9 @@ export class ProcessServiceStub implements IProcessService { } export class FileSystemStub implements IFileSystem { + deleteDirectorySafe(directory: string): void { + return this.deleteDirectory(directory); + } async zipFiles(zipFile: string, files: string[], zipPathCallback: (path: string) => string): Promise { return undefined; } @@ -73,8 +76,8 @@ export class FileSystemStub implements IFileSystem { return undefined; } - async deleteDirectory(directory: string): Promise { - return Promise.resolve(); + deleteDirectory(directory: string): void { + return undefined; } getFileSize(path: string): number { @@ -227,6 +230,9 @@ export class ErrorsStub implements IErrors { } export class PackageInstallationManagerStub implements IPackageInstallationManager { + clearInspectorCache(): void { + return undefined; + } async install(packageName: string, pathToSave?: string, options?: INpmInstallOptions): Promise { return Promise.resolve(""); }