From 39854a18cd38b82686151b2c4c1da894c822e6f2 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 29 Feb 2016 17:34:40 +0200 Subject: [PATCH] Fix installation of latest compatible version CLI should install latest compatible version of runtimes, based on it's current version. For example in case CLI's version is "1.6.0", when `tns platform add android` is called, CLI should install latest 1.6.x version of android runtime, no matter if 1.7.x exists. This was not working correctly as it was using beta versions, incorrect sorting, etc. Fix it by using semver methods and always use the `@latest` tag of the package. In case it doesn't match CLI version, try finding latest matching tag with maxSatisfying method of semver, which dismisses beta versions. Required for publishing `@next` in npm. --- lib/npm-installation-manager.ts | 45 +++++----- test/npm-installation-manager.ts | 137 ++++++++++++++++++++----------- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/lib/npm-installation-manager.ts b/lib/npm-installation-manager.ts index e6e0d402fb..101f0b46b2 100644 --- a/lib/npm-installation-manager.ts +++ b/lib/npm-installation-manager.ts @@ -65,6 +65,9 @@ export class NpmInstallationManager implements INpmInstallationManager { public getLatestVersion(packageName: string): IFuture { return (() => { let data = this.$npm.view(packageName, "dist-tags").wait(); + // data is something like : + // { '1.0.1': { 'dist-tags': { latest: '1.0.1', next: '1.0.2-2016-02-25-181', next1: '1.0.2' } } + // There's only one key and it's always the @latest tag. let latestVersion = _.first(_.keys(data)); this.$logger.trace("Using version %s. ", latestVersion); @@ -74,22 +77,31 @@ export class NpmInstallationManager implements INpmInstallationManager { public getLatestCompatibleVersion(packageName: string): IFuture { return (() => { + let cliVersionRange = `~${this.$staticConfig.version}`; let latestVersion = this.getLatestVersion(packageName).wait(); - let data = this.$npm.view(packageName, "versions").wait(); - let versions: string[] = data[latestVersion].versions; - - let versionData = this.getVersionData(this.$staticConfig.version); - - let compatibleVersions = _(versions) - .map(ver => this.getVersionData(ver)) - .filter(verData => versionData.major === verData.major && versionData.minor === verData.minor) - .sortBy(verData => verData.patch) - .value(); - - let result = _.last(compatibleVersions) || this.getVersionData(latestVersion); + if(semver.satisfies(latestVersion, cliVersionRange)) { + return latestVersion; + } - let latestCompatibleVersion = `${result.major}.${result.minor}.${result.patch}`; - return latestCompatibleVersion; + let data: any = this.$npm.view(packageName, "versions").wait(); + /* data is something like: + { + "1.1.0":{ + "versions":[ + "1.0.0", + "1.0.1-2016-02-25-181", + "1.0.1", + "1.0.2-2016-02-25-182", + "1.0.2", + "1.1.0-2016-02-25-183", + "1.1.0", + "1.2.0-2016-02-25-184" + ] + } + } + */ + let versions: string[] = data && data[latestVersion] && data[latestVersion].versions; + return semver.maxSatisfying(versions, cliVersionRange) || latestVersion; }).future()(); } @@ -204,10 +216,5 @@ export class NpmInstallationManager implements INpmInstallationManager { return this.$fs.exists(directory).wait() && this.$fs.enumerateFilesInDirectorySync(directory).length > 0; }).future()(); } - - private getVersionData(version: string): IVersionData { - let [ major, minor, patch ] = version.split("."); - return { major, minor, patch }; - } } $injector.register("npmInstallationManager", NpmInstallationManager); diff --git a/test/npm-installation-manager.ts b/test/npm-installation-manager.ts index dcda6ef26c..19e0b66692 100644 --- a/test/npm-installation-manager.ts +++ b/test/npm-installation-manager.ts @@ -50,65 +50,110 @@ function mockNpm(testInjector: IInjector, versions: string[], latestVersion: str }); } -describe("Npm installation manager tests", () => { - it("returns correct latest compatible version when only one exists", () => { - let testInjector = createTestInjector(); - - let versions = ["1.4.0"]; - let latestVersion = "1.4.0"; - - mockNpm(testInjector, versions, latestVersion); +interface ITestData { + /** + * All versions of the package, including the ones from another tags. + */ + versions: string[]; + + /** + * The version under latest tag. + */ + packageLatestVersion: string; + + /** + * Version of nativescript-cli, based on which the version of the package that will be installed is detected. + */ + cliVersion: string; + + /** + * Expected result + */ + expectedResult: string; +} - // Mock staticConfig.version - let staticConfig = testInjector.resolve("staticConfig"); - staticConfig.version = "1.4.0"; +describe("Npm installation manager tests", () => { + let testData: IDictionary = { + "when there's only one available version and it matches CLI's version": { + versions: ["1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.4.0", + expectedResult: "1.4.0" + }, - // Mock npmInstallationManager.getLatestVersion - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestVersion = (packageName: string) => Future.fromResult(latestVersion); + "when there's only one available version and it is higher than match CLI's version": { + versions: ["1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.2.0", + expectedResult: "1.4.0" + }, - let actualLatestCompatibleVersion = npmInstallationManager.getLatestCompatibleVersion("").wait(); - let expectedLatestCompatibleVersion = "1.4.0"; - assert.equal(actualLatestCompatibleVersion, expectedLatestCompatibleVersion); - }); + "when there's only one available version and it is lower than CLI's version": { + versions: ["1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.6.0", + expectedResult: "1.4.0" + }, - it("returns correct latest compatible version", () => { - let testInjector = createTestInjector(); + "when there are multiple package versions and the latest one matches ~":{ + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.3.3", + cliVersion: "1.3.0", + expectedResult: "1.3.3" + }, - let versions = ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"]; - let latestVersion = "1.3.3"; + "when there are multiple package versions and the latest one matches ~ when there are newer matching versions but they are not under latest tag":{ + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.3.2", + cliVersion: "1.3.0", + expectedResult: "1.3.2" + }, - mockNpm(testInjector, versions, latestVersion); + "when there are multiple package versions and the latest one is lower than ~": { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"], + packageLatestVersion: "1.4.0", + cliVersion: "1.5.0", + expectedResult: "1.4.0" + }, - // Mock staticConfig.version - let staticConfig = testInjector.resolve("staticConfig"); - staticConfig.version = "1.3.0"; + "when there are multiple package versions and there's beta version matching CLI's semver": { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0-2016-02-25-182"], + packageLatestVersion: "1.4.0", + cliVersion: "1.5.0", + expectedResult: "1.4.0" + }, - // Mock npmInstallationManager.getLatestVersion - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestVersion = (packageName: string) => Future.fromResult(latestVersion); + "when there are multiple package versions and package's latest version is greater than CLI's version": { + versions: ["1.2.0", "1.3.0", "1.3.1", "1.4.0", "1.5.0-2016-02-25-182", "1.5.0", "1.6.0"], + packageLatestVersion: "1.6.0", + cliVersion: "1.5.0", + expectedResult: "1.5.0" + }, - let actualLatestCompatibleVersion = npmInstallationManager.getLatestCompatibleVersion("").wait(); - let expectedLatestCompatibleVersion = "1.3.3"; - assert.equal(actualLatestCompatibleVersion, expectedLatestCompatibleVersion); - }); + "when there are multiple versions latest one does not match CLI's semver and other versions are not matching either": { + versions: ["1.0.0", "1.0.1", "1.2.0", "1.3.1", "1.4.0", "1.5.0-2016-02-25-182", "1.5.0"], + packageLatestVersion: "1.0.0", + cliVersion: "1.1.0", + expectedResult: "1.0.0" + } + }; - it("returns correct latest compatible version", () => { - let testInjector = createTestInjector(); + _.each(testData, (currentTestData: ITestData, testName: string) => { + it(`returns correct latest compatible version, ${testName}`, () => { + let testInjector = createTestInjector(); - let versions = ["1.2.0", "1.3.0", "1.3.1", "1.3.2", "1.3.3", "1.4.0"]; - let latestVersion = _.last(versions); - mockNpm(testInjector, versions, latestVersion); + mockNpm(testInjector, currentTestData.versions, currentTestData.packageLatestVersion); - // Mock staticConfig.version - let staticConfig = testInjector.resolve("staticConfig"); - staticConfig.version = "1.5.0"; + // Mock staticConfig.version + let staticConfig = testInjector.resolve("staticConfig"); + staticConfig.version = currentTestData.cliVersion; - // Mock npmInstallationManager.getLatestVersion - let npmInstallationManager = testInjector.resolve("npmInstallationManager"); - npmInstallationManager.getLatestVersion = (packageName: string) => Future.fromResult(latestVersion); + // Mock npmInstallationManager.getLatestVersion + let npmInstallationManager = testInjector.resolve("npmInstallationManager"); + npmInstallationManager.getLatestVersion = (packageName: string) => Future.fromResult(currentTestData.packageLatestVersion); - let actualLatestCompatibleVersion = npmInstallationManager.getLatestCompatibleVersion("").wait(); - assert.equal(actualLatestCompatibleVersion, latestVersion); + let actualLatestCompatibleVersion = npmInstallationManager.getLatestCompatibleVersion("").wait(); + assert.equal(actualLatestCompatibleVersion, currentTestData.expectedResult); + }); }); });