Skip to content
Draft
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
42 changes: 42 additions & 0 deletions internal/documentation/docs/updates/migrate-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Or update your global install via: `npm i --global @ui5/cli@next`

- **@ui5/cli: `ui5 serve` renders a status banner in interactive terminals**

- **@ui5/project: UI5 framework resolver constructors now require explicit `ui5DataDir`**


## Node.js and npm Version Support

Expand Down Expand Up @@ -107,6 +109,46 @@ If you previously passed any of these options to a command that did not use them

The `ui5 init` command now generates projects with Specification Version 5.0 by default.

## Changes to @ui5/project (Node.js API)

When consuming the Node.js API, UI5 framework resolver constructors now require the `ui5DataDir` option.
This affects `Openui5Resolver`, `Sapui5Resolver`, and `Sapui5MavenSnapshotResolver`.

Previously, `ui5DataDir` was optional and resolver constructors implicitly resolved a fallback from
environment/configuration. In UI5 CLI v5, callers must resolve the UI5 data directory before constructing a
resolver and pass it explicitly. This change improves API clarity by making the dependency explicit.

Use [`resolveUi5DataDir`](../api/module-@ui5_project_utils_dataDir.md) to resolve the path once at your async
entry boundary and forward the resolved value to all APIs that need it.

::: code-group
```js [Before]
import Sapui5Resolver from "@ui5/project/ui5Framework/Sapui5Resolver";

const resolver = new Sapui5Resolver({
cwd: process.cwd(),
version: "1.120.15"
});
```

```js [After]
import Sapui5Resolver from "@ui5/project/ui5Framework/Sapui5Resolver";
import {resolveUi5DataDir} from "@ui5/project/utils/dataDir";

async function createResolver() {
const ui5DataDir = await resolveUi5DataDir({projectRootPath: process.cwd()});

const resolver = new Sapui5Resolver({
cwd: process.cwd(),
version: "1.120.15",
ui5DataDir
});

return resolver;
}
```
:::

## Component Type

The `component` type feature aims to introduce a new project type within the UI5 CLI ecosystem to support the development of UI5 component-like applications intended to run in container apps such as the Fiori Launchpad (FLP) Sandbox or testsuite environments.
Expand Down
18 changes: 3 additions & 15 deletions packages/cli/lib/framework/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "node:path";
import {graphFromStaticFile, graphFromPackageDependencies} from "@ui5/project/graph";
import Configuration from "@ui5/project/config/Configuration";
import {resolveUi5DataDir} from "@ui5/project/utils/dataDir";

export async function getRootProjectConfiguration(projectGraphOptions) {
let graph;
Expand Down Expand Up @@ -37,34 +36,23 @@ export async function createFrameworkResolverInstance({frameworkName, frameworkV
return new Resolver({
cwd,
version: frameworkVersion,
ui5DataDir: await utils.getUi5DataDir({cwd})
ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd})
});
}

export async function frameworkResolverResolveVersion({frameworkName, frameworkVersion}, {cwd}) {
const Resolver = await utils.getFrameworkResolver(frameworkName, frameworkVersion);
return Resolver.resolveVersion(frameworkVersion, {
cwd,
ui5DataDir: await utils.getUi5DataDir({cwd})
ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd})
});
}

async function getUi5DataDir({cwd}) {
// ENV var should take precedence over the dataDir from the configuration.
let ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
return ui5DataDir ? path.resolve(cwd, ui5DataDir) : undefined;
}

const utils = {
getRootProjectConfiguration,
getFrameworkResolver,
createFrameworkResolverInstance,
frameworkResolverResolveVersion,
getUi5DataDir
};
let _utils;
// For mocking of functions in unit tests and testing internal functions
Expand Down
89 changes: 15 additions & 74 deletions packages/cli/test/lib/framework/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import test from "ava";
import sinonGlobal from "sinon";
import esmock from "esmock";
import path from "node:path";

