From 6ce985c8feab26e6a97ca4570b3931f507773666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20O=C3=9Fwald?= <1410947+matz3@users.noreply.github.com> Date: Fri, 29 May 2020 14:34:23 +0200 Subject: [PATCH] [FIX] ui5Framework: Allow providing exact prerelease versions (#326) - Prereleases can now be passed, but "latest" won't resolve to them. - Add comments about RegExp patterns / test against schema pattern - Adopt error handling / add tests --- lib/ui5Framework/AbstractResolver.js | 39 +++++--- test/lib/ui5framework/AbstractResolver.js | 110 ++++++++++++++++++++++ 2 files changed, 138 insertions(+), 11 deletions(-) diff --git a/lib/ui5Framework/AbstractResolver.js b/lib/ui5Framework/AbstractResolver.js index a96c116c0..8a6047929 100644 --- a/lib/ui5Framework/AbstractResolver.js +++ b/lib/ui5Framework/AbstractResolver.js @@ -2,7 +2,18 @@ const path = require("path"); const log = require("@ui5/logger").getLogger("ui5Framework:AbstractResolver"); const semver = require("semver"); -const versionRegExp = /^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?$/; +// Matches Semantic Versioning 2.0.0 versions +// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string +// +// This needs to be aligned with the ui5.yaml JSON schema: +// lib/validation/schema/specVersion/2.0/kind/project.json#/definitions/framework/properties/version/pattern +// +// eslint-disable-next-line max-len +const SEMVER_VERSION_REGEXP = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + +// Reduced Semantic Versioning pattern +// Matches MAJOR.MINOR as a simple version range to be resolved to the latest patch +const VERSION_RANGE_REGEXP = /^(0|[1-9]\d*)\.(0|[1-9]\d*)$/; /** * Abstract Resolver @@ -163,7 +174,7 @@ class AbstractResolver { let spec; if (version === "latest") { spec = "*"; - } else if (versionRegExp.test(version)) { + } else if (VERSION_RANGE_REGEXP.test(version) || SEMVER_VERSION_REGEXP.test(version)) { spec = version; } else { throw new Error(`Framework version specifier "${version}" is incorrect or not supported`); @@ -171,15 +182,16 @@ class AbstractResolver { const versions = await this.fetchAllVersions({ui5HomeDir, cwd}); const resolvedVersion = semver.maxSatisfying(versions, spec); if (!resolvedVersion) { - if (this.name === "Sapui5Resolver" && semver.lt(spec, "1.76.0")) { - throw new Error(`Could not resolve framework version ${version}. ` + - `Note that SAPUI5 framework libraries can only be consumed by the UI5 Tooling ` + - `starting with SAPUI5 v1.76.0`); - } else - if (this.name === "Openui5Resolver" && semver.lt(spec, "1.52.5")) { - throw new Error(`Could not resolve framework version ${version}. ` + - `Note that OpenUI5 framework libraries can only be consumed by the UI5 Tooling ` + - `starting with OpenUI5 v1.52.5`); + if (semver.valid(spec)) { + if (this.name === "Sapui5Resolver" && semver.lt(spec, "1.76.0")) { + throw new Error(`Could not resolve framework version ${version}. ` + + `Note that SAPUI5 framework libraries can only be consumed by the UI5 Tooling ` + + `starting with SAPUI5 v1.76.0`); + } else if (this.name === "Openui5Resolver" && semver.lt(spec, "1.52.5")) { + throw new Error(`Could not resolve framework version ${version}. ` + + `Note that OpenUI5 framework libraries can only be consumed by the UI5 Tooling ` + + `starting with OpenUI5 v1.52.5`); + } } throw new Error(`Could not resolve framework version ${version}`); } @@ -198,4 +210,9 @@ class AbstractResolver { } } +if (process.env.NODE_ENV === "test") { + // Export pattern for testing to be checked against JSON schema pattern + AbstractResolver._SEMVER_VERSION_REGEXP = SEMVER_VERSION_REGEXP; +} + module.exports = AbstractResolver; diff --git a/test/lib/ui5framework/AbstractResolver.js b/test/lib/ui5framework/AbstractResolver.js index 9f84408ce..0d38f755c 100644 --- a/test/lib/ui5framework/AbstractResolver.js +++ b/test/lib/ui5framework/AbstractResolver.js @@ -354,6 +354,42 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH }], "fetchAllVersions should be called with expected arguments"); }); +test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH-prerelease'", async (t) => { + const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") + .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT"]); + + const version = await MyResolver.resolveVersion("1.79.0-SNAPSHOT", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + }); + + t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); + + t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + }], "fetchAllVersions should be called with expected arguments"); +}); + +test.serial("AbstractResolver: Static resolveVersion does not include prereleases for 'latest' version", async (t) => { + const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") + .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT"]); + + const version = await MyResolver.resolveVersion("latest", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + }); + + t.is(version, "1.78.0", "Resolved version should be correct"); + + t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + }], "fetchAllVersions should be called with expected arguments"); +}); + test.serial("AbstractResolver: Static resolveVersion without options", async (t) => { const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0"]); @@ -494,3 +530,77 @@ test.serial( `Could not resolve framework version 1.75.0. Note that SAPUI5 framework libraries can only be ` + `consumed by the UI5 Tooling starting with SAPUI5 v1.76.0`); }); + +test.serial( + "AbstractResolver: Static resolveVersion throws error when latest OpenUI5 version cannot be found", async (t) => { + class Openui5Resolver extends AbstractResolver { + static async fetchAllVersions() {} + } + + sinon.stub(Openui5Resolver, "fetchAllVersions") + .returns([]); + + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("latest", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + })); + + t.is(error.message, `Could not resolve framework version latest`); + }); + +test.serial( + "AbstractResolver: Static resolveVersion throws error when latest SAPUI5 version cannot be found", async (t) => { + class Sapui5Resolver extends AbstractResolver { + static async fetchAllVersions() {} + } + + sinon.stub(Sapui5Resolver, "fetchAllVersions") + .returns([]); + + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("latest", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + })); + + t.is(error.message, `Could not resolve framework version latest`); + }); + +test.serial( + "AbstractResolver: Static resolveVersion throws error when OpenUI5 version range cannot be resolved", async (t) => { + class Openui5Resolver extends AbstractResolver { + static async fetchAllVersions() {} + } + + sinon.stub(Openui5Resolver, "fetchAllVersions") + .returns([]); + + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.99", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + })); + + t.is(error.message, `Could not resolve framework version 1.99`); + }); + +test.serial( + "AbstractResolver: Static resolveVersion throws error when SAPUI5 version range cannot be resolved", async (t) => { + class Sapui5Resolver extends AbstractResolver { + static async fetchAllVersions() {} + } + + sinon.stub(Sapui5Resolver, "fetchAllVersions") + .returns([]); + + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.99", { + cwd: "/cwd", + ui5HomeDir: "/ui5HomeDir" + })); + + t.is(error.message, `Could not resolve framework version 1.99`); + }); + +test.serial("AbstractResolver: SEMVER_VERSION_REGEXP should be aligned with JSON schema", async (t) => { + const projectSchema = require("../../../lib/validation/schema/specVersion/2.0/kind/project.json"); + const schemaPattern = projectSchema.definitions.framework.properties.version.pattern; + t.is(schemaPattern, AbstractResolver._SEMVER_VERSION_REGEXP.source); +});