From 0f8c9e4a4251fea666963aec33780c159b21d35c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 1 Jun 2017 14:50:05 +0300 Subject: [PATCH 1/2] Expose loadExtension to fix double loading of packages In case you install extension, the method automatically loads it. However calling `loadExtensions` after that loads it again, so our `$injector` fails. In order to fix this, remove the loading from the `installExtension` method and expose `loadExtension(name)` method. So in the future in case someone wants to load a new an extension after calling `loadExtensions`, they'll be able to use the new method. --- PublicAPI.md | 25 ++++++++++ lib/definitions/extensibility.d.ts | 8 ++++ lib/services/extensibility-service.ts | 5 +- test/nativescript-cli-lib.ts | 2 +- test/services/extensibility-service.ts | 64 +++++++++++++------------- 5 files changed, 70 insertions(+), 34 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 394d39f390..0f49009c36 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -180,6 +180,31 @@ for (let promise of loadExtensionsPromises) { } ``` +### loadExtension +Loads a specified extension. + +* Definition +```TypeScript +/** + * Loads a single extension, so their methods and commands can be used from CLI. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 + * A Promise is returned. It will be rejected in case the extension cannot be loaded. + * @returns {Promise} Promise, resolved with IExtensionData. + */ +loadExtension(extensionName: string): Promise; +``` + +* Usage: +```JavaScript +tns.extensibilityService.loadExtension("my-extension") + .then(extensionData => console.log(`Loaded extension: ${extensionData.extensionName}.`), + err => { + console.log(`Failed to load extension: ${err.extensionName}`); + console.log(err); + }); +} +``` + ## settingsService `settingsService` module provides a way to configure various settings. diff --git a/lib/definitions/extensibility.d.ts b/lib/definitions/extensibility.d.ts index 3aa9dba5cd..222b4b747f 100644 --- a/lib/definitions/extensibility.d.ts +++ b/lib/definitions/extensibility.d.ts @@ -35,6 +35,14 @@ interface IExtensibilityService { */ loadExtensions(): Promise[]; + /** + * Loads a single extension, so their methods and commands can be used from CLI. + * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 + * A Promise is returned. It will be rejected in case the extension cannot be loaded. + * @returns {Promise} Promise, resolved with IExtensionData. + */ + loadExtension(extensionName: string): Promise; + /** * Gets information about installed dependencies - names and versions. * @returns {IStringDictionary} diff --git a/lib/services/extensibility-service.ts b/lib/services/extensibility-service.ts index 3eaadf6456..9cbed92579 100644 --- a/lib/services/extensibility-service.ts +++ b/lib/services/extensibility-service.ts @@ -35,7 +35,7 @@ export class ExtensibilityService implements IExtensibilityService { const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts); this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`); - return await this.loadExtension(installResultInfo.name); + return { extensionName: installResultInfo.name }; } @exported("extensibilityService") @@ -74,7 +74,8 @@ export class ExtensibilityService implements IExtensibilityService { return null; } - private async loadExtension(extensionName: string): Promise { + @exported("extensibilityService") + public async loadExtension(extensionName: string): Promise { try { await this.assertExtensionIsInstalled(extensionName); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 118fd54383..653d440dec 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -19,7 +19,7 @@ describe("nativescript-cli-lib", () => { localBuildService: ["build"], deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], - extensibilityService: ["loadExtensions", "getInstalledExtensions", "installExtension", "uninstallExtension"], + extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/services/extensibility-service.ts b/test/services/extensibility-service.ts index 25d80063a9..ba32641507 100644 --- a/test/services/extensibility-service.ts +++ b/test/services/extensibility-service.ts @@ -136,37 +136,6 @@ describe("extensibilityService", () => { const actualResult = await extensibilityService.installExtension(extensionName); assert.deepEqual(actualResult, { extensionName }); }); - - it("throws error that has extensionName property when unable to load extension", async () => { - const expectedErrorMessage = "Require failed"; - - const extensionName = "extension1"; - const testInjector = getTestInjector(); - const fs: IFileSystem = testInjector.resolve("fs"); - fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; - - fs.readDirectory = (dir: string): string[] => [extensionName]; - - const npm: INodePackageManager = testInjector.resolve("npm"); - npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); - - const requireService: IRequireService = testInjector.resolve("requireService"); - requireService.require = (pathToRequire: string) => { - throw new Error(expectedErrorMessage); - }; - - const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); - let isErrorRaised = false; - try { - await extensibilityService.installExtension(extensionName); - } catch (err) { - isErrorRaised = true; - assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); - assert.deepEqual(err.extensionName, extensionName); - } - - assert.isTrue(isErrorRaised); - }); }); describe("loadExtensions", () => { @@ -609,4 +578,37 @@ describe("extensibilityService", () => { assert.deepEqual(extensibilityService.getInstalledExtensions(), dependencies); }); }); + + describe("loadExtension", () => { + it("throws error that has extensionName property when unable to load extension", async () => { + const expectedErrorMessage = "Require failed"; + + const extensionName = "extension1"; + const testInjector = getTestInjector(); + const fs: IFileSystem = testInjector.resolve("fs"); + fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName; + + fs.readDirectory = (dir: string): string[] => [extensionName]; + + const npm: INodePackageManager = testInjector.resolve("npm"); + npm.install = async (packageName: string, pathToSave: string, config?: any): Promise => ({ name: extensionName }); + + const requireService: IRequireService = testInjector.resolve("requireService"); + requireService.require = (pathToRequire: string) => { + throw new Error(expectedErrorMessage); + }; + + const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService); + let isErrorRaised = false; + try { + await extensibilityService.loadExtension(extensionName); + } catch (err) { + isErrorRaised = true; + assert.deepEqual(err.message, `Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds.`); + assert.deepEqual(err.extensionName, extensionName); + } + + assert.isTrue(isErrorRaised); + }); + }); }); From a93e4a40a19c7f5bdaf3b5da5fd516080f4801c5 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 1 Jun 2017 14:53:34 +0300 Subject: [PATCH 2/2] Update ios-device-lib Update `ios-device-lib` to 0.4.3. The new version includes a fix for long living processes where detaching iOS device causes a huge memory consuption and core dump. --- PublicAPI.md | 6 +++--- lib/commands/extensibility/install-extension.ts | 3 +++ lib/definitions/extensibility.d.ts | 4 ++-- package.json | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 0f49009c36..3e3a89b629 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -94,12 +94,12 @@ interface IExtensionData { ``` ### installExtension -Installs specified extension and loads it in the current process, so the functionality that it adds can be used immediately. +Installs specified extension. * Definition: ```TypeScript /** - * Installs and loads specified extension. + * Installs a specified extension. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage etc. * @returns {Promise} Information about installed extensions. */ @@ -186,7 +186,7 @@ Loads a specified extension. * Definition ```TypeScript /** - * Loads a single extension, so their methods and commands can be used from CLI. + * Loads a single extension, so its methods and commands can be used from CLI. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 * A Promise is returned. It will be rejected in case the extension cannot be loaded. * @returns {Promise} Promise, resolved with IExtensionData. diff --git a/lib/commands/extensibility/install-extension.ts b/lib/commands/extensibility/install-extension.ts index 91bea2a76a..08513d67bc 100644 --- a/lib/commands/extensibility/install-extension.ts +++ b/lib/commands/extensibility/install-extension.ts @@ -6,6 +6,9 @@ export class InstallExtensionCommand implements ICommand { public async execute(args: string[]): Promise { const extensionData = await this.$extensibilityService.installExtension(args[0]); this.$logger.info(`Successfully installed extension ${extensionData.extensionName}.`); + + await this.$extensibilityService.loadExtension(extensionData.extensionName); + this.$logger.info(`Successfully loaded extension ${extensionData.extensionName}.`); } allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to install.")]; diff --git a/lib/definitions/extensibility.d.ts b/lib/definitions/extensibility.d.ts index 222b4b747f..90d2916ef9 100644 --- a/lib/definitions/extensibility.d.ts +++ b/lib/definitions/extensibility.d.ts @@ -13,7 +13,7 @@ interface IExtensionData { */ interface IExtensibilityService { /** - * Installs and loads specified extension. + * Installs a specified extension. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0, * myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage, etc. * @returns {Promise} Information about installed extensions. @@ -36,7 +36,7 @@ interface IExtensibilityService { loadExtensions(): Promise[]; /** - * Loads a single extension, so their methods and commands can be used from CLI. + * Loads a single extension, so its methods and commands can be used from CLI. * @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, myPackage@1.0.0 * A Promise is returned. It will be rejected in case the extension cannot be loaded. * @returns {Promise} Promise, resolved with IExtensionData. diff --git a/package.json b/package.json index 2226ba0a2d..ac14a00071 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.2", + "ios-device-lib": "0.4.3", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1",