Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions packages/builder/lib/processors/versionInfoGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,57 @@ const processLibraryInfo = async (libraryInfo) => {
* @typedef {object} LibraryInfo
* @property {string} name The library name, e.g. "lib.x"
* @property {string} version The library version, e.g. "1.0.0"
* @property {string} [id] The npm package id of the library project, e.g. "@openui5/sap.ui.core".
* Used by {@link resolveFramework} to detect the UI5 framework via npm-scope scanning.
* @property {@ui5/fs/Resource} libraryManifest library manifest resource,
* e.g. resource with path "lib/x/manifest.json"
* @property {@ui5/fs/Resource[]} embeddedManifests list of embedded manifest resources,
* e.g. resource with path "lib/x/sub/manifest.json"
*/

/**
* Resolves the UI5 framework name and version from raw project inputs.
*
* Resolution order:
* 1. Explicit config: <code>frameworkName</code> is <code>"OpenUI5"</code> or <code>"SAPUI5"</code>
* → returns it with <code>frameworkVersion</code> coalesced to <code>""</code>.
* 2. Dependency scan: examines <code>libraryInfos[].id</code> for <code>@sapui5/</code> or
* <code>@openui5/</code> npm scopes; version comes from the <code>sap.ui.core</code> entry.
* 3. Fallback: <code>{name: "", version: ""}</code>.
*
* The returned object always has both <code>name</code> and <code>version</code> as strings.
*
* @public
* @param {string} [frameworkName] Framework name from the root project's ui5.yaml
* @param {string} [frameworkVersion] Framework version from the root project's ui5.yaml
* @param {module:@ui5/builder/processors/versionInfoGenerator~LibraryInfo[]} libraryInfos
* Library infos; each entry's optional <code>id</code> field is used for the npm-scope scan
* @returns {{name: string, version: string}} Resolved framework object
*/
export function resolveFramework(frameworkName, frameworkVersion, libraryInfos) {
if (frameworkName === "OpenUI5" || frameworkName === "SAPUI5") {
return {name: frameworkName, version: frameworkVersion || ""};
}
let openui5Detected = false;
let openui5CoreVersion = "";
for (const libraryInfo of libraryInfos) {
const {id} = libraryInfo;
if (id?.startsWith("@sapui5/")) {
return {name: "SAPUI5", version: ""};
}
if (id?.startsWith("@openui5/")) {
openui5Detected = true;
if (libraryInfo.name === "sap.ui.core") {
openui5CoreVersion = libraryInfo.version;
}
}
}
if (openui5Detected) {
return {name: "OpenUI5", version: openui5CoreVersion};
}
return {name: "", version: ""};
}

/**
* Creates sap-ui-version.json.
*
Expand All @@ -379,6 +424,11 @@ const processLibraryInfo = async (libraryInfo) => {
* }
* ```
*
* @param {string} [parameters.options.frameworkName] Framework name from the root project's ui5.yaml,
* e.g. <code>"OpenUI5"</code> or <code>"SAPUI5"</code>. Used for framework resolution.
* @param {string} [parameters.options.frameworkVersion] Framework version from the root project's ui5.yaml,
* e.g. <code>"1.136.0"</code>. Used only when <code>frameworkName</code> is recognized;
* defaults to <code>""</code> when omitted.
* @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with an array containing the versionInfo resource
*/

Expand Down Expand Up @@ -463,6 +513,7 @@ export default async function({options}) {
buildTimestamp: buildTimestamp,
scmRevision: "", // TODO: insert current application scm revision here
// gav: "", // TODO: insert current application id + version here
framework: resolveFramework(options.frameworkName, options.frameworkVersion, options.libraryInfos),
libraries,
components
};
Expand Down
5 changes: 4 additions & 1 deletion packages/builder/lib/tasks/generateVersionInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export default async ({workspace, dependencies, options: {rootProject, pattern}}
libraryManifest,
embeddedManifests,
name: dotLibResource.getProject().getName(),
version: dotLibResource.getProject().getVersion()
version: dotLibResource.getProject().getVersion(),
id: dotLibResource.getProject().getId?.()
};
});
});
Expand All @@ -51,6 +52,8 @@ export default async ({workspace, dependencies, options: {rootProject, pattern}}
options: {
rootProjectName: rootProject.getName(),
rootProjectVersion: rootProject.getVersion(),
frameworkName: rootProject.getFrameworkName?.(),
frameworkVersion: rootProject.getFrameworkVersion?.(),
libraryInfos
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"version": "1.0.0",
"buildTimestamp": "202008120917",
"scmRevision": "",
"framework": {
"name": "",
"version": ""
},
"libraries": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"version": "1.0.0",
"buildTimestamp": "WILL_BE_IGNORED_BY_TESTS",
"scmRevision": "",
"framework": {
"name": "",
"version": ""
},
"libraries": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
"version": "1.0.0",
"buildTimestamp": "WILL_BE_IGNORED_BY_TESTS",
"scmRevision": "",
"framework": {
"name": "",
"version": ""
},
"libraries": []
}
81 changes: 81 additions & 0 deletions packages/builder/test/lib/processors/versionInfoGenerator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test from "ava";
import sinon from "sinon";
import esmock from "esmock";
import {resolveFramework} from "../../../lib/processors/versionInfoGenerator.js";

test.beforeEach(async (t) => {
t.context.warnLogStub = sinon.stub();
Expand Down Expand Up @@ -37,6 +38,13 @@ const assertVersionInfoContent = (t, oExpectedVersionInfo, sActualContent) => {
delete lib.buildTimestamp; // removing to allow deep comparison
});

// The "framework" object is always emitted. When the expectation does not assert it
// explicitly, verify it defaults to empty strings and remove it to allow deep comparison.
if (!("framework" in oExpectedVersionInfo)) {
t.deepEqual(currentVersionInfo.framework, {name: "", version: ""},
"framework defaults to empty strings when no framework option is passed");
delete currentVersionInfo.framework;
}

t.deepEqual(currentVersionInfo, oExpectedVersionInfo, "Correct content");
};
Expand All @@ -59,6 +67,79 @@ test.serial("versionInfoGenerator empty libraryInfos parameter", async (t) => {
});


test.serial("versionInfoGenerator resolves framework from explicit frameworkName/frameworkVersion", async (t) => {
const {versionInfoGenerator} = t.context;
const versionInfos = await versionInfoGenerator({options: {
rootProjectName: "myname", rootProjectVersion: "1.33.7", libraryInfos: [],
frameworkName: "OpenUI5", frameworkVersion: "1.136.0"}});

const result = JSON.parse(await versionInfos[0].getString());
t.deepEqual(result.framework, {name: "OpenUI5", version: "1.136.0"});
});

// --- resolveFramework unit tests (pure function, no esmock needed) ---

test("resolveFramework: explicit OpenUI5 returns name and version", (t) => {
t.deepEqual(resolveFramework("OpenUI5", "1.136.0", []), {name: "OpenUI5", version: "1.136.0"});
});

test("resolveFramework: explicit SAPUI5 returns name and version", (t) => {
t.deepEqual(resolveFramework("SAPUI5", "1.136.0", []), {name: "SAPUI5", version: "1.136.0"});
});

test("resolveFramework: name-only config coalesces missing version to empty string (fixes #3)", (t) => {
t.deepEqual(resolveFramework("SAPUI5", undefined, []), {name: "SAPUI5", version: ""});
t.deepEqual(resolveFramework("OpenUI5", undefined, []), {name: "OpenUI5", version: ""});
});

test("resolveFramework: explicit config wins over dependency scan", (t) => {
const libs = [{id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"}];
t.deepEqual(resolveFramework("OpenUI5", "1.138.0", libs), {name: "OpenUI5", version: "1.138.0"});
});

test("resolveFramework: unrecognized frameworkName falls through to scan and then fallback", (t) => {
t.deepEqual(resolveFramework("CustomFramework", "1.0.0", []), {name: "", version: ""});
});

test("resolveFramework: scans @openui5/ dep and resolves OpenUI5 with sap.ui.core version", (t) => {
const libs = [
{id: "@openui5/sap.m", name: "sap.m", version: "1.136.0"},
{id: "@openui5/sap.ui.core", name: "sap.ui.core", version: "1.136.0"}
];
t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "OpenUI5", version: "1.136.0"});
});

test("resolveFramework: scans @openui5/ dep without sap.ui.core yields empty version", (t) => {
const libs = [{id: "@openui5/sap.m", name: "sap.m", version: "1.136.0"}];
t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "OpenUI5", version: ""});
});

test("resolveFramework: scans @sapui5/ dep and resolves SAPUI5 with empty version", (t) => {
const libs = [{id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"}];
t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "SAPUI5", version: ""});
});

test("resolveFramework: @sapui5/ dep short-circuits before @openui5/ accumulation", (t) => {
const libs = [
{id: "@openui5/sap.ui.core", name: "sap.ui.core", version: "1.136.0"},
{id: "@sapui5/sap.m", name: "sap.m", version: "1.136.0"}
];
t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "SAPUI5", version: ""});
});

test("resolveFramework: libs without id are ignored in scan (no-id fallback)", (t) => {
const libs = [
{name: "sap.m", version: "1.136.0"},
{id: undefined, name: "sap.ui.core", version: "1.136.0"}
];
t.deepEqual(resolveFramework(undefined, undefined, libs), {name: "", version: ""});
});

test("resolveFramework: empty libraryInfos returns fallback", (t) => {
t.deepEqual(resolveFramework(undefined, undefined, []), {name: "", version: ""});
});


test.serial("versionInfoGenerator simple library infos", async (t) => {
const {versionInfoGenerator} = t.context;
const options = {
Expand Down
Loading
Loading