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
1 change: 1 addition & 0 deletions packages/project/lib/build/helpers/MonitoredTaskUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const TRACKED_TASK_UTIL_METHODS = {
getEnv: "env",
getTime: "time",
isRootProject: "isRootProject",
getResolvedFrameworkVersion: "resolvedFrameworkVersion",
};

// Tracked methods whose first argument is the input name (the environment variable name, the time
Expand Down
13 changes: 13 additions & 0 deletions packages/project/lib/build/helpers/ProjectBuildContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ class ProjectBuildContext {
return undefined;
}
rawValue = quantizeTime(name, this.getBuildTime());
} else if (type === "resolvedFrameworkVersion") {
rawValue = this.getResolvedFrameworkVersion();
} else if (type.startsWith("project.")) {
const method = type.slice("project.".length);
const project = this.getProject(name);
Expand Down Expand Up @@ -206,6 +208,17 @@ class ProjectBuildContext {
return this._project;
}

/**
* Returns the effective framework version resolved for this graph during enrichment
* (reflects --framework-version, or a workspace link), or the root
* project's configured version when no resolution occurred.
*
* @returns {string|undefined} Effective framework version, e.g. "1.138.0"
*/
getResolvedFrameworkVersion() {
return this._buildContext.getGraph().getResolvedFrameworkVersion();
}

/**
* Retrieves a list of direct dependencies of a given project from the dependency graph
*
Expand Down
11 changes: 11 additions & 0 deletions packages/project/lib/build/helpers/TaskUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@ class TaskUtil {
return this._projectBuildContext.getBuildTime();
}

/**
* Returns the effective framework version resolved for the build's graph during enrichment
* (reflects --framework-version, or a workspace link), or the root
* project's configured version when no resolution occurred.
*
* @returns {string|undefined} Effective framework version, e.g. "1.138.0"
*/
getResolvedFrameworkVersion() {
return this._projectBuildContext.getResolvedFrameworkVersion();
}

/**
* Check whether the project currently being built is the root project.
*
Expand Down
27 changes: 27 additions & 0 deletions packages/project/lib/graph/ProjectGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class ProjectGraph {
this._sealed = false;
this._hasUnresolvedOptionalDependencies = false; // Performance optimization flag
this._taskRepository = null;
this._resolvedFrameworkVersion = null;
}

/**
Expand Down Expand Up @@ -675,6 +676,32 @@ class ProjectGraph {
this._taskRepository = taskRepository;
}

/**
* Set the framework version resolved for this graph during enrichment.
*
* @public
* @param {string} version Effective framework version
*/
setResolvedFrameworkVersion(version) {
this._resolvedFrameworkVersion = version;
}

/**
* Get the effective framework version for this graph, as resolved during enrichment.
*
* Returns the value set by {@link #setResolvedFrameworkVersion}. When enrichment has not run
* (e.g. the project has no framework configuration), the stored value is <code>null</code> and
* this falls back to the root project's configured <code>framework.version</code>.
*
* @public
* @returns {string|undefined} Effective framework version, e.g. "1.138.0", or <code>""</code>
* when the framework is active but has no resolvable distribution version (SAPUI5 + workspace),
* or the configured version as a fallback when enrichment did not run.
*/
getResolvedFrameworkVersion() {
return this._resolvedFrameworkVersion ?? this.getRoot().getFrameworkVersion();
}

async _getTaskRepository() {
if (!this._taskRepository) {
try {
Expand Down
40 changes: 31 additions & 9 deletions packages/project/lib/graph/helpers/ui5Framework.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,15 +399,6 @@ export default {

const {libraryMetadata} = await resolver.install(referencedLibraries);

if (version) {
process.emit("ui5.project-framework-resolved", {
framework: {
name: frameworkName,
version,
},
});
}

if (log.isLevelEnabled("verbose")) {
const timeDiff = process.hrtime(startTime);
const {default: prettyHrtime} = await import("pretty-hrtime");
Expand Down Expand Up @@ -435,6 +426,37 @@ export default {
log.verbose("Joining framework graph into project graph...");
projectGraph.join(frameworkGraph);
await utils.declareFrameworkDependenciesInGraph(projectGraph);

// Without a workspace, `version` is the effective framework version resolved above (it already
// reflects --framework-version and the ui5.yaml config). With a workspace that config is
// ignored: OpenUI5 reports the linked sap.ui.core version, while SAPUI5 has no reliable
// distribution version and is left empty.
let resolvedFrameworkVersion;
if (workspace) {
if (frameworkName === "SAPUI5") {
resolvedFrameworkVersion = "";
} else {
resolvedFrameworkVersion = projectGraph.getProject("sap.ui.core")?.getVersion() ?? "";
}
} else {
resolvedFrameworkVersion = version;
}
projectGraph.setResolvedFrameworkVersion(resolvedFrameworkVersion);

// Emit after workspace-aware resolution so the version reflects the actual resolved
// version (e.g. the linked sap.ui.core for OpenUI5 workspace) rather than the
// configured ui5.yaml version that may differ.
// Always emit when a workspace is active (framework name is known even if version is ""),
// and when no workspace but a concrete version was resolved.
if (workspace || resolvedFrameworkVersion) {
process.emit("ui5.project-framework-resolved", {
framework: {
name: frameworkName,
version: resolvedFrameworkVersion,
},
});
}

return projectGraph;
},

Expand Down
9 changes: 9 additions & 0 deletions packages/project/test/lib/build/helpers/MonitoredTaskUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test.beforeEach((t) => {
getEnv: sinon.stub().callsFake((name) => (name === "SET" ? "on" : undefined)),
getTime: sinon.stub().callsFake((granularity) => (granularity === "year" ? "2026" : "2026-09-25")),
isRootProject: sinon.stub().returns(true),
getResolvedFrameworkVersion: sinon.stub().returns("1.138.0"),
getDependencies: sinon.stub().returns(["dep.a", "dep.b"]),
getProject: sinon.stub().callsFake((name) => {
if (name === undefined || name === "sap.ui.core") {
Expand Down Expand Up @@ -95,6 +96,14 @@ test("records isRootProject reads", (t) => {
]);
});

test("records getResolvedFrameworkVersion reads", (t) => {
const monitored = new MonitoredTaskUtil(t.context.taskUtil);
t.is(monitored.getResolvedFrameworkVersion(), "1.138.0");
t.deepEqual(monitored.getInputRecording(), [
{type: "resolvedFrameworkVersion", name: "", value: "1.138.0"},
]);
});

test("records getDependencies reads, resolving the default project name", (t) => {
const monitored = new MonitoredTaskUtil(t.context.taskUtil);

Expand Down
35 changes: 35 additions & 0 deletions packages/project/test/lib/build/helpers/ProjectBuildContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,19 @@ test("getProject: No name provided", (t) => {
t.is(getProjectStub.callCount, 0, "ProjectGraph#getProject has not been called");
});

test("getResolvedFrameworkVersion delegates to graph", (t) => {
const buildContext = createBuildContextStub({
getGraph: () => ({
getResolvedFrameworkVersion: () => "1.138.0"
})
});
const project = {getName: () => "project", getType: () => "type"};
const projectBuildContext = new ProjectBuildContext(buildContext, project);

t.is(projectBuildContext.getResolvedFrameworkVersion(), "1.138.0",
"Returns the graph's effective framework version");
});

test("getDependencies", (t) => {
const project = {
getName: () => "project",
Expand Down Expand Up @@ -466,6 +479,28 @@ test("resolveInputValue: isRootProject normalizes the boolean", (t) => {
t.is(projectBuildContext.resolveInputValue("isRootProject", ""), "true");
});

test("resolveInputValue: resolvedFrameworkVersion normalizes the version string", (t) => {
const buildContext = createBuildContextStub({
getGraph: () => ({getResolvedFrameworkVersion: () => "1.138.0"})
});
const project = {getName: () => "project", getType: () => "type"};
const projectBuildContext = new ProjectBuildContext(buildContext, project);

t.is(projectBuildContext.resolveInputValue("resolvedFrameworkVersion", ""), "1.138.0",
"Returns the resolved framework version as-is (string passthrough)");
});

test("resolveInputValue: resolvedFrameworkVersion normalizes empty string (SAPUI5 workspace)", (t) => {
const buildContext = createBuildContextStub({
getGraph: () => ({getResolvedFrameworkVersion: () => ""})
});
const project = {getName: () => "project", getType: () => "type"};
const projectBuildContext = new ProjectBuildContext(buildContext, project);

t.is(projectBuildContext.resolveInputValue("resolvedFrameworkVersion", ""), "",
"Returns empty string for SAPUI5 workspace (no distribution version)");
});

test("resolveInputValue: getDependencies normalizes the array via the graph", (t) => {
const getDependencies = sinon.stub().returns(["dep.a", "dep.b"]);
const buildContext = createBuildContextStub({
Expand Down
22 changes: 22 additions & 0 deletions packages/project/test/lib/build/helpers/TaskUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,28 @@ test("getBuildTime returns the build run's raw timestamp", (t) => {
t.is(taskUtil.getBuildTime(), buildTime, "returns the build context's Date instance");
});

test("getResolvedFrameworkVersion delegates to projectBuildContext", (t) => {
const taskUtil = new TaskUtil({
projectBuildContext: {
getResolvedFrameworkVersion: () => "1.138.0"
}
});

t.is(taskUtil.getResolvedFrameworkVersion(), "1.138.0",
"Returns the value from ProjectBuildContext.getResolvedFrameworkVersion()");
});

test("getResolvedFrameworkVersion returns undefined when unset (fallback passes through)", (t) => {
const taskUtil = new TaskUtil({
projectBuildContext: {
getResolvedFrameworkVersion: () => undefined
}
});

t.is(taskUtil.getResolvedFrameworkVersion(), undefined,
"Undefined propagates so the processor uses its own fallback");
});

test("registerCleanupTask", (t) => {
const registerCleanupTaskStub = sinon.stub();
const taskUtil = new TaskUtil({
Expand Down
34 changes: 34 additions & 0 deletions packages/project/test/lib/graph/ProjectGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -1934,3 +1934,37 @@ test("Seal/isSealed", async (t) => {
const extension = graph.getExtension("extension.b");
t.is(extension, undefined, "extension.b should not be added");
});

test("getResolvedFrameworkVersion: returns value set by setResolvedFrameworkVersion", async (t) => {
const {ProjectGraph} = t.context;
const rootProject = await createProject("root");
const graph = new ProjectGraph({rootProjectName: "root"});
await graph.addProject(rootProject);

graph.setResolvedFrameworkVersion("1.138.0");
t.is(graph.getResolvedFrameworkVersion(), "1.138.0");
});

test("getResolvedFrameworkVersion: falls back to root project getFrameworkVersion() when unset", async (t) => {
const {ProjectGraph} = t.context;
const rootProject = await createProject("root");
// Stub getFrameworkVersion on the project instance
rootProject.getFrameworkVersion = () => "1.136.0";
const graph = new ProjectGraph({rootProjectName: "root"});
await graph.addProject(rootProject);

t.is(graph.getResolvedFrameworkVersion(), "1.136.0",
"Should fall back to the root project's configured version");
});

test("getResolvedFrameworkVersion: setResolvedFrameworkVersion overrides configured root version", async (t) => {
const {ProjectGraph} = t.context;
const rootProject = await createProject("root");
rootProject.getFrameworkVersion = () => "1.136.0";
const graph = new ProjectGraph({rootProjectName: "root"});
await graph.addProject(rootProject);

graph.setResolvedFrameworkVersion("1.138.0");
t.is(graph.getResolvedFrameworkVersion(), "1.138.0",
"Set value must take precedence over configured root version");
});
Loading
Loading