test.beforeEach(async (t) => {
// Tests either rely on not having UI5_DATA_DIR defined, or explicitly define it
Expand All @@ -21,12 +20,7 @@ test.beforeEach(async (t) => {
t.context.Openui5Resolver = sinon.stub();
t.context.Sapui5MavenSnapshotResolver = sinon.stub();

t.context.ConfigurationGetUi5DataDirStub = sinon.stub().returns(undefined);
t.context.ConfigurationStub = {
fromFile: sinon.stub().resolves({
getUi5DataDir: t.context.ConfigurationGetUi5DataDirStub
})
};
t.context.resolveUi5DataDirStub = sinon.stub().resolves("my-default-ui5-data-dir");

t.context.utils = await esmock.p("../../../lib/framework/utils.js", {
"@ui5/project/graph": {
Expand All @@ -42,7 +36,9 @@ test.beforeEach(async (t) => {
"@ui5/project/ui5Framework/Sapui5MavenSnapshotResolver": {
default: t.context.Sapui5MavenSnapshotResolver
},
"@ui5/project/config/Configuration": t.context.ConfigurationStub
"@ui5/project/utils/dataDir": {
resolveUi5DataDir: t.context.resolveUi5DataDirStub
}
});
t.context._utils = t.context.utils._utils;
});
Expand Down Expand Up @@ -142,13 +138,13 @@ test.serial("getFrameworkResolver: Invalid framework.name", async (t) => {
});
});

test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => {
test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses resolved default)", async (t) => {
const {createFrameworkResolverInstance} = t.context.utils;
const {sinon} = t.context;
const {sinon, resolveUi5DataDirStub} = t.context;

const ResolverStub = sinon.stub().returns({});
sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub);
sinon.stub(t.context._utils, "getUi5DataDir").resolves(undefined);
resolveUi5DataDirStub.resolves("my-default-ui5-data-dir");

const result = await createFrameworkResolverInstance({
frameworkName: "<framework-name>",
Expand All @@ -163,12 +159,7 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) =>
"<framework-version>"
]);

t.is(t.context._utils.getUi5DataDir.callCount, 1);
t.deepEqual(t.context._utils.getUi5DataDir.getCall(0).args, [
{
cwd: "my-project-path"
}
]);
t.is(resolveUi5DataDirStub.callCount, 1);

t.is(ResolverStub.callCount, 1);
t.is(result, ResolverStub.getCall(0).returnValue);
Expand All @@ -177,18 +168,18 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) =>
{
cwd: "my-project-path",
version: "<framework-version>",
ui5DataDir: undefined
ui5DataDir: "my-default-ui5-data-dir"
}
]);
});

