Skip to content

Commit

Permalink
[INTERNAL] AbstractResolver: refactor logic
Browse files Browse the repository at this point in the history
  • Loading branch information
flovogt authored and matz3 committed Sep 1, 2023
1 parent c7469fd commit e8191a4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 66 deletions.
94 changes: 55 additions & 39 deletions lib/ui5Framework/AbstractResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,45 +212,9 @@ class AbstractResolver {
throw new Error(`Framework version specifier "${version}" is incorrect or not supported`);
}

const isSnapshotVersionOrRange = version.toLowerCase().endsWith("-snapshot");
if (isSnapshotVersionOrRange) {
const versionMatch = version.match(VERSION_RANGE_REGEXP);
if (versionMatch) {
// For snapshot version ranges we need to insert a stand-in "x" for the patch level
// and - in case none is provided - another "x" for the major version in order to
// convert it to a valid semver range:
// "1-SNAPSHOT" becomes "1.x.x-SNAPSHOT" and "1.112-SNAPSHOT" becomes "1.112.x-SNAPSHOT"
version = `${versionMatch[1]}.${versionMatch[2] || "x"}.x-SNAPSHOT`;
}
}

let spec;
const spec = await this._getVersionSpec(version, {ui5HomeDir, cwd});

if (semver.validRange(version)) {
spec = version;
} else if (encodeURIComponent(version) === version) {
// Valid tag name (same check as npm does)
const allTags = await this.fetchAllTags({ui5HomeDir, cwd});
if (!allTags) {
// Resolver doesn't support tags (e.g. Sapui5MavenSnapshotResolver)
if (version === "latest" || version === "latest-snapshot") {
// Only latest and latest-snapshot are supported which both resolve
// to the latest available version.
// See "isSnapshotVersionOrRange" for -snapshot handling
spec = "*";
}
} else if (allTags[version]) {
// Use version from tag
spec = allTags[version];
} else {
throw new Error(
`Could not resolve framework version via tag '${version}'. ` +
`Make sure the tag is available in the configured registry.`
);
}
}

