Skip to content
Open
5 changes: 3 additions & 2 deletions internal/documentation/docs/pages/Builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ All available standard tasks are documented under **API -> @ui5/builder -> tasks
| [executeJsdocSdkTransformation](../api/module-@ui5_builder_tasks_jsdoc_executeJsdocSdkTransformation) | | | *disabled* <sup>1</sup> | |
| [minify](../api/module-@ui5_builder_tasks_minify) | enabled | enabled | enabled | |
| [generateFlexChangesBundle](../api/module-@ui5_builder_tasks_bundlers_generateFlexChangesBundle) | enabled | enabled | enabled | |
| [generateLibraryManifest](../api/module-@ui5_builder_tasks_generateLibraryManifest) | | | enabled | |
| [generateLibraryManifest](../api/module-@ui5_builder_tasks_generateLibraryManifest) | | | *disabled* <sup>7</sup> | |
| [enhanceManifest](../api/module-@ui5_builder_tasks_enhanceManifest) | enabled | enabled | enabled | |
| [generateComponentPreload](../api/module-@ui5_builder_tasks_bundlers_generateComponentPreload) | enabled | enabled | *disabled* <sup>2</sup> | |
| [generateLibraryPreload](../api/module-@ui5_builder_tasks_bundlers_generateLibraryPreload) | | | enabled | |
Expand All @@ -58,7 +58,8 @@ All available standard tasks are documented under **API -> @ui5/builder -> tasks
<sup>3</sup> Enabled in `self-contained` build, which disables `generateComponentPreload` and `generateLibraryPreload`
<sup>4</sup> Enabled for projects defining a [bundle configuration](./Configuration.md#custom-bundling)
<sup>5</sup> Can be enabled for framework projects via the `includeTask` option. For other projects, this task is skipped
<sup>6</sup> Disabled for the server due to a corresponding middleware producing the same output
<sup>6</sup> Disabled for the server due to a corresponding middleware producing the same output
<sup>7</sup> Enabled for Specification Version 4.0 and lower, and for framework projects. For other projects using Specification Version 5.0 and higher, this task is skipped

### minify

Expand Down
4 changes: 4 additions & 0 deletions internal/documentation/docs/pages/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,10 @@ Version | UI5 CLI Release

### Specification Version 5.0

**Breaking changes:**

- The `generateLibraryManifest` build task is no longer executed for projects of type `library`. SAPUI5 distribution libraries (framework projects) are not affected. Libraries must provide a `manifest.json` directly in their source directory. See [Migrate to v5: generateLibraryManifest Task No Longer Executed](../updates/migrate-v5.md#generatelibrarymanifest-task-no-longer-executed) for details.

**Features:**

- Adds support for the new [`component`](./Project.md#component) project type for developing UI5 components — including application, reusable UI, and faceless components — which, unlike `application`-type projects, are served under their own namespace so multiple can coexist in one environment
Expand Down
17 changes: 17 additions & 0 deletions internal/documentation/docs/updates/migrate-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ UI5 CLI 5.x introduces **Specification Version 5.0**, which enables the new Comp

Projects using older **Specification Versions** are expected to be **fully compatible with UI5 CLI v5**.

## generateLibraryManifest Task No Longer Executed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should be reviewed again by UA, but my thoughts would be:
Does the title still explain the changes well enough? It is not wrong, but something that describes the new expectations with specVersion 5, i.e. "manifest.json is mandatory" might be easer to understand for consumers, who might not even have heard of the task name before.

@d3xter666 d3xter666 Aug 26, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my perspective, it states clear what we have done and what action needs to be taken- we have disabled {some task name} and manifest.json is now mandatory when building a library. Further more, 7cc5d60 enforces develoeprs to provide a manifest.json


::: info Specification Version 5.0 only
This change only applies to library projects that upgrade their `specVersion` to `5.0` in `ui5.yaml`. Projects on **Specification Version 4.0 and lower are not affected**. **SAPUI5 distribution libraries (framework projects) are not affected**.
:::

With **Specification Version 5.0**, the [`generateLibraryManifest`](../api/module-@ui5_builder_tasks_generateLibraryManifest) build task is no longer executed for non-framework library projects. Libraries must provide a `manifest.json` directly in their source directory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take directly in their source directory literally, then it would be at src/manifest.json, right? This would be wrong if the library uses folders for the namespace (like src/sap/ui/core/). I think we should make sure the expectations are described in a way that they cannot be misunderstood.
Saying that it needs to be placed next to library.js would be a good way of explaining what to do, without having to explain the two different ways of structuring the sources.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to judge on developers' experience and knowledge. It's clear now that they need to provide a manifest.json for the library project. It's event enforced now with 7cc5d60.

IMO, we must not mix/couple our documentation with UI5 runtime information. It must be clear from the UI5 documentation where certain files must be positioned and all the edge cases that might come out of that.


**Action required** when upgrading a library project to Specification Version 5.0:

- Ensure your library has a `manifest.json` in its source directory. A previously auto-generated one is fully compatible and can be reused as-is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have described this more literally, by telling to run a build before changing the specVersion and copy over the manifest.json file (located next to the library.js file) from the build result into the sources.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel this is the correct way to describe this. For me, it feels like a workaround over the old specVersion, so that you can run the code with the new one.
Then, if something had a better experience previously, why do we do this migration?

- If no `manifest.json` is present, any UI5 CLI command that resolves the project graph (such as `ui5 build`, `ui5 serve`, or `ui5 tree`) will fail with a descriptive error and a link to this migration guide.

::: tip
To see which standard tasks are executed for each project type, check out the [Standard Tasks](../pages/Builder#standard-tasks) table in the UI5 Builder page.
:::

## Build Cache

UI5 CLI v5 introduces **builds with caching** for both the `ui5 build` and `ui5 serve` commands. This fundamental architectural change significantly improves build performance by reusing cached results from previous builds. It also simplifies development with the server by making most custom middleware obsolete.
Expand Down
7 changes: 6 additions & 1 deletion packages/project/lib/build/definitions/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ export default function({project, taskUtil, getTask}) {
}
});

tasks.set("generateLibraryManifest", {});
// For specVersion 5.0+, only execute for framework libraries
if (project.getSpecVersion().lt("5.0") || project.isFrameworkProject()) {
tasks.set("generateLibraryManifest", {});
} else {
tasks.set("generateLibraryManifest", {taskFunction: null});
Comment thread
matz3 marked this conversation as resolved.
}

tasks.set("enhanceManifest", {});

Expand Down
17 changes: 17 additions & 0 deletions packages/project/lib/specifications/types/Library.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,23 @@ class Library extends ComponentProject {
async _parseConfiguration(config, buildManifest) {
await super._parseConfiguration(config, buildManifest);

// For Specification Version 5.0+, non-framework libraries must provide a manifest.json
if (this.getSpecVersion().gte("5.0") && !this.isFrameworkProject()) {
try {
await this._getManifest();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the check is done before the if (buildManifest) { check and early return, but for Application.js / Component.js the check / error handling is done after (via _getNamespace). I'm not sure whether it makes a difference, but if it does, I assume we should align both places, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildManifest is not the manifest itself in some form. Please, take a look at the JSDoc: @param {object} buildManifest Cache metadata object.

There's a manifest check in the _getNamespace() -> _getNamespaceFromManifest(), but it can easily be skipped i.e. this early return for buildManifest

Yes, _getNamespace() requests the manifest, but its sole purpose it to extract the namespace. Therefore, messing in there would make the solution quite more complex.
For Application type we do not have these constraints we have for Libraries!

Given that, my preference would be to keep this solution as clearer and more maintainable

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not what I meant. Yes, buildManifest != manifest.json.

If an application project is already built, and therefore buildManifest is supplied, then we do not perform a check for its manifest.json file because we rely on the build that happened before to check for such constraints. The buildManifest provides the namespace, so we can use it from there.
The same happens for component projects.

But now if a library project is already built, the existence of a manifest.json is still checked.
This is my point. I have not checked whether it causes issues (or if it is even covered by tests), but to me this is not consistent with our existing handling in other types.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and now, after this task, the manifest.json is not part of the build anymore. It is additionally provided as a resource.
It must not be treated as a build artifact or something. As long as we rely on this fact, we must check its existence everytime.
As far as I got it from the code, the namespace for buildManifest might not come from the manifest.json

} catch (err) {
throw new Error(
`Could not find required manifest.json for library project ${this.getName()}: ` +
`${err.message}\n\n` +
`Library projects using Specification Version 5.0 or higher must provide ` +
`a manifest.json directly in their source directory.\n` +
`For migration details, please refer to:\n` +
`https://ui5.github.io/cli/updates/migrate-v5/#generatelibrarymanifest-task-no-longer-executed`, {
cause: err
});
}
}

if (buildManifest) {
this._namespace = buildManifest.namespace;
return;
Expand Down
7 changes: 7 additions & 0 deletions packages/project/test/lib/build/ProjectBuilder.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,11 @@ test.serial("Build library.d (Custom Library preload configuration)", async (t)
// This custom preload configuration generates a library-preload.js similar to a default one.
// However, it will omit a resource ("some.js") from the bundle.

// ui5-custom-preload-config.yaml uses specVersion 5.0, which requires a manifest.json
await fixtureTester._initialize();
await fs.writeFile(`${fixtureTester.fixturePath}/main/src/library/d/manifest.json`,
JSON.stringify({"sap.app": {"id": "library.d", "type": "library"}}, null, "\t"));

// #1 build (no cache, no changes)
await fixtureTester.buildProject({
graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"},
Expand Down Expand Up @@ -3064,6 +3069,8 @@ sap.ui.define([
};
return thisLib;
});`);
await fs.writeFile(`${this.fixturePath}/node_modules/library.z/src/library/z/manifest.json`,
JSON.stringify({"sap.app": {"id": "library.z", "type": "library"}}, null, "\t"));
await fs.writeFile(`${this.fixturePath}/node_modules/library.z/src/library/z/.library`,
`<?xml version="1.0" encoding="UTF-8" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd" >
Expand Down
3 changes: 2 additions & 1 deletion packages/project/test/lib/build/TaskRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ function getMockProject(type) {
getMinificationExcludes: emptyarray,
getSpecVersion: () => {
return {
gte: () => false
gte: () => false,
lt: () => true
};
},
getComponentPreloadPaths: () => [
Expand Down
45 changes: 42 additions & 3 deletions packages/project/test/lib/build/definitions/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ function getMockProject() {
getSpecVersion: () => {
return {
toString: () => "2.6",
gte: () => true
gte: () => true,
lt: () => true
};
},
getMinificationExcludes: emptyarray,
Expand Down Expand Up @@ -187,7 +188,8 @@ test("Standard build with legacy spec version", (t) => {
project.getSpecVersion = () => {
return {
toString: () => "0.1",
gte: () => false
gte: () => false,
lt: () => true
};
};

Expand Down Expand Up @@ -507,7 +509,8 @@ test("Minification excludes not applied for legacy specVersion", (t) => {
project.getSpecVersion = () => {
return {
toString: () => "2.5",
gte: () => false
gte: () => false,
lt: () => true
};
};
project.getMinificationExcludes = () => ["**.html"];
Expand Down Expand Up @@ -639,6 +642,42 @@ test("buildThemes: Project is not root", (t) => {
}
}, "Correct buildThemes task definition");
});
test("generateLibraryManifest: specVersion 5.0, non-framework project", (t) => {
const {project, taskUtil, getTask} = t.context;

project.getSpecVersion = () => {
return {
toString: () => "5.0",
gte: () => true,
lt: () => false
};
};
project.isFrameworkProject = () => false;

const tasks = library({project, taskUtil, getTask});

t.deepEqual(tasks.get("generateLibraryManifest"), {taskFunction: null},
"generateLibraryManifest is skipped for non-framework libraries on specVersion 5.0");
});

test("generateLibraryManifest: specVersion 5.0, framework project", (t) => {
const {project, taskUtil, getTask} = t.context;

project.getSpecVersion = () => {
return {
toString: () => "5.0",
gte: () => true,
lt: () => false
};
};
project.isFrameworkProject = () => true;

const tasks = library({project, taskUtil, getTask});

t.deepEqual(tasks.get("generateLibraryManifest"), {},
"generateLibraryManifest runs for framework libraries on specVersion 5.0");
});

test("buildThemes: CSS Variables enabled", (t) => {
const {project, taskUtil, getTask} = t.context;
taskUtil.getBuildOption.returns(true);
Expand Down
43 changes: 43 additions & 0 deletions packages/project/test/lib/specifications/types/Library.js
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,49 @@ test.serial("_parseConfiguration: No preload exclude fallback for non-framework
t.is(getPreloadExcludesFromDotLibraryStub.callCount, 0, "_getPreloadExcludesFromDotLibrary has not been called");
});

test.serial("_parseConfiguration: Missing manifest.json throws for specVersion 5.0 non-framework library",
async (t) => {
const {projectInput, sinon} = t.context;
projectInput.configuration.specVersion = "5.0";

sinon.stub(Library.prototype, "isFrameworkProject").returns(false);
sinon.stub(Library.prototype, "_getManifest").rejects(
new Error("Could not find manifest.json file for project library.d"));
sinon.stub(Library.prototype, "_getNamespace").resolves("library/d");

const error = await t.throwsAsync(new Library().init(projectInput));

t.true(error.message.includes("Could not find required manifest.json for library project library.d"),
"Error message mentions missing manifest.json");
t.true(error.message.includes("migrate-v5"),
"Error message references the migration guide");
});

test.serial("_parseConfiguration: Missing manifest.json is allowed for specVersion 5.0 framework library",
async (t) => {
const {projectInput, sinon} = t.context;
projectInput.configuration.specVersion = "5.0";

sinon.stub(Library.prototype, "isFrameworkProject").returns(true);
sinon.stub(Library.prototype, "_getManifest").rejects(
new Error("Could not find manifest.json file for project library.d"));

await t.notThrowsAsync(new Library().init(projectInput),
"No error thrown for framework library without manifest.json");
});

test.serial("_parseConfiguration: Missing manifest.json is allowed for specVersion 4.0 library", async (t) => {
const {projectInput, sinon} = t.context;
projectInput.configuration.specVersion = "4.0";

sinon.stub(Library.prototype, "isFrameworkProject").returns(false);
sinon.stub(Library.prototype, "_getManifest").rejects(
new Error("Could not find manifest.json file for project library.d"));

await t.notThrowsAsync(new Library().init(projectInput),
"No error thrown for specVersion 4.0 library without manifest.json");
});

test("_getManifest: Reads correctly", async (t) => {
const {projectInput, sinon} = t.context;

Expand Down
Loading