test.serial("createFrameworkResolverInstance: With ui5DataDir", async (t) => {
const {createFrameworkResolverInstance} = t.context.utils;
const {sinon} = t.context;
const {sinon, resolveUi5DataDirStub} = t.context;

const ResolverStub = sinon.stub().returns({});
sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub);
sinon.stub(t.context._utils, "getUi5DataDir").resolves("my-ui5-data-dir");
resolveUi5DataDirStub.resolves("my-ui5-data-dir");

const result = await createFrameworkResolverInstance({
frameworkName: "<framework-name>",
Expand All @@ -203,12 +194,7 @@ test.serial("createFrameworkResolverInstance: With ui5DataDir", async (t) => {
"<framework-version>"
]);

t.is(t.context._utils.getUi5DataDir.callCount, 1);
t.deepEqual(t.context._utils.getUi5DataDir.getCall(0).args, [
{
cwd: "my-project-path"
}
]);
t.is(resolveUi5DataDirStub.callCount, 1);

t.is(ResolverStub.callCount, 1);
t.is(result, ResolverStub.getCall(0).returnValue);
Expand All @@ -224,13 +210,13 @@ test.serial("createFrameworkResolverInstance: With ui5DataDir", async (t) => {

test.serial("frameworkResolverResolveVersion", async (t) => {
const {frameworkResolverResolveVersion} = t.context.utils;
const {sinon} = t.context;
const {sinon, resolveUi5DataDirStub} = t.context;

const resolveVersionStub = sinon.stub().resolves("1.111.1");
sinon.stub(t.context._utils, "getFrameworkResolver").resolves({
resolveVersion: resolveVersionStub
});
sinon.stub(t.context._utils, "getUi5DataDir").resolves(undefined);
resolveUi5DataDirStub.resolves("my-default-ui5-data-dir");

const result = await frameworkResolverResolveVersion({
frameworkName: "SAPUI5",
Expand All @@ -246,52 +232,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => {
"latest",
{
cwd: "my-project-path",
ui5DataDir: undefined
ui5DataDir: "my-default-ui5-data-dir"
}
]);
});

test.serial("getUi5DataDir: no value defined", async (t) => {
const {ConfigurationGetUi5DataDirStub} = t.context;
const {getUi5DataDir} = t.context._utils;

const result = await getUi5DataDir({
cwd: path.resolve("foo")
});

t.is(result, undefined);

t.is(ConfigurationGetUi5DataDirStub.callCount, 1);
});

test.serial("getUi5DataDir: from environment variable", async (t) => {
const {ConfigurationGetUi5DataDirStub} = t.context;
const {getUi5DataDir} = t.context._utils;

// Environment variable must be preferred over configuration value
ConfigurationGetUi5DataDirStub.returns(".ui5-data-dir-from-configuration");
process.env.UI5_DATA_DIR = ".ui5-data-dir-from-env-variable";

const result = await getUi5DataDir({
cwd: path.resolve("foo")
});

t.is(result, path.join(path.resolve("foo"), ".ui5-data-dir-from-env-variable"));

t.is(ConfigurationGetUi5DataDirStub.callCount, 0);
});

test.serial("getUi5DataDir: from Configuration", async (t) => {
const {ConfigurationGetUi5DataDirStub} = t.context;
const {getUi5DataDir} = t.context._utils;

ConfigurationGetUi5DataDirStub.returns(".ui5-data-dir-from-configuration");

const result = await getUi5DataDir({
cwd: path.resolve("foo")
});

t.is(result, path.join(path.resolve("foo"), ".ui5-data-dir-from-configuration"));

t.is(ConfigurationGetUi5DataDirStub.callCount, 1);
});
28 changes: 8 additions & 20 deletions packages/project/lib/build/cache/CacheManager.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import path from "node:path";
import os from "node:os";
import Configuration from "../../config/Configuration.js";
import {getLogger} from "@ui5/logger";
import BuildCacheStorage from "./BuildCacheStorage.js";
import os from "node:os";

const log = getLogger("build:cache:CacheManager");

Expand Down Expand Up @@ -60,31 +59,20 @@ export default class CacheManager {
* Returns a singleton CacheManager for the determined cache directory.
* The cache directory is resolved in this order:
* 1. Explicit <code>ui5DataDir</code> option (resolved relative to cwd)
* 2. UI5_DATA_DIR environment variable (resolved relative to cwd)
* 3. ui5DataDir from UI5 configuration file
* 4. Default: ~/.ui5/
* 2. UI5_DATA_DIR environment variable or ui5DataDir from configuration file
* (resolved relative to cwd via {@link resolveUi5DataDir})
* 3. Default: ~/.ui5/
*
* @public
* @param {string} cwd Current working directory for resolving relative paths
* @param {object} [options]
* @param {string} [options.ui5DataDir] Explicit UI5 data directory. When provided,
* environment variable, configuration file, and home-directory fallbacks are skipped.
* @returns {Promise<CacheManager>} Singleton CacheManager instance for the cache directory
*/
static async create(cwd, {ui5DataDir} = {}) {
if (!ui5DataDir) {
// ENV var should take precedence over the dataDir from the configuration.
ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
}
if (ui5DataDir) {
ui5DataDir = path.resolve(cwd, ui5DataDir);
} else {
ui5DataDir = path.join(os.homedir(), ".ui5");
}
static async create({ui5DataDir} = {}) {
ui5DataDir = path.resolve(
ui5DataDir || path.join(os.homedir(), ".ui5")
);
const cacheDir = path.join(ui5DataDir, "buildCache");
log.verbose(`Using build cache directory: ${cacheDir}`);

Expand Down
6 changes: 4 additions & 2 deletions packages/project/lib/build/helpers/BuildContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {getBaseSignature} from "./getBuildSignature.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("build:helpers:BuildContext");
import Cache from "../cache/Cache.js";
import {resolveUi5DataDir} from "../../utils/dataDir.js";

/**
* Context of a build process
Expand Down Expand Up @@ -170,8 +171,9 @@ class BuildContext {
if (this.#cacheManager) {
return this.#cacheManager;
}
this.#cacheManager = await CacheManager.create(this._graph.getRoot().getRootPath(), {
ui5DataDir: this._ui5DataDir,
this.#cacheManager = await CacheManager.create({
ui5DataDir: this._ui5DataDir ??
await resolveUi5DataDir({projectRootPath: this.getRootProject().getRootPath()}),
});
return this.#cacheManager;
}
Expand Down
15 changes: 4 additions & 11 deletions packages/project/lib/graph/helpers/ui5Framework.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import Module from "../Module.js";
import ProjectGraph from "../ProjectGraph.js";
import {getLogger} from "@ui5/logger";
const log = getLogger("graph:helpers:ui5Framework");
import Configuration from "../../config/Configuration.js";
import path from "node:path";
import {resolveUi5DataDir} from "../../utils/dataDir.js";

class ProjectProcessor {
constructor({libraryMetadata, graph, workspace}) {
Expand Down Expand Up @@ -348,15 +347,9 @@ export default {
Resolver = (await import("../../ui5Framework/Sapui5Resolver.js")).default;
}

// ENV var should take precedence over the dataDir from the configuration.
let ui5DataDir = process.env.UI5_DATA_DIR;
if (!ui5DataDir) {
const config = await Configuration.fromFile();
ui5DataDir = config.getUi5DataDir();
}
if (ui5DataDir) {
ui5DataDir = path.resolve(cwd, ui5DataDir);
}
// Resolve the UI5 data directory using the shared utility.
// Returns ~/.ui5 by default when no env var or configuration is set.
const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd});

if (options.versionOverride) {
version = await Resolver.resolveVersion(options.versionOverride, {
Expand Down
Loading
Loading