// For all invalid cases which are not explicitly handled above
// For all invalid cases which are not explicitly handled in _getVersionSpec
if (!spec) {
throw new Error(`Framework version specifier "${version}" is incorrect or not supported`);
}
Expand All @@ -259,8 +223,9 @@ class AbstractResolver {
const resolvedVersion = semver.maxSatisfying(versions, spec, {
// Allow ranges that end with -SNAPSHOT to match any -SNAPSHOT version
// like a normal version in order to support ranges like 1.x.x-SNAPSHOT.
includePrerelease: isSnapshotVersionOrRange
includePrerelease: this._isSnapshotVersionOrRange(version)
});

if (!resolvedVersion) {
if (semver.valid(spec)) {
if (this.name === "Sapui5Resolver" && semver.lt(spec, "1.76.0")) {
Expand All @@ -277,9 +242,60 @@ class AbstractResolver {
`Could not resolve framework version ${version}. ` +
`Make sure the version is valid and available in the configured registry.`);
}

return resolvedVersion;
}

static async _getVersionSpec(version, {ui5HomeDir, cwd}) {
if (this._isSnapshotVersionOrRange(version)) {
const versionMatch = version.match(VERSION_RANGE_REGEXP);
if (versionMatch) {
// For snapshot version ranges we need to insert a stand-in "x" for the patch level
// and - in case none is provided - another "x" for the major version in order to
// convert it to a valid semver range:
// "1-SNAPSHOT" becomes "1.x.x-SNAPSHOT" and "1.112-SNAPSHOT" becomes "1.112.x-SNAPSHOT"
return `${versionMatch[1]}.${versionMatch[2] || "x"}.x-SNAPSHOT`;
}
}

if (semver.validRange(version)) {
return version;
}

// Check for invalid tag name (same check as npm does)
if (encodeURIComponent(version) !== version) {
return null;
}

const allTags = await this.fetchAllTags({ui5HomeDir, cwd});

if (!allTags && (version === "latest" || version === "latest-snapshot")) {
// Resolver doesn't support tags (e.g. Sapui5MavenSnapshotResolver)
// Only latest and latest-snapshot are supported which both resolve
// to the latest available version.
// See "isSnapshotVersionOrRange" for -snapshot handling
return "*";
}

if (!allTags) {
return null;
}

if (!allTags[version]) {
throw new Error(
`Could not resolve framework version via tag '${version}'. ` +
`Make sure the tag is available in the configured registry.`
);
}

// Use version from tag
return allTags[version];
}

static _isSnapshotVersionOrRange(version) {
return version.toLowerCase().endsWith("-snapshot");
}

// To be implemented by resolver
async getLibraryMetadata(libraryName) {
throw new Error("AbstractResolver: getLibraryMetadata must be implemented!");
Expand Down
19 changes: 9 additions & 10 deletions lib/ui5Framework/Openui5Resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,24 +84,23 @@ class Openui5Resolver extends AbstractResolver {
})
};
}
static async fetchAllVersions({ui5HomeDir, cwd} = {}) {
const installer = new Installer({
cwd: cwd ? path.resolve(cwd) : process.cwd(),
ui5HomeDir:
ui5HomeDir ? path.resolve(ui5HomeDir) :
path.join(os.homedir(), ".ui5")
});
static async fetchAllVersions(options) {
const installer = this._getInstaller(options);
return await installer.fetchPackageVersions({pkgName: OPENUI5_CORE_PACKAGE});
}

static async fetchAllTags({ui5HomeDir, cwd} = {}) {
const installer = new Installer({
static async fetchAllTags(options) {
const installer = this._getInstaller(options);
return installer.fetchPackageDistTags({pkgName: OPENUI5_CORE_PACKAGE});
}

static _getInstaller({ui5HomeDir, cwd} = {}) {
return new Installer({
cwd: cwd ? path.resolve(cwd) : process.cwd(),
ui5HomeDir:
ui5HomeDir ? path.resolve(ui5HomeDir) :
path.join(os.homedir(), ".ui5")
});
return installer.fetchPackageDistTags({pkgName: OPENUI5_CORE_PACKAGE});
}
}

Expand Down
61 changes: 44 additions & 17 deletions test/lib/ui5framework/Openui5Resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import os from "node:os";

test.beforeEach(async (t) => {
t.context.InstallerStub = sinon.stub();
t.context.fetchPackageDistTags = sinon.stub();
t.context.fetchPackageManifestStub = sinon.stub();
t.context.fetchPackageVersionsStub = sinon.stub();
t.context.installPackageStub = sinon.stub();
t.context.InstallerStub.callsFake(() => {
return {
fetchPackageDistTags: t.context.fetchPackageDistTags,
fetchPackageManifest: t.context.fetchPackageManifestStub,
fetchPackageVersions: t.context.fetchPackageVersionsStub,
installPackage: t.context.installPackageStub
Expand Down Expand Up @@ -139,40 +141,48 @@ test.serial("Openui5Resolver: handleLibrary", async (t) => {
t.deepEqual(await promises.install, {pkgPath: "/foo/sap.ui.lib1"}, "Install should resolve with expected object");
});

test.serial("Openui5Resolver: Static fetchAllVersions", async (t) => {
test.serial("Openui5Resolver: Static _getInstaller", (t) => {
const {Openui5Resolver} = t.context;

const expectedVersions = ["1.75.0", "1.75.1", "1.76.0"];
const options = {
cwd: "/cwd",
ui5HomeDir: "/ui5HomeDir"
};

t.context.fetchPackageVersionsStub.returns(expectedVersions);
const installer = Openui5Resolver._getInstaller(options);

const versions = await Openui5Resolver.fetchAllVersions(options);
t.is(t.context.InstallerStub.callCount, 1, "Installer should be called once");
t.true(t.context.InstallerStub.calledWithNew(), "Installer should be called with new");
t.is(installer, t.context.InstallerStub.getCall(0).returnValue, "Installer instance is returned");
t.deepEqual(t.context.InstallerStub.getCall(0).args, [{
cwd: path.resolve("/cwd"),
ui5HomeDir: path.resolve("/ui5HomeDir")
}], "Installer should be called with expected arguments");
});

t.deepEqual(versions, expectedVersions, "Fetched versions should be correct");
test.serial("Openui5Resolver: Static _getInstaller without options", (t) => {
const {Openui5Resolver} = t.context;

t.is(t.context.fetchPackageVersionsStub.callCount, 1, "fetchPackageVersions should be called once");
t.deepEqual(t.context.fetchPackageVersionsStub.getCall(0).args, [{pkgName: "@openui5/sap.ui.core"}],
"fetchPackageVersions should be called with expected arguments");
const installer = Openui5Resolver._getInstaller();

t.is(t.context.InstallerStub.callCount, 1, "Installer should be called once");
t.true(t.context.InstallerStub.calledWithNew(), "Installer should be called with new");
t.is(installer, t.context.InstallerStub.getCall(0).returnValue, "Installer instance is returned");
t.deepEqual(t.context.InstallerStub.getCall(0).args, [{
cwd: path.resolve("/cwd"),
ui5HomeDir: path.resolve("/ui5HomeDir")
cwd: process.cwd(),
ui5HomeDir: path.join(os.homedir(), ".ui5")
}], "Installer should be called with expected arguments");
});

test.serial("Openui5Resolver: Static fetchAllVersions without options", async (t) => {
test.serial("Openui5Resolver: Static fetchAllVersions", async (t) => {
const {Openui5Resolver} = t.context;

const expectedVersions = ["1.75.0", "1.75.1", "1.76.0"];

t.context.fetchPackageVersionsStub.returns(expectedVersions);

const getInstallerSpy = sinon.spy(Openui5Resolver, "_getInstaller");

const versions = await Openui5Resolver.fetchAllVersions();

t.deepEqual(versions, expectedVersions, "Fetched versions should be correct");
Expand All @@ -181,10 +191,27 @@ test.serial("Openui5Resolver: Static fetchAllVersions without options", async (t
t.deepEqual(t.context.fetchPackageVersionsStub.getCall(0).args, [{pkgName: "@openui5/sap.ui.core"}],
"fetchPackageVersions should be called with expected arguments");

t.is(t.context.InstallerStub.callCount, 1, "Installer should be called once");
t.true(t.context.InstallerStub.calledWithNew(), "Installer should be called with new");
t.deepEqual(t.context.InstallerStub.getCall(0).args, [{
cwd: process.cwd(),
ui5HomeDir: path.join(os.homedir(), ".ui5")
}], "Installer should be called with expected arguments");
t.is(getInstallerSpy.callCount, 1, "_getInstaller should be called once");
t.is(getInstallerSpy.getCall(0).args[0], undefined, "_getInstaller should be called without any options");
});

test.serial("Openui5Resolver: Static fetchAllTags", async (t) => {
const {Openui5Resolver} = t.context;

const expectedTags = ["latest", "latest-1.71", "latest-1"];

t.context.fetchPackageDistTags.returns(expectedTags);

const getInstallerSpy = sinon.spy(Openui5Resolver, "_getInstaller");

const tags = await Openui5Resolver.fetchAllTags();

t.deepEqual(tags, expectedTags, "Fetched tags should be correct");

t.is(t.context.fetchPackageDistTags.callCount, 1, "fetchPackageVersions should be called once");
t.deepEqual(t.context.fetchPackageDistTags.getCall(0).args, [{pkgName: "@openui5/sap.ui.core"}],
"fetchPackageVersions should be called with expected arguments");

t.is(getInstallerSpy.callCount, 1, "_getInstaller should be called once");
t.is(getInstallerSpy.getCall(0).args[0], undefined, "_getInstaller should be called without any options");
});

0 comments on commit e8191a4

Please sign in to comment.