From f42dd974ce1315761316a9d7c247cfa46fba8030 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 09:44:34 +0300 Subject: [PATCH 01/20] refactor: Consolidate UI5 data directory resolution into resolveUi5DataDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces packages/project/lib/utils/dataDir.js with a single resolveUi5DataDir() utility that encapsulates the full resolution chain: 1. UI5_DATA_DIR environment variable 2. ui5DataDir from configuration file (~/.ui5rc) 3. Default: ~/.ui5 Removes the duplicate getUi5DataDir() function from packages/cli/lib/framework/utils.js and migrates its callers (createFrameworkResolverInstance, frameworkResolverResolveVersion) to use resolveUi5DataDir() directly. The defensive fallbacks in the public Resolver/Installer APIs (AbstractResolver, Openui5Resolver, Sapui5Resolver, Sapui5MavenSnapshotResolver) are intentionally kept — those are synchronous constructors that cannot call the async utility, and they are publicly exported so external callers may pass undefined. --- packages/cli/lib/framework/utils.js | 18 +---- packages/cli/test/lib/framework/utils.js | 83 ++++------------------ packages/project/lib/utils/dataDir.js | 28 ++++++++ packages/project/package.json | 1 + packages/project/test/lib/utils/dataDir.js | 80 +++++++++++++++++++++ 5 files changed, 124 insertions(+), 86 deletions(-) create mode 100644 packages/project/lib/utils/dataDir.js create mode 100644 packages/project/test/lib/utils/dataDir.js diff --git a/packages/cli/lib/framework/utils.js b/packages/cli/lib/framework/utils.js index 799c8a35253..0599b824e47 100644 --- a/packages/cli/lib/framework/utils.js +++ b/packages/cli/lib/framework/utils.js @@ -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; @@ -37,7 +36,7 @@ export async function createFrameworkResolverInstance({frameworkName, frameworkV return new Resolver({ cwd, version: frameworkVersion, - ui5DataDir: await utils.getUi5DataDir({cwd}) + ui5DataDir: await resolveUi5DataDir() }); } @@ -45,26 +44,15 @@ export async function frameworkResolverResolveVersion({frameworkName, frameworkV const Resolver = await utils.getFrameworkResolver(frameworkName, frameworkVersion); return Resolver.resolveVersion(frameworkVersion, { cwd, - ui5DataDir: await utils.getUi5DataDir({cwd}) + ui5DataDir: await resolveUi5DataDir() }); } -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 diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index 0ac6f4aaf37..1094bce26d9 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -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 @@ -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(undefined); t.context.utils = await esmock.p("../../../lib/framework/utils.js", { "@ui5/project/graph": { @@ -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; }); @@ -144,11 +140,11 @@ test.serial("getFrameworkResolver: Invalid framework.name", async (t) => { test.serial("createFrameworkResolverInstance: Without 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(undefined); + resolveUi5DataDirStub.resolves(undefined); const result = await createFrameworkResolverInstance({ frameworkName: "", @@ -163,12 +159,7 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => "" ]); - 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); @@ -184,11 +175,11 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => 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: "", @@ -203,12 +194,7 @@ test.serial("createFrameworkResolverInstance: With ui5DataDir", async (t) => { "" ]); - 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); @@ -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(undefined); const result = await frameworkResolverResolveVersion({ frameworkName: "SAPUI5", @@ -250,48 +236,3 @@ test.serial("frameworkResolverResolveVersion", async (t) => { } ]); }); - -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); -}); diff --git a/packages/project/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js new file mode 100644 index 00000000000..484b7bfda1b --- /dev/null +++ b/packages/project/lib/utils/dataDir.js @@ -0,0 +1,28 @@ +import path from "node:path"; +import os from "node:os"; +import Configuration from "../config/Configuration.js"; + +/** + * Resolves the UI5 data directory using the standard precedence chain: + *
    + *
  1. UI5_DATA_DIR environment variable
  2. + *
  3. ui5DataDir option from the configuration file (~/.ui5rc)
  4. + *
  5. Default: ~/.ui5
  6. + *
+ * + * Relative paths are resolved against cwd. + * This function always returns an absolute path — never undefined. + * + * @returns {Promise} Resolved absolute path to the UI5 data directory + */ +export async function resolveUi5DataDir() { + let ui5DataDir = process.env.UI5_DATA_DIR; + if (!ui5DataDir) { + const config = await Configuration.fromFile(); + ui5DataDir = config.getUi5DataDir(); + } + if (ui5DataDir) { + return path.resolve(process.cwd(), ui5DataDir); + } + return path.join(os.homedir(), ".ui5"); +} diff --git a/packages/project/package.json b/packages/project/package.json index 17b2e8957c4..f77f1260778 100644 --- a/packages/project/package.json +++ b/packages/project/package.json @@ -27,6 +27,7 @@ "./ui5Framework/Sapui5Resolver": "./lib/ui5Framework/Sapui5Resolver.js", "./ui5Framework/maven/SnapshotCache": "./lib/ui5Framework/maven/SnapshotCache.js", "./validation/validator": "./lib/validation/validator.js", + "./utils/dataDir": "./lib/utils/dataDir.js", "./validation/ValidationError": "./lib/validation/ValidationError.js", "./graph/ProjectGraph": "./lib/graph/ProjectGraph.js", "./graph/projectGraphBuilder": "./lib/graph/projectGraphBuilder.js", diff --git a/packages/project/test/lib/utils/dataDir.js b/packages/project/test/lib/utils/dataDir.js new file mode 100644 index 00000000000..7ccf6937ed6 --- /dev/null +++ b/packages/project/test/lib/utils/dataDir.js @@ -0,0 +1,80 @@ +import test from "ava"; +import path from "node:path"; +import os from "node:os"; +import sinon from "sinon"; +import esmock from "esmock"; +test.beforeEach(async (t) => { + t.context.originalUi5DataDirEnv = process.env.UI5_DATA_DIR; + delete process.env.UI5_DATA_DIR; + + t.context.configGetUi5DataDirStub = sinon.stub().returns(undefined); + t.context.ConfigurationStub = { + fromFile: sinon.stub().resolves({ + getUi5DataDir: t.context.configGetUi5DataDirStub + }) + }; + + const {resolveUi5DataDir} = await esmock("../../../lib/utils/dataDir.js", { + "../../../lib/config/Configuration.js": t.context.ConfigurationStub + }); + t.context.resolveUi5DataDir = resolveUi5DataDir; +}); + +test.afterEach.always((t) => { + if (typeof t.context.originalUi5DataDirEnv === "undefined") { + delete process.env.UI5_DATA_DIR; + } else { + process.env.UI5_DATA_DIR = t.context.originalUi5DataDirEnv; + } + sinon.restore(); +}); + +test.serial("resolveUi5DataDir: returns ~/.ui5 when nothing is configured", async (t) => { + const {resolveUi5DataDir} = t.context; + const result = await resolveUi5DataDir(); + t.is(result, path.join(os.homedir(), ".ui5")); +}); + +test.serial("resolveUi5DataDir: returns value from UI5_DATA_DIR env var (absolute)", async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "/custom/data/dir"; + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("/custom/data/dir")); + t.is(t.context.ConfigurationStub.fromFile.callCount, 0, "Configuration not read when env var is set"); +}); + +test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against cwd", async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "relative/data"; + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("relative/data")); +}); + +test.serial("resolveUi5DataDir: returns value from Configuration (absolute)", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("/config/data/dir"); + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("/config/data/dir")); +}); + +test.serial("resolveUi5DataDir: resolves relative Configuration value against cwd", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("my-data"); + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("my-data")); +}); + +test.serial("resolveUi5DataDir: env var takes precedence over Configuration", async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "/env/data"; + t.context.configGetUi5DataDirStub.returns("/config/data"); + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("/env/data")); +}); + +test.serial("resolveUi5DataDir: uses process.cwd() when cwd is not provided", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("relative/data"); + const result = await resolveUi5DataDir(); + t.is(result, path.resolve(process.cwd(), "relative/data")); +}); From 0ebf0d75b06b5a226ae426d05f6b5212848d0397 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 10:07:07 +0300 Subject: [PATCH 02/20] refactor: Use projectRootPath in resolveUi5DataDir for correct relative path resolution Renames the cwd option to projectRootPath to make its purpose explicit: this is the root of the specific project being processed (where package.json lives), not necessarily the shell's current working directory. When omitted, falls back to process.cwd(). Callers in createFrameworkResolverInstance and frameworkResolverResolveVersion now thread the project root through so relative ui5DataDir values in config are resolved against the correct base directory. --- packages/cli/lib/framework/utils.js | 4 ++-- packages/project/lib/utils/dataDir.js | 12 +++++++++--- packages/project/test/lib/utils/dataDir.js | 20 +++++++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/cli/lib/framework/utils.js b/packages/cli/lib/framework/utils.js index 0599b824e47..d5e272eda05 100644 --- a/packages/cli/lib/framework/utils.js +++ b/packages/cli/lib/framework/utils.js @@ -36,7 +36,7 @@ export async function createFrameworkResolverInstance({frameworkName, frameworkV return new Resolver({ cwd, version: frameworkVersion, - ui5DataDir: await resolveUi5DataDir() + ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd}) }); } @@ -44,7 +44,7 @@ export async function frameworkResolverResolveVersion({frameworkName, frameworkV const Resolver = await utils.getFrameworkResolver(frameworkName, frameworkVersion); return Resolver.resolveVersion(frameworkVersion, { cwd, - ui5DataDir: await resolveUi5DataDir() + ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd}) }); } diff --git a/packages/project/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js index 484b7bfda1b..d13c16801e9 100644 --- a/packages/project/lib/utils/dataDir.js +++ b/packages/project/lib/utils/dataDir.js @@ -10,19 +10,25 @@ import Configuration from "../config/Configuration.js"; *
  • Default: ~/.ui5
  • * * - * Relative paths are resolved against cwd. * This function always returns an absolute path — never undefined. * + * @param {object} [options] + * @param {string} [options.projectRootPath] The root directory of the project being processed. + * Used to resolve a relative ui5DataDir value from the environment variable or + * configuration file against the correct base. This is NOT necessarily the shell's current + * working directory — when processing a project in a sub-directory or a workspace member, + * this should be the root of that specific project (where its package.json lives). + * Defaults to process.cwd() when not provided. * @returns {Promise} Resolved absolute path to the UI5 data directory */ -export async function resolveUi5DataDir() { +export async function resolveUi5DataDir({projectRootPath} = {}) { let ui5DataDir = process.env.UI5_DATA_DIR; if (!ui5DataDir) { const config = await Configuration.fromFile(); ui5DataDir = config.getUi5DataDir(); } if (ui5DataDir) { - return path.resolve(process.cwd(), ui5DataDir); + return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); } return path.join(os.homedir(), ".ui5"); } diff --git a/packages/project/test/lib/utils/dataDir.js b/packages/project/test/lib/utils/dataDir.js index 7ccf6937ed6..485a4bab118 100644 --- a/packages/project/test/lib/utils/dataDir.js +++ b/packages/project/test/lib/utils/dataDir.js @@ -43,7 +43,14 @@ test.serial("resolveUi5DataDir: returns value from UI5_DATA_DIR env var (absolut t.is(t.context.ConfigurationStub.fromFile.callCount, 0, "Configuration not read when env var is set"); }); -test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against cwd", async (t) => { +test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against projectRootPath", async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "relative/data"; + const result = await resolveUi5DataDir({projectRootPath: "/my/project"}); + t.is(result, path.join("/my/project", "relative/data")); +}); + +test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against process.cwd() when no projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; process.env.UI5_DATA_DIR = "relative/data"; const result = await resolveUi5DataDir(); @@ -57,7 +64,14 @@ test.serial("resolveUi5DataDir: returns value from Configuration (absolute)", as t.is(result, path.resolve("/config/data/dir")); }); -test.serial("resolveUi5DataDir: resolves relative Configuration value against cwd", async (t) => { +test.serial("resolveUi5DataDir: resolves relative Configuration value against projectRootPath", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("my-data"); + const result = await resolveUi5DataDir({projectRootPath: "/my/project"}); + t.is(result, path.join("/my/project", "my-data")); +}); + +test.serial("resolveUi5DataDir: resolves relative Configuration value against process.cwd() when no projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; t.context.configGetUi5DataDirStub.returns("my-data"); const result = await resolveUi5DataDir(); @@ -72,7 +86,7 @@ test.serial("resolveUi5DataDir: env var takes precedence over Configuration", as t.is(result, path.resolve("/env/data")); }); -test.serial("resolveUi5DataDir: uses process.cwd() when cwd is not provided", async (t) => { +test.serial("resolveUi5DataDir: uses process.cwd() when projectRootPath is not provided", async (t) => { const {resolveUi5DataDir} = t.context; t.context.configGetUi5DataDirStub.returns("relative/data"); const result = await resolveUi5DataDir(); From 66c5d806bc2c59643cdd77617c7159d87ff40c4d Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 10:59:08 +0300 Subject: [PATCH 03/20] refactor: Migrate remaining inline ui5DataDir resolutions to resolveUi5DataDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two call sites that duplicated the env→config→default resolution logic are migrated to resolveUi5DataDir({projectRootPath}): - packages/project/lib/graph/helpers/ui5Framework.js: enrichProjectGraph resolves ui5DataDir via resolveUi5DataDir using rootProject.getRootPath() as projectRootPath, then passes it to the framework Resolver. Configuration and os imports removed. - packages/project/lib/build/cache/CacheManager.create(): The no-ui5DataDir path now delegates to resolveUi5DataDir instead of inlining env/config/default logic. The explicit ui5DataDir option still resolves against cwd for backward compatibility. os and Configuration imports removed. Tests updated: resolveUi5DataDir stub added to ui5Framework.js esmocks so the stub mirrors the real resolution logic (including env and config stubs), and package-exports count updated. --- .../project/lib/build/cache/CacheManager.js | 15 ++----- .../project/lib/graph/helpers/ui5Framework.js | 16 +++---- .../graph/helpers/ui5Framework.integration.js | 5 ++- .../test/lib/graph/helpers/ui5Framework.js | 42 +++++++++++++------ packages/project/test/lib/package-exports.js | 2 +- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index e1e37a6f521..d2621ced02f 100644 --- a/packages/project/lib/build/cache/CacheManager.js +++ b/packages/project/lib/build/cache/CacheManager.js @@ -1,6 +1,5 @@ import path from "node:path"; -import os from "node:os"; -import Configuration from "../../config/Configuration.js"; +import {resolveUi5DataDir} from "../../utils/dataDir.js"; import {getLogger} from "@ui5/logger"; import BuildCacheStorage from "./BuildCacheStorage.js"; @@ -73,17 +72,9 @@ export default class CacheManager { */ 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); + ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); } else { - ui5DataDir = path.join(os.homedir(), ".ui5"); + ui5DataDir = path.resolve(cwd, ui5DataDir); } const cacheDir = path.join(ui5DataDir, "buildCache"); log.verbose(`Using build cache directory: ${cacheDir}`); diff --git a/packages/project/lib/graph/helpers/ui5Framework.js b/packages/project/lib/graph/helpers/ui5Framework.js index 660cc78427e..9bf90e21546 100644 --- a/packages/project/lib/graph/helpers/ui5Framework.js +++ b/packages/project/lib/graph/helpers/ui5Framework.js @@ -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}) { @@ -348,15 +347,10 @@ 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. + // Only env var and configuration are considered — if neither is set the + // Resolver uses its own default (~/.ui5) via its fallback. + const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); if (options.versionOverride) { version = await Resolver.resolveVersion(options.versionOverride, { diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.integration.js b/packages/project/test/lib/graph/helpers/ui5Framework.integration.js index 93096d50109..13d867c969b 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.integration.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.integration.js @@ -135,7 +135,10 @@ test.beforeEach(async (t) => { "../../../../lib/graph/Module.js": t.context.Module, "../../../../lib/ui5Framework/Openui5Resolver.js": t.context.Openui5Resolver, "../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5Resolver, - "../../../../lib/config/Configuration.js": t.context.Configuration + "../../../../lib/config/Configuration.js": t.context.Configuration, + "../../../../lib/utils/dataDir.js": { + resolveUi5DataDir: sinon.stub().resolves(path.join(fakeBaseDir, "homedir", ".ui5")) + } }); t.context.projectGraphBuilder = await esmock.p("../../../../lib/graph/projectGraphBuilder.js", { diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index b134ac187ac..0f974a82997 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -1,4 +1,5 @@ import path from "node:path"; +import os from "node:os"; import test from "ava"; import sinonGlobal from "sinon"; import esmock from "esmock"; @@ -62,11 +63,28 @@ test.beforeEach(async (t) => { }) }; + t.context.resolveUi5DataDirStub = sinon.stub().callsFake(async ({projectRootPath} = {}) => { + // Mirror resolveUi5DataDir logic using the test's own Configuration stub + // so tests that set getUi5DataDirStub or UI5_DATA_DIR get the expected path. + let ui5DataDir = process.env.UI5_DATA_DIR; + if (!ui5DataDir) { + const config = await t.context.ConfigurationStub.fromFile(); + ui5DataDir = config.getUi5DataDir(); + } + if (ui5DataDir) { + return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); + } + return path.join(os.homedir(), ".ui5"); + }); + t.context.ui5Framework = await esmock.p("../../../../lib/graph/helpers/ui5Framework.js", { "@ui5/logger": ui5Logger, "../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5ResolverStub, "../../../../lib/ui5Framework/Sapui5MavenSnapshotResolver.js": t.context.Sapui5MavenSnapshotResolverStub, "../../../../lib/config/Configuration.js": t.context.ConfigurationStub, + "../../../../lib/utils/dataDir.js": { + resolveUi5DataDir: t.context.resolveUi5DataDirStub, + }, }); t.context.utils = t.context.ui5Framework._utils; }); @@ -131,7 +149,7 @@ test.serial("enrichProjectGraph", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); @@ -336,7 +354,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { cwd: dependencyTree.path, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -344,7 +362,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -398,7 +416,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { cwd: dependencyTree.path, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -407,7 +425,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -461,7 +479,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { cwd: dependencyTree.path, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -470,7 +488,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -630,7 +648,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an snapshotCache: undefined, cwd: dependencyTree.path, version: "1.2.3", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -726,7 +744,7 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { cwd: dependencyTree.path, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -735,7 +753,7 @@ test.serial("enrichProjectGraph should resolve framework project " + snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -1000,7 +1018,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works snapshotCache: undefined, cwd: dependencyTree.path, version: "1.111.1", - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); t.is(Sapui5ResolverStub.getCall(0).args[0].providedLibraryMetadata, workspaceFrameworkLibraryMetadata); @@ -1058,7 +1076,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ snapshotCache: undefined, cwd: dependencyTree.path, - ui5DataDir: undefined, + ui5DataDir: path.join(os.homedir(), ".ui5"), version: undefined, providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); diff --git a/packages/project/test/lib/package-exports.js b/packages/project/test/lib/package-exports.js index 684e8634a84..ec16c6e22bc 100644 --- a/packages/project/test/lib/package-exports.js +++ b/packages/project/test/lib/package-exports.js @@ -13,7 +13,7 @@ test("export of package.json", (t) => { // Check number of definied exports test("check number of exports", (t) => { const packageJson = require("@ui5/project/package.json"); - t.is(Object.keys(packageJson.exports).length, 14); + t.is(Object.keys(packageJson.exports).length, 15); }); // Public API contract (exported modules) From e566611ea60ff53fb75b963a3285f4793d2f5d7b Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 11:57:42 +0300 Subject: [PATCH 04/20] refactor: Migrate remaining inline ui5DataDir resolutions to resolveUi5DataDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two call sites that duplicated the env→config→default resolution logic are migrated to resolveUi5DataDir({projectRootPath}): - packages/project/lib/graph/helpers/ui5Framework.js: enrichProjectGraph resolves ui5DataDir via resolveUi5DataDir using rootProject.getRootPath() as projectRootPath, then passes it to the framework Resolver. Configuration and os imports removed. - packages/project/lib/build/cache/CacheManager.create(): The no-ui5DataDir path now delegates to resolveUi5DataDir instead of inlining env/config/default logic. The explicit ui5DataDir option still resolves against cwd for backward compatibility. os and Configuration imports removed. Tests updated: resolveUi5DataDir stub added to ui5Framework.js esmocks so the stub mirrors the real resolution logic (including env and config stubs), and package-exports count updated. --- packages/project/test/lib/utils/dataDir.js | 46 ++++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/project/test/lib/utils/dataDir.js b/packages/project/test/lib/utils/dataDir.js index 485a4bab118..710715336e0 100644 --- a/packages/project/test/lib/utils/dataDir.js +++ b/packages/project/test/lib/utils/dataDir.js @@ -43,6 +43,13 @@ test.serial("resolveUi5DataDir: returns value from UI5_DATA_DIR env var (absolut t.is(t.context.ConfigurationStub.fromFile.callCount, 0, "Configuration not read when env var is set"); }); +test.serial("resolveUi5DataDir: absolute UI5_DATA_DIR ignores projectRootPath", async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "/custom/data/dir"; + const result = await resolveUi5DataDir({projectRootPath: "/some/project"}); + t.is(result, "/custom/data/dir", "absolute path is returned as-is regardless of projectRootPath"); +}); + test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; process.env.UI5_DATA_DIR = "relative/data"; @@ -50,12 +57,16 @@ test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against p t.is(result, path.join("/my/project", "relative/data")); }); -test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against process.cwd() when no projectRootPath", async (t) => { - const {resolveUi5DataDir} = t.context; - process.env.UI5_DATA_DIR = "relative/data"; - const result = await resolveUi5DataDir(); - t.is(result, path.resolve("relative/data")); -}); +test.serial( + "resolveUi5DataDir: resolves relative UI5_DATA_DIR " + + "env var against process.cwd() when no projectRootPath", + async (t) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = "relative/data"; + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("relative/data")); + }, +); test.serial("resolveUi5DataDir: returns value from Configuration (absolute)", async (t) => { const {resolveUi5DataDir} = t.context; @@ -64,6 +75,13 @@ test.serial("resolveUi5DataDir: returns value from Configuration (absolute)", as t.is(result, path.resolve("/config/data/dir")); }); +test.serial("resolveUi5DataDir: absolute Configuration value ignores projectRootPath", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("/config/data/dir"); + const result = await resolveUi5DataDir({projectRootPath: "/some/project"}); + t.is(result, "/config/data/dir", "absolute path is returned as-is regardless of projectRootPath"); +}); + test.serial("resolveUi5DataDir: resolves relative Configuration value against projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; t.context.configGetUi5DataDirStub.returns("my-data"); @@ -71,12 +89,16 @@ test.serial("resolveUi5DataDir: resolves relative Configuration value against pr t.is(result, path.join("/my/project", "my-data")); }); -test.serial("resolveUi5DataDir: resolves relative Configuration value against process.cwd() when no projectRootPath", async (t) => { - const {resolveUi5DataDir} = t.context; - t.context.configGetUi5DataDirStub.returns("my-data"); - const result = await resolveUi5DataDir(); - t.is(result, path.resolve("my-data")); -}); +test.serial( + "resolveUi5DataDir: resolves relative Configuration value against" + + " process.cwd() when no projectRootPath", + async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns("my-data"); + const result = await resolveUi5DataDir(); + t.is(result, path.resolve("my-data")); + }, +); test.serial("resolveUi5DataDir: env var takes precedence over Configuration", async (t) => { const {resolveUi5DataDir} = t.context; From db3ac7fbdc08a0a5eee7ee840458b21c74b22d44 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 14:14:43 +0300 Subject: [PATCH 05/20] docs: Fix stale comments after resolveUi5DataDir migration --- packages/project/lib/build/cache/CacheManager.js | 6 +++--- packages/project/lib/graph/helpers/ui5Framework.js | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index d2621ced02f..9a4bbdd324d 100644 --- a/packages/project/lib/build/cache/CacheManager.js +++ b/packages/project/lib/build/cache/CacheManager.js @@ -59,9 +59,9 @@ export default class CacheManager { * Returns a singleton CacheManager for the determined cache directory. * The cache directory is resolved in this order: * 1. Explicit ui5DataDir 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 diff --git a/packages/project/lib/graph/helpers/ui5Framework.js b/packages/project/lib/graph/helpers/ui5Framework.js index 9bf90e21546..06921dc5b23 100644 --- a/packages/project/lib/graph/helpers/ui5Framework.js +++ b/packages/project/lib/graph/helpers/ui5Framework.js @@ -348,8 +348,7 @@ export default { } // Resolve the UI5 data directory using the shared utility. - // Only env var and configuration are considered — if neither is set the - // Resolver uses its own default (~/.ui5) via its fallback. + // Returns ~/.ui5 by default when no env var or configuration is set. const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); if (options.versionOverride) { From 6c911851437ca158b61b046ccb0ee0097f7067f9 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 14:25:55 +0300 Subject: [PATCH 06/20] test: Fix Windows path failures in dataDir tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded POSIX absolute paths (/my/project, /custom/data/dir, etc.) with path.resolve(os.tmpdir(), ...) so assertions hold on all platforms. On Windows, /foo is not an absolute path — it is relative to the current drive root — causing four tests to fail. --- packages/project/test/lib/utils/dataDir.js | 37 ++++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/project/test/lib/utils/dataDir.js b/packages/project/test/lib/utils/dataDir.js index 710715336e0..66675186510 100644 --- a/packages/project/test/lib/utils/dataDir.js +++ b/packages/project/test/lib/utils/dataDir.js @@ -3,6 +3,7 @@ import path from "node:path"; import os from "node:os"; import sinon from "sinon"; import esmock from "esmock"; + test.beforeEach(async (t) => { t.context.originalUi5DataDirEnv = process.env.UI5_DATA_DIR; delete process.env.UI5_DATA_DIR; @@ -37,24 +38,25 @@ test.serial("resolveUi5DataDir: returns ~/.ui5 when nothing is configured", asyn test.serial("resolveUi5DataDir: returns value from UI5_DATA_DIR env var (absolute)", async (t) => { const {resolveUi5DataDir} = t.context; - process.env.UI5_DATA_DIR = "/custom/data/dir"; + process.env.UI5_DATA_DIR = path.resolve("custom", "data", "dir"); const result = await resolveUi5DataDir(); - t.is(result, path.resolve("/custom/data/dir")); + t.is(result, path.resolve("custom", "data", "dir")); t.is(t.context.ConfigurationStub.fromFile.callCount, 0, "Configuration not read when env var is set"); }); test.serial("resolveUi5DataDir: absolute UI5_DATA_DIR ignores projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; - process.env.UI5_DATA_DIR = "/custom/data/dir"; - const result = await resolveUi5DataDir({projectRootPath: "/some/project"}); - t.is(result, "/custom/data/dir", "absolute path is returned as-is regardless of projectRootPath"); + process.env.UI5_DATA_DIR = path.resolve("custom", "data", "dir"); + const result = await resolveUi5DataDir({projectRootPath: path.resolve("some", "project")}); + t.is(result, path.resolve("custom", "data", "dir"), + "absolute path is returned as-is regardless of projectRootPath"); }); test.serial("resolveUi5DataDir: resolves relative UI5_DATA_DIR env var against projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; process.env.UI5_DATA_DIR = "relative/data"; - const result = await resolveUi5DataDir({projectRootPath: "/my/project"}); - t.is(result, path.join("/my/project", "relative/data")); + const result = await resolveUi5DataDir({projectRootPath: path.resolve("my", "project")}); + t.is(result, path.join(path.resolve("my", "project"), "relative/data")); }); test.serial( @@ -70,23 +72,24 @@ test.serial( test.serial("resolveUi5DataDir: returns value from Configuration (absolute)", async (t) => { const {resolveUi5DataDir} = t.context; - t.context.configGetUi5DataDirStub.returns("/config/data/dir"); + t.context.configGetUi5DataDirStub.returns(path.resolve("config", "data", "dir")); const result = await resolveUi5DataDir(); - t.is(result, path.resolve("/config/data/dir")); + t.is(result, path.resolve("config", "data", "dir")); }); test.serial("resolveUi5DataDir: absolute Configuration value ignores projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; - t.context.configGetUi5DataDirStub.returns("/config/data/dir"); - const result = await resolveUi5DataDir({projectRootPath: "/some/project"}); - t.is(result, "/config/data/dir", "absolute path is returned as-is regardless of projectRootPath"); + t.context.configGetUi5DataDirStub.returns(path.resolve("config", "data", "dir")); + const result = await resolveUi5DataDir({projectRootPath: path.resolve("some", "project")}); + t.is(result, path.resolve("config", "data", "dir"), + "absolute path is returned as-is regardless of projectRootPath"); }); test.serial("resolveUi5DataDir: resolves relative Configuration value against projectRootPath", async (t) => { const {resolveUi5DataDir} = t.context; t.context.configGetUi5DataDirStub.returns("my-data"); - const result = await resolveUi5DataDir({projectRootPath: "/my/project"}); - t.is(result, path.join("/my/project", "my-data")); + const result = await resolveUi5DataDir({projectRootPath: path.resolve("my", "project")}); + t.is(result, path.join(path.resolve("my", "project"), "my-data")); }); test.serial( @@ -102,10 +105,10 @@ test.serial( test.serial("resolveUi5DataDir: env var takes precedence over Configuration", async (t) => { const {resolveUi5DataDir} = t.context; - process.env.UI5_DATA_DIR = "/env/data"; - t.context.configGetUi5DataDirStub.returns("/config/data"); + process.env.UI5_DATA_DIR = path.resolve("custom", "data", "dir"); + t.context.configGetUi5DataDirStub.returns(path.resolve("config", "data", "dir")); const result = await resolveUi5DataDir(); - t.is(result, path.resolve("/env/data")); + t.is(result, path.resolve("custom", "data", "dir")); }); test.serial("resolveUi5DataDir: uses process.cwd() when projectRootPath is not provided", async (t) => { From 6a5641be57e95f8461b0b542db8a46d213b51bde Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 14:55:29 +0300 Subject: [PATCH 07/20] fix: Ensure resolveUi5DataDir always returns an absolute path - Use path.resolve instead of path.join for the default ~/.ui5 fallback so the result is absolute even when os.homedir() returns a relative path (e.g. "./" in some CI/container environments). - Add edge-case test that asserts absoluteness when HOME is relative. - Fix CLI framework/utils tests: stubs resolving undefined reflected old behavior where no ui5DataDir meant undefined; add clarifying comments that these tests validate stub wiring, not real resolution. - Add utils/dataDir to the explicit API parity list in package-exports test so a path/mapping regression would be caught by a specific test. --- packages/cli/test/lib/framework/utils.js | 8 ++++---- packages/project/lib/utils/dataDir.js | 2 +- packages/project/test/lib/package-exports.js | 1 + packages/project/test/lib/utils/dataDir.js | 13 ++++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index 1094bce26d9..eae789dbe04 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -138,7 +138,7 @@ 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, resolveUi5DataDirStub} = t.context; @@ -168,7 +168,7 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => { cwd: "my-project-path", version: "", - ui5DataDir: undefined + ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior } ]); }); @@ -216,7 +216,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { sinon.stub(t.context._utils, "getFrameworkResolver").resolves({ resolveVersion: resolveVersionStub }); - resolveUi5DataDirStub.resolves(undefined); + resolveUi5DataDirStub.resolves(undefined); // stub contract test — real resolver always returns a path const result = await frameworkResolverResolveVersion({ frameworkName: "SAPUI5", @@ -232,7 +232,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { "latest", { cwd: "my-project-path", - ui5DataDir: undefined + ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior } ]); }); diff --git a/packages/project/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js index d13c16801e9..1c20725a038 100644 --- a/packages/project/lib/utils/dataDir.js +++ b/packages/project/lib/utils/dataDir.js @@ -30,5 +30,5 @@ export async function resolveUi5DataDir({projectRootPath} = {}) { if (ui5DataDir) { return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); } - return path.join(os.homedir(), ".ui5"); + return path.resolve(os.homedir(), ".ui5"); } diff --git a/packages/project/test/lib/package-exports.js b/packages/project/test/lib/package-exports.js index ec16c6e22bc..10472adbf86 100644 --- a/packages/project/test/lib/package-exports.js +++ b/packages/project/test/lib/package-exports.js @@ -26,6 +26,7 @@ test("check number of exports", (t) => { "ui5Framework/Sapui5Resolver", "ui5Framework/Sapui5MavenSnapshotResolver", "ui5Framework/maven/SnapshotCache", + "utils/dataDir", "validation/validator", "validation/ValidationError", "graph/ProjectGraph", diff --git a/packages/project/test/lib/utils/dataDir.js b/packages/project/test/lib/utils/dataDir.js index 66675186510..6ecc3324d9c 100644 --- a/packages/project/test/lib/utils/dataDir.js +++ b/packages/project/test/lib/utils/dataDir.js @@ -33,7 +33,18 @@ test.afterEach.always((t) => { test.serial("resolveUi5DataDir: returns ~/.ui5 when nothing is configured", async (t) => { const {resolveUi5DataDir} = t.context; const result = await resolveUi5DataDir(); - t.is(result, path.join(os.homedir(), ".ui5")); + t.is(result, path.resolve(os.homedir(), ".ui5")); + t.true(path.isAbsolute(result), "default path must always be absolute"); +}); + +test.serial("resolveUi5DataDir: default is absolute even when HOME is relative", async (t) => { + const {resolveUi5DataDir: resolveWithRelativeHome} = await esmock("../../../lib/utils/dataDir.js", { + "../../../lib/config/Configuration.js": t.context.ConfigurationStub, + "node:os": {...os, homedir: () => "./relative-home"} + }); + const result = await resolveWithRelativeHome(); + t.true(path.isAbsolute(result), "result must be absolute even when os.homedir() is relative"); + esmock.purge(resolveWithRelativeHome); }); test.serial("resolveUi5DataDir: returns value from UI5_DATA_DIR env var (absolute)", async (t) => { From 915d0bcbbd28cb01ad1a9c79b4305ee106e9e3f1 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 15:41:16 +0300 Subject: [PATCH 08/20] test: Fix ESLint findings --- packages/cli/test/lib/framework/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index eae789dbe04..e9455d89d83 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -168,7 +168,7 @@ test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses { cwd: "my-project-path", version: "", - ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior + ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior } ]); }); @@ -232,7 +232,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { "latest", { cwd: "my-project-path", - ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior + ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior } ]); }); From a405322c3362cfdfafd2df116892ef0fcf485b6e Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 15:57:45 +0300 Subject: [PATCH 09/20] test: Fix ESLint findings - cli/framework/utils.js: stub now resolves the real default path (path.join(os.homedir(), ".ui5")) instead of undefined, matching the production contract of resolveUi5DataDir. - graph/helpers/ui5Framework.js: stub and assertions use path.resolve instead of path.join for the default ~/.ui5 path, matching the stricter absolute-path guarantee of the implementation. --- packages/cli/test/lib/framework/utils.js | 12 +++++---- .../test/lib/graph/helpers/ui5Framework.js | 26 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index e9455d89d83..ef278e2443e 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -1,4 +1,6 @@ import test from "ava"; +import path from "node:path"; +import os from "node:os"; import sinonGlobal from "sinon"; import esmock from "esmock"; @@ -20,7 +22,7 @@ test.beforeEach(async (t) => { t.context.Openui5Resolver = sinon.stub(); t.context.Sapui5MavenSnapshotResolver = sinon.stub(); - t.context.resolveUi5DataDirStub = sinon.stub().resolves(undefined); + t.context.resolveUi5DataDirStub = sinon.stub().resolves(path.join(os.homedir(), ".ui5")); t.context.utils = await esmock.p("../../../lib/framework/utils.js", { "@ui5/project/graph": { @@ -144,7 +146,7 @@ test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses const ResolverStub = sinon.stub().returns({}); sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub); - resolveUi5DataDirStub.resolves(undefined); + resolveUi5DataDirStub.resolves(path.join(os.homedir(), ".ui5")); const result = await createFrameworkResolverInstance({ frameworkName: "", @@ -168,7 +170,7 @@ test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses { cwd: "my-project-path", version: "", - ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior + ui5DataDir: path.join(os.homedir(), ".ui5") } ]); }); @@ -216,7 +218,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { sinon.stub(t.context._utils, "getFrameworkResolver").resolves({ resolveVersion: resolveVersionStub }); - resolveUi5DataDirStub.resolves(undefined); // stub contract test — real resolver always returns a path + resolveUi5DataDirStub.resolves(path.join(os.homedir(), ".ui5")); const result = await frameworkResolverResolveVersion({ frameworkName: "SAPUI5", @@ -232,7 +234,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { "latest", { cwd: "my-project-path", - ui5DataDir: undefined // stub resolves undefined — tests stub contract, not real behavior + ui5DataDir: path.join(os.homedir(), ".ui5") } ]); }); diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index 0f974a82997..c1e3bfe8e70 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -74,7 +74,7 @@ test.beforeEach(async (t) => { if (ui5DataDir) { return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); } - return path.join(os.homedir(), ".ui5"); + return path.resolve(os.homedir(), ".ui5"); }); t.context.ui5Framework = await esmock.p("../../../../lib/graph/helpers/ui5Framework.js", { @@ -149,7 +149,7 @@ test.serial("enrichProjectGraph", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); @@ -354,7 +354,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { cwd: dependencyTree.path, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -362,7 +362,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -416,7 +416,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { cwd: dependencyTree.path, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -425,7 +425,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -479,7 +479,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { cwd: dependencyTree.path, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -488,7 +488,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -648,7 +648,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an snapshotCache: undefined, cwd: dependencyTree.path, version: "1.2.3", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -744,7 +744,7 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { cwd: dependencyTree.path, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -753,7 +753,7 @@ test.serial("enrichProjectGraph should resolve framework project " + snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -1018,7 +1018,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works snapshotCache: undefined, cwd: dependencyTree.path, version: "1.111.1", - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); t.is(Sapui5ResolverStub.getCall(0).args[0].providedLibraryMetadata, workspaceFrameworkLibraryMetadata); @@ -1076,7 +1076,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ snapshotCache: undefined, cwd: dependencyTree.path, - ui5DataDir: path.join(os.homedir(), ".ui5"), + ui5DataDir: path.resolve(os.homedir(), ".ui5"), version: undefined, providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); From aab0fa9d9c719b0aa7bc5af3f9ea1804d247c481 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 17:10:56 +0300 Subject: [PATCH 10/20] test: Remove os/path imports from framework utils test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stub for resolveUi5DataDir does not need to compute a real filesystem path — it just needs an opaque value that flows through correctly. Replace path.join(os.homedir(), '.ui5') with the string literal 'my-default-ui5-data-dir' and remove the unused os and path imports. --- packages/cli/test/lib/framework/utils.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index ef278e2443e..48c7a4ba990 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -1,6 +1,4 @@ import test from "ava"; -import path from "node:path"; -import os from "node:os"; import sinonGlobal from "sinon"; import esmock from "esmock"; @@ -22,7 +20,7 @@ test.beforeEach(async (t) => { t.context.Openui5Resolver = sinon.stub(); t.context.Sapui5MavenSnapshotResolver = sinon.stub(); - t.context.resolveUi5DataDirStub = sinon.stub().resolves(path.join(os.homedir(), ".ui5")); + t.context.resolveUi5DataDirStub = sinon.stub().resolves("my-default-ui5-data-dir"); t.context.utils = await esmock.p("../../../lib/framework/utils.js", { "@ui5/project/graph": { @@ -146,7 +144,7 @@ test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses const ResolverStub = sinon.stub().returns({}); sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub); - resolveUi5DataDirStub.resolves(path.join(os.homedir(), ".ui5")); + resolveUi5DataDirStub.resolves("my-default-ui5-data-dir"); const result = await createFrameworkResolverInstance({ frameworkName: "", @@ -170,7 +168,7 @@ test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses { cwd: "my-project-path", version: "", - ui5DataDir: path.join(os.homedir(), ".ui5") + ui5DataDir: "my-default-ui5-data-dir" } ]); }); @@ -218,7 +216,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { sinon.stub(t.context._utils, "getFrameworkResolver").resolves({ resolveVersion: resolveVersionStub }); - resolveUi5DataDirStub.resolves(path.join(os.homedir(), ".ui5")); + resolveUi5DataDirStub.resolves("my-default-ui5-data-dir"); const result = await frameworkResolverResolveVersion({ frameworkName: "SAPUI5", @@ -234,7 +232,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { "latest", { cwd: "my-project-path", - ui5DataDir: path.join(os.homedir(), ".ui5") + ui5DataDir: "my-default-ui5-data-dir" } ]); }); From 168349bd365f36898bd1ba1f2367b10eff16ceff Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 17:40:49 +0300 Subject: [PATCH 11/20] test: Refactor tests to mock correctly Configuration object --- .../test/lib/graph/helpers/ui5Framework.js | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index c1e3bfe8e70..8fcbcb78500 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -1,5 +1,4 @@ import path from "node:path"; -import os from "node:os"; import test from "ava"; import sinonGlobal from "sinon"; import esmock from "esmock"; @@ -55,7 +54,7 @@ test.beforeEach(async (t) => { t.context.Sapui5MavenSnapshotResolverResolveVersionStub = sinon.stub(); t.context.Sapui5MavenSnapshotResolverStub.resolveVersion = t.context.Sapui5MavenSnapshotResolverResolveVersionStub; - t.context.getUi5DataDirStub = sinon.stub().returns(undefined); + t.context.getUi5DataDirStub = sinon.stub().returns("/fake-ui5-data-dir"); t.context.ConfigurationStub = { fromFile: sinon.stub().resolves({ @@ -63,28 +62,12 @@ test.beforeEach(async (t) => { }) }; - t.context.resolveUi5DataDirStub = sinon.stub().callsFake(async ({projectRootPath} = {}) => { - // Mirror resolveUi5DataDir logic using the test's own Configuration stub - // so tests that set getUi5DataDirStub or UI5_DATA_DIR get the expected path. - let ui5DataDir = process.env.UI5_DATA_DIR; - if (!ui5DataDir) { - const config = await t.context.ConfigurationStub.fromFile(); - ui5DataDir = config.getUi5DataDir(); - } - if (ui5DataDir) { - return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); - } - return path.resolve(os.homedir(), ".ui5"); - }); - t.context.ui5Framework = await esmock.p("../../../../lib/graph/helpers/ui5Framework.js", { "@ui5/logger": ui5Logger, "../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5ResolverStub, "../../../../lib/ui5Framework/Sapui5MavenSnapshotResolver.js": t.context.Sapui5MavenSnapshotResolverStub, + }, { "../../../../lib/config/Configuration.js": t.context.ConfigurationStub, - "../../../../lib/utils/dataDir.js": { - resolveUi5DataDir: t.context.resolveUi5DataDirStub, - }, }); t.context.utils = t.context.ui5Framework._utils; }); @@ -149,7 +132,7 @@ test.serial("enrichProjectGraph", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); @@ -354,7 +337,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { cwd: dependencyTree.path, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -362,7 +345,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -416,7 +399,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { cwd: dependencyTree.path, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -425,7 +408,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -479,7 +462,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { cwd: dependencyTree.path, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -488,7 +471,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -648,7 +631,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an snapshotCache: undefined, cwd: dependencyTree.path, version: "1.2.3", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -744,7 +727,7 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { cwd: dependencyTree.path, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -753,7 +736,7 @@ test.serial("enrichProjectGraph should resolve framework project " + snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -1018,7 +1001,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works snapshotCache: undefined, cwd: dependencyTree.path, version: "1.111.1", - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); t.is(Sapui5ResolverStub.getCall(0).args[0].providedLibraryMetadata, workspaceFrameworkLibraryMetadata); @@ -1076,7 +1059,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ snapshotCache: undefined, cwd: dependencyTree.path, - ui5DataDir: path.resolve(os.homedir(), ".ui5"), + ui5DataDir: "/fake-ui5-data-dir", version: undefined, providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); From 029fbc087f3d6ea62f02fcacbb301b982bffe735 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 17 Jul 2026 17:48:46 +0300 Subject: [PATCH 12/20] test: Fix tests for Windows --- .../test/lib/graph/helpers/ui5Framework.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index 8fcbcb78500..3d9e91a7820 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -54,7 +54,7 @@ test.beforeEach(async (t) => { t.context.Sapui5MavenSnapshotResolverResolveVersionStub = sinon.stub(); t.context.Sapui5MavenSnapshotResolverStub.resolveVersion = t.context.Sapui5MavenSnapshotResolverResolveVersionStub; - t.context.getUi5DataDirStub = sinon.stub().returns("/fake-ui5-data-dir"); + t.context.getUi5DataDirStub = sinon.stub().returns(path.resolve("fake-ui5-data-dir")); t.context.ConfigurationStub = { fromFile: sinon.stub().resolves({ @@ -132,7 +132,7 @@ test.serial("enrichProjectGraph", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); @@ -337,7 +337,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { cwd: dependencyTree.path, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -345,7 +345,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -399,7 +399,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { cwd: dependencyTree.path, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -408,7 +408,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -462,7 +462,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { cwd: dependencyTree.path, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -471,7 +471,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -631,7 +631,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an snapshotCache: undefined, cwd: dependencyTree.path, version: "1.2.3", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -727,7 +727,7 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { cwd: dependencyTree.path, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -736,7 +736,7 @@ test.serial("enrichProjectGraph should resolve framework project " + snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -1001,7 +1001,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works snapshotCache: undefined, cwd: dependencyTree.path, version: "1.111.1", - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); t.is(Sapui5ResolverStub.getCall(0).args[0].providedLibraryMetadata, workspaceFrameworkLibraryMetadata); @@ -1059,7 +1059,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ snapshotCache: undefined, cwd: dependencyTree.path, - ui5DataDir: "/fake-ui5-data-dir", + ui5DataDir: path.resolve("fake-ui5-data-dir"), version: undefined, providedLibraryMetadata: workspaceFrameworkLibraryMetadata }], "Sapui5Resolver#constructor should be called with expected args"); From 39e788c87da94b94e9eb8c44de3b17fb530850b5 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Mon, 20 Jul 2026 10:29:39 +0300 Subject: [PATCH 13/20] docs: Expose ui5DataDir util to JSDoc --- packages/project/lib/utils/dataDir.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/project/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js index 1c20725a038..fd0ae59a6b8 100644 --- a/packages/project/lib/utils/dataDir.js +++ b/packages/project/lib/utils/dataDir.js @@ -2,6 +2,13 @@ import path from "node:path"; import os from "node:os"; import Configuration from "../config/Configuration.js"; +/** + * Utilities for resolving the UI5 data directory. + * + * @public + * @module @ui5/project/utils/dataDir + */ + /** * Resolves the UI5 data directory using the standard precedence chain: *
      @@ -12,6 +19,7 @@ import Configuration from "../config/Configuration.js"; * * This function always returns an absolute path — never undefined. * + * @public * @param {object} [options] * @param {string} [options.projectRootPath] The root directory of the project being processed. * Used to resolve a relative ui5DataDir value from the environment variable or From d255237b389c506a95a9c6dcda5ede0bb60514c5 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Mon, 20 Jul 2026 14:23:52 +0300 Subject: [PATCH 14/20] refactor: CacheManager falls back as AbstractResolver on ui5DataDir --- packages/project/lib/build/cache/CacheManager.js | 13 +++++-------- packages/project/lib/build/helpers/BuildContext.js | 6 ++++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index 9a4bbdd324d..a4b5f12aad4 100644 --- a/packages/project/lib/build/cache/CacheManager.js +++ b/packages/project/lib/build/cache/CacheManager.js @@ -1,7 +1,7 @@ import path from "node:path"; -import {resolveUi5DataDir} from "../../utils/dataDir.js"; import {getLogger} from "@ui5/logger"; import BuildCacheStorage from "./BuildCacheStorage.js"; +import os from "node:os"; const log = getLogger("build:cache:CacheManager"); @@ -64,18 +64,15 @@ export default class CacheManager { * 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} Singleton CacheManager instance for the cache directory */ - static async create(cwd, {ui5DataDir} = {}) { - if (!ui5DataDir) { - ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); - } else { - ui5DataDir = path.resolve(cwd, ui5DataDir); - } + 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}`); diff --git a/packages/project/lib/build/helpers/BuildContext.js b/packages/project/lib/build/helpers/BuildContext.js index 59821282710..fe857a0df16 100644 --- a/packages/project/lib/build/helpers/BuildContext.js +++ b/packages/project/lib/build/helpers/BuildContext.js @@ -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 @@ -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; } From 954b4cc162d5eaccb5cad72a80662badafcca011 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Tue, 21 Jul 2026 14:16:56 +0300 Subject: [PATCH 15/20] refactor!: Require ui5DataDir for *Resolve files --- .../documentation/docs/updates/migrate-v5.md | 42 +++++++++++++++++++ .../lib/ui5Framework/AbstractResolver.js | 22 +++++----- .../lib/ui5Framework/Openui5Resolver.js | 4 +- .../Sapui5MavenSnapshotResolver.js | 4 +- .../lib/ui5Framework/Sapui5Resolver.js | 7 ++-- .../test/lib/ui5framework/AbstractResolver.js | 42 +++++++++---------- .../test/lib/ui5framework/Openui5Resolver.js | 6 ++- .../Sapui5MavenSnapshotResolver.js | 17 +++++--- .../test/lib/ui5framework/Sapui5Resolver.js | 9 ++-- 9 files changed, 103 insertions(+), 50 deletions(-) diff --git a/internal/documentation/docs/updates/migrate-v5.md b/internal/documentation/docs/updates/migrate-v5.md index c9db7b58dac..bbb66f25ac5 100644 --- a/internal/documentation/docs/updates/migrate-v5.md +++ b/internal/documentation/docs/updates/migrate-v5.md @@ -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 @@ -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. diff --git a/packages/project/lib/ui5Framework/AbstractResolver.js b/packages/project/lib/ui5Framework/AbstractResolver.js index 266d4bcd1a6..fb2aaf0c46e 100644 --- a/packages/project/lib/ui5Framework/AbstractResolver.js +++ b/packages/project/lib/ui5Framework/AbstractResolver.js @@ -1,5 +1,4 @@ import path from "node:path"; -import os from "node:os"; import {getLogger} from "@ui5/logger"; const log = getLogger("ui5Framework:AbstractResolver"); import semver from "semver"; @@ -26,8 +25,8 @@ class AbstractResolver { * @param {boolean} [options.sources=false] Whether to install framework libraries as sources or * pre-built (with build manifest) * @param {string} [options.cwd=process.cwd()] Current working directory - * @param {string} [options.ui5DataDir="~/.ui5"] UI5 home directory location. This will be used to store packages, - * metadata and configuration used by the resolvers. Relative to `process.cwd()` + * @param {string} options.ui5DataDir Resolved UI5 home directory location. This is used to store + * metadata and packages used by the resolvers and must be resolved by the caller. * @param {object.} [options.providedLibraryMetadata] * Resolver skips installing listed libraries and uses the dependency information to resolve their dependencies. * version can be omitted in case all libraries can be resolved via the providedLibraryMetadata. @@ -38,12 +37,12 @@ class AbstractResolver { if (new.target === AbstractResolver) { throw new TypeError("Class 'AbstractResolver' is abstract"); } + if (!ui5DataDir) { + const resolverName = new.target?.name || "AbstractResolver"; + throw new Error(`${resolverName}: Missing parameter "ui5DataDir"`); + } - // In some CI environments, the homedir might be set explicitly to a relative - // path (e.g. "./"), but tooling requires an absolute path - this._ui5DataDir = path.resolve( - ui5DataDir || path.join(os.homedir(), ".ui5") - ); + this._ui5DataDir = path.resolve(ui5DataDir); this._cwd = cwd ? path.resolve(cwd) : process.cwd(); this._version = version; @@ -174,9 +173,12 @@ class AbstractResolver { * Installs the provided libraries and their dependencies * * ```js - * const resolver = new Sapui5Resolver({version: "1.76.0"}); + * const resolver = new Sapui5Resolver({ + * version: "1.76.0", + * ui5DataDir: "/path/to/.ui5" + * }); * // Or for OpenUI5: - * // const resolver = new Openui5Resolver({version: "1.76.0"}); + * // const resolver = new Openui5Resolver({version: "1.76.0", ui5DataDir: "/path/to/.ui5"}); * * resolver.install(["sap.ui.core", "sap.m"]).then(({libraryMetadata}) => { * // Installation done diff --git a/packages/project/lib/ui5Framework/Openui5Resolver.js b/packages/project/lib/ui5Framework/Openui5Resolver.js index a6a9c4fc02a..72eefe02a3e 100644 --- a/packages/project/lib/ui5Framework/Openui5Resolver.js +++ b/packages/project/lib/ui5Framework/Openui5Resolver.js @@ -18,8 +18,8 @@ class Openui5Resolver extends AbstractResolver { * @param {*} options options * @param {string} options.version OpenUI5 version to use * @param {string} [options.cwd=process.cwd()] Working directory to resolve configurations like .npmrc - * @param {string} [options.ui5DataDir="~/.ui5"] UI5 home directory location. This will be used to store packages, - * metadata and configuration used by the resolvers. Relative to `process.cwd()` + * @param {string} options.ui5DataDir Resolved UI5 home directory location. This is used to + * store metadata and packages used by the resolvers. * @param {string} [options.cacheDir] Where to store temp/cached packages. * @param {string} [options.packagesDir] Where to install packages * @param {string} [options.stagingDir] The staging directory for the packages diff --git a/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js b/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js index 01c4b843541..c56b4f7cba0 100644 --- a/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js +++ b/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js @@ -32,8 +32,8 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { * @param {boolean} [options.sources=false] Whether to install framework libraries as sources or * pre-built (with build manifest) * @param {string} [options.cwd=process.cwd()] Current working directory - * @param {string} [options.ui5DataDir="~/.ui5"] UI5 home directory location. This will be used to store packages, - * metadata and configuration used by the resolvers. Relative to `process.cwd()` + * @param {string} options.ui5DataDir Resolved UI5 home directory location. This is used to + * store metadata and packages used by the resolvers. * @param {module:@ui5/project/ui5Framework/maven/SnapshotCache} [options.snapshotCache=Default] * Snapshot cache mode to use */ diff --git a/packages/project/lib/ui5Framework/Sapui5Resolver.js b/packages/project/lib/ui5Framework/Sapui5Resolver.js index 300020dce25..0593cf734d7 100644 --- a/packages/project/lib/ui5Framework/Sapui5Resolver.js +++ b/packages/project/lib/ui5Framework/Sapui5Resolver.js @@ -21,8 +21,8 @@ class Sapui5Resolver extends AbstractResolver { * @param {*} options options * @param {string} options.version SAPUI5 version to use * @param {string} [options.cwd=process.cwd()] Working directory to resolve configurations like .npmrc - * @param {string} [options.ui5DataDir="~/.ui5"] UI5 home directory location. This will be used to store packages, - * metadata and configuration used by the resolvers. Relative to `process.cwd()` + * @param {string} options.ui5DataDir Resolved UI5 home directory location. This is used to + * store metadata and packages used by the resolvers. * @param {string} [options.cacheDir] Where to store temp/cached packages. * @param {string} [options.packagesDir] Where to install packages * @param {string} [options.stagingDir] The staging directory for packages @@ -75,7 +75,8 @@ class Sapui5Resolver extends AbstractResolver { const {default: Openui5Resolver} = await import("./Openui5Resolver.js"); const openui5Resolver = new Openui5Resolver({ cwd: this._cwd, - version: metadata.version + version: metadata.version, + ui5DataDir: this._ui5DataDir }); const openui5Metadata = await openui5Resolver.getLibraryMetadata(libraryName); return { diff --git a/packages/project/test/lib/ui5framework/AbstractResolver.js b/packages/project/test/lib/ui5framework/AbstractResolver.js index 7156df0a545..fe3065433d6 100644 --- a/packages/project/test/lib/ui5framework/AbstractResolver.js +++ b/packages/project/test/lib/ui5framework/AbstractResolver.js @@ -1,18 +1,19 @@ import test from "ava"; import sinon from "sinon"; import path from "node:path"; -import os from "node:os"; import esmock from "esmock"; test.beforeEach(async (t) => { - t.context.osHomeDirStub = sinon.stub().callsFake(() => os.homedir()); - t.context.AbstractResolver = await esmock.p("../../../lib/ui5Framework/AbstractResolver.js", { - "node:os": { - homedir: t.context.osHomeDirStub - } - }); + t.context.AbstractResolver = await esmock.p("../../../lib/ui5Framework/AbstractResolver.js", {}); class MyResolver extends t.context.AbstractResolver { + constructor(options = {}) { + super({ + ui5DataDir: "/ui5DataDir", + ...options + }); + } + static async fetchAllVersions() {} } @@ -118,24 +119,19 @@ test("AbstractResolver: Set relative 'ui5DataDir'", (t) => { t.is(resolver._ui5DataDir, path.resolve("./my-ui5DataDir"), "Should be resolved 'ui5DataDir'"); }); -test("AbstractResolver: 'ui5DataDir' overriden os.homedir()", (t) => { - const {MyResolver, osHomeDirStub} = t.context; - - osHomeDirStub.returns("./"); +test("AbstractResolver: constructor without 'ui5DataDir' should throw", (t) => { + const {AbstractResolver} = t.context; - const resolver = new MyResolver({ - version: "1.75.0" - }); - t.is(resolver._ui5DataDir, path.resolve("./.ui5"), "Should be resolved 'ui5DataDir'"); -}); + class MyResolverWithoutDefaults extends AbstractResolver { + static async fetchAllVersions() {} + } -test("AbstractResolver: Defaults 'ui5DataDir' to ~/.ui5", (t) => { - const {MyResolver} = t.context; - const resolver = new MyResolver({ - version: "1.75.0", - cwd: "/test-project/" - }); - t.is(resolver._ui5DataDir, path.join(os.homedir(), ".ui5"), "Should default to ~/.ui5"); + t.throws(() => { + new MyResolverWithoutDefaults({ + version: "1.75.0", + cwd: "/test-project/" + }); + }, {message: "MyResolverWithoutDefaults: Missing parameter \"ui5DataDir\""}); }); test("AbstractResolver: getLibraryMetadata should throw an Error when not implemented", async (t) => { diff --git a/packages/project/test/lib/ui5framework/Openui5Resolver.js b/packages/project/test/lib/ui5framework/Openui5Resolver.js index 34d756987da..278a4077d74 100644 --- a/packages/project/test/lib/ui5framework/Openui5Resolver.js +++ b/packages/project/test/lib/ui5framework/Openui5Resolver.js @@ -44,7 +44,8 @@ test.serial("Openui5Resolver: getLibraryMetadata", async (t) => { const resolver = new Openui5Resolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); t.context.fetchPackageManifestStub @@ -104,7 +105,8 @@ test.serial("Openui5Resolver: handleLibrary", async (t) => { const resolver = new Openui5Resolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); const getLibraryMetadataStub = sinon.stub(resolver, "getLibraryMetadata"); diff --git a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js index ed9a4de6cd9..a88836ae2fd 100644 --- a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js +++ b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js @@ -63,7 +63,8 @@ test.serial( const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); const expectedMetadata = { @@ -114,7 +115,8 @@ test.serial("Sapui5MavenSnapshotResolver: getLibraryMetadata throws", async (t) const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); @@ -132,7 +134,8 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary", async (t) => { const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.116.0-SNAPSHOT" + version: "1.116.0-SNAPSHOT", + ui5DataDir: "/ui5DataDir" }); const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); @@ -186,7 +189,8 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary - legacy version", async const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0-SNAPSHOT" + version: "1.75.0-SNAPSHOT", + ui5DataDir: "/ui5DataDir" }); const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); @@ -240,6 +244,7 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary - sources requested", as const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", version: "1.116.0-SNAPSHOT", + ui5DataDir: "/ui5DataDir", sources: true }); @@ -294,6 +299,7 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary - sources requested with const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", version: "1.75.0-SNAPSHOT", + ui5DataDir: "/ui5DataDir", sources: true }); @@ -347,7 +353,8 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary throws", async (t) => { const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); sinon.stub(resolver, "getLibraryMetadata").resolves({}); diff --git a/packages/project/test/lib/ui5framework/Sapui5Resolver.js b/packages/project/test/lib/ui5framework/Sapui5Resolver.js index 63288d356ed..99c5bad9c44 100644 --- a/packages/project/test/lib/ui5framework/Sapui5Resolver.js +++ b/packages/project/test/lib/ui5framework/Sapui5Resolver.js @@ -37,7 +37,8 @@ test.serial( const resolver = new Sapui5Resolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); t.context.getTargetDirForPackageStub.callsFake(({pkgName, version}) => { @@ -88,7 +89,8 @@ test.serial("Sapui5Resolver: handleLibrary", async (t) => { const resolver = new Sapui5Resolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); @@ -207,7 +209,8 @@ test.serial( const resolver = new Sapui5Resolver({ cwd: "/test-project/", - version: "1.77.7" + version: "1.77.7", + ui5DataDir: "/ui5DataDir" }); const openui5LibraryMetadata = { From 19d31ff4b7747be533e75b30e6c549c11dd8a169 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Thu, 23 Jul 2026 14:03:09 +0300 Subject: [PATCH 16/20] feat: Make dataDir a required param for public APIs --- .../documentation/docs/updates/migrate-v5.md | 27 ++ packages/builder/test/lib/builder/builder.js | 49 +++ .../builder/test/lib/builder/sourceMaps.js | 1 + .../generateLibraryPreload.integration.js | 5 + ...generateStandaloneAppBundle.integration.js | 3 + .../test/lib/tasks/generateCachebusterInfo.js | 2 + packages/cli/lib/cli/commands/build.js | 5 + packages/cli/lib/cli/commands/serve.js | 5 + packages/cli/lib/cli/commands/tree.js | 4 + packages/cli/lib/framework/utils.js | 13 +- packages/cli/test/lib/cli/commands/build.js | 15 + packages/cli/test/lib/cli/commands/serve.js | 19 +- packages/cli/test/lib/cli/commands/tree.js | 28 +- packages/cli/test/lib/framework/utils.js | 10 +- packages/project/lib/build/ProjectBuilder.js | 2 +- .../project/lib/build/cache/CacheManager.js | 19 +- .../project/lib/build/helpers/BuildContext.js | 11 +- packages/project/lib/graph/ProjectGraph.js | 12 +- packages/project/lib/graph/graph.js | 30 +- .../project/lib/graph/helpers/ui5Framework.js | 20 +- .../lib/ui5Framework/AbstractResolver.js | 19 +- .../lib/ui5Framework/Openui5Resolver.js | 18 +- .../Sapui5MavenSnapshotResolver.js | 9 +- .../lib/ui5Framework/Sapui5Resolver.js | 18 +- .../test/lib/build/BuildServer.integration.js | 1 + .../lib/build/ProjectBuilder.integration.js | 1 + .../test/lib/build/helpers/BuildContext.js | 4 +- .../lib/build/helpers/composeProjectList.js | 4 +- .../test/lib/graph/graph.integration.js | 17 +- packages/project/test/lib/graph/graph.js | 45 ++- .../project/test/lib/graph/graphFromObject.js | 61 ++- .../lib/graph/graphFromPackageDependencies.js | 10 +- .../test/lib/graph/graphFromStaticFile.js | 7 + .../graph/helpers/ui5Framework.integration.js | 18 +- .../test/lib/graph/helpers/ui5Framework.js | 58 +-- .../test/lib/ui5framework/AbstractResolver.js | 371 +++++++----------- .../Openui5Resolver.integration.js | 44 ++- .../test/lib/ui5framework/Openui5Resolver.js | 35 +- ...Sapui5MavenSnapshotResolver.integration.js | 42 +- .../Sapui5MavenSnapshotResolver.js | 28 +- .../Sapui5Resolver.integration.js | 44 ++- .../test/lib/ui5framework/Sapui5Resolver.js | 35 +- packages/server/lib/server.js | 7 +- .../lib/server/acceptRemoteConnections.js | 4 +- packages/server/test/lib/server/caching.js | 4 +- packages/server/test/lib/server/h2.js | 4 +- packages/server/test/lib/server/main.js | 20 +- packages/server/test/lib/server/ports.js | 18 +- packages/server/test/lib/server/server.js | 18 +- 49 files changed, 724 insertions(+), 520 deletions(-) diff --git a/internal/documentation/docs/updates/migrate-v5.md b/internal/documentation/docs/updates/migrate-v5.md index bbb66f25ac5..55ca44fc58c 100644 --- a/internal/documentation/docs/updates/migrate-v5.md +++ b/internal/documentation/docs/updates/migrate-v5.md @@ -29,6 +29,8 @@ Or update your global install via: `npm i --global @ui5/cli@next` - **@ui5/project: UI5 framework resolver constructors now require explicit `ui5DataDir`** +- **@ui5/project: UI5 framework resolver static APIs now require explicit `ui5DataDir`** + ## Node.js and npm Version Support @@ -114,6 +116,9 @@ The `ui5 init` command now generates projects with Specification Version 5.0 by When consuming the Node.js API, UI5 framework resolver constructors now require the `ui5DataDir` option. This affects `Openui5Resolver`, `Sapui5Resolver`, and `Sapui5MavenSnapshotResolver`. +The same requirement now applies to static resolver APIs (for example `resolveVersion`, `fetchAllVersions`, and +`fetchAllTags`). Calls without `ui5DataDir` now throw and no longer fall back to implicit default paths. + 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. @@ -149,6 +154,28 @@ async function createResolver() { ``` ::: +For static APIs, pass the same resolved value as the second argument: + +::: code-group +```js [Before] +import Sapui5Resolver from "@ui5/project/ui5Framework/Sapui5Resolver"; + +const version = await Sapui5Resolver.resolveVersion("latest"); +``` + +```js [After] +import Sapui5Resolver from "@ui5/project/ui5Framework/Sapui5Resolver"; +import {resolveUi5DataDir} from "@ui5/project/utils/dataDir"; + +async function resolveFrameworkVersion(projectRootPath) { + const ui5DataDir = await resolveUi5DataDir({projectRootPath}); + return Sapui5Resolver.resolveVersion("latest", ui5DataDir, { + cwd: projectRootPath + }); +} +``` +::: + ## 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. diff --git a/packages/builder/test/lib/builder/builder.js b/packages/builder/test/lib/builder/builder.js index cf0f0c0c46b..69331bc7982 100644 --- a/packages/builder/test/lib/builder/builder.js +++ b/packages/builder/test/lib/builder/builder.js @@ -97,6 +97,7 @@ test.serial("Build application.a", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.a", "dest"); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationAPath }); graph.setTaskRepository(taskRepository); @@ -119,6 +120,7 @@ test.serial("Build application.a with dependencies", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.a", "dest-deps"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); graph.setTaskRepository(taskRepository); @@ -145,6 +147,7 @@ test.serial("Build application.a with dependencies exclude", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.a", "dest-deps-excl"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); graph.setTaskRepository(taskRepository); @@ -172,6 +175,7 @@ test.serial("Build application.a self-contained", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.a", "dest-self"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); graph.setTaskRepository(taskRepository); @@ -195,6 +199,7 @@ test.serial("Build application.a with dependencies self-contained", async (t) => const expectedPath = path.join("test", "expected", "build", "application.a", "dest-depself"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); graph.setTaskRepository(taskRepository); @@ -221,21 +226,26 @@ test.serial("Build application.a and clean target path", async (t) => { const destPath = "./test/tmp/build/application.a/dest-clean"; const destPathRubbishSubFolder = destPath + "/rubbish-should-be-deleted"; const expectedPath = path.join("test", "expected", "build", "application.a", "dest-clean"); + const ui5DataDir = isolatedUi5DataDir(t); const graph1 = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); const graph2 = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationATree }); graph1.setTaskRepository(taskRepository); await graph1.build({ + ui5DataDir, graph: graph1, destPath: destPathRubbishSubFolder, excludedTasks: ["*"] }); graph2.setTaskRepository(taskRepository); await graph2.build({ + ui5DataDir, graph: graph2, destPath, cleanDest: true, @@ -255,6 +265,7 @@ test.serial("Build application.g", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.g", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationGTree }); graph.setTaskRepository(taskRepository); @@ -277,6 +288,7 @@ test.serial("Build application.g with component preload paths", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.g", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationGTreeComponentPreloadPaths }); graph.setTaskRepository(taskRepository); @@ -299,6 +311,7 @@ test.serial("Build application.g with excludes", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.g", "excludes"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationGTreeWithExcludes }); graph.setTaskRepository(taskRepository); @@ -321,6 +334,7 @@ test.serial("Build application.h", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.h", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationHTree }); graph.setTaskRepository(taskRepository); @@ -344,6 +358,7 @@ test.serial("Build application.h (no minify)", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.h", "no-minify"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationHTree }); graph.setTaskRepository(taskRepository); @@ -367,6 +382,7 @@ test.serial("Build application.i", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.i", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationITree }); graph.setTaskRepository(taskRepository); @@ -389,6 +405,7 @@ test.serial("Build application.j", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.j", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationJTree }); graph.setTaskRepository(taskRepository); @@ -417,6 +434,7 @@ test.serial("Build application.j with resources.json and version info", async (t sinon.stub(Date.prototype, "getMinutes").returns(17); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationJTree }); graph.setTaskRepository(taskRepository); @@ -439,6 +457,7 @@ test.serial("Build application.k (componentPreload excludes)", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.k", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationKTree }); graph.setTaskRepository(taskRepository); @@ -462,6 +481,7 @@ test.serial("Build application.k (package sub-components / componentPreload excl const expectedPath = path.join("test", "expected", "build", "application.k", "dest-package-subcomponents"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationKPackageSubcomponentsTree }); graph.setTaskRepository(taskRepository); @@ -485,6 +505,7 @@ test.serial("Build application.l: minification excludes, w/ namespace", async (t const expectedPath = path.join("test", "expected", "build", "application.l", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationLTree }); graph.setTaskRepository(taskRepository); @@ -507,6 +528,7 @@ test.serial("Build application.m: bundle should not contain hashbang but an empt const expectedPath = path.join("test", "expected", "build", "application.m", "dest"); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationMPath }); graph.setTaskRepository(taskRepository); @@ -528,6 +550,7 @@ test.serial("Build application.ø", async (t) => { const expectedPath = path.join("test", "expected", "build", "application.ø", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationØTree }); graph.setTaskRepository(taskRepository); @@ -550,6 +573,7 @@ test.serial("Build library.d with copyright from .library file", async (t) => { const expectedPath = path.join("test", "expected", "build", "library.d", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryDTree }); graph.setTaskRepository(taskRepository); @@ -572,6 +596,7 @@ test.serial("Build library.e with copyright from metadata configuration of tree" const expectedPath = path.join("test", "expected", "build", "library.e", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryETree }); graph.setTaskRepository(taskRepository); @@ -598,6 +623,7 @@ test.serial("Build library.e with build manifest", async (t) => { // Stub date because of timestamp in build-manifest.json const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryETree }); graph.setTaskRepository(taskRepository); @@ -674,6 +700,7 @@ test.serial("Build library.h with custom bundles and component-preloads", async const expectedPath = path.join("test", "expected", "build", "library.h", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryHTree }); graph.setTaskRepository(taskRepository); @@ -696,6 +723,7 @@ test.serial("Build library.h with custom bundles and component-preloads (no mini const expectedPath = path.join("test", "expected", "build", "library.h", "no-minify"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryHTree }); graph.setTaskRepository(taskRepository); @@ -722,6 +750,7 @@ test.serial("Build library.h w/ custom bundles, component-preloads, resources.js // Stub date because of timestamp in build-manifest.json const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryHTree }); graph.setTaskRepository(taskRepository); @@ -872,6 +901,7 @@ test.serial("Build library.i with manifest info taken from .library and library. const expectedPath = path.join("test", "expected", "build", "library.i", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryITree }); graph.setTaskRepository(taskRepository); @@ -894,6 +924,7 @@ test.serial("Build library.j with JSDoc build only", async (t) => { const expectedPath = path.join("test", "expected", "build", "library.j", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryJTree }); graph.setTaskRepository(taskRepository); @@ -921,6 +952,7 @@ test.serial("Build library.i, bundling library.h", async (t) => { const expectedPath = path.join("test", "expected", "build", "library.i", "bundle-library.h"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryIBundlingHTree }); graph.setTaskRepository(taskRepository); @@ -944,15 +976,18 @@ test.serial("Build library.i, bundling library.h with build manifest", async (t) const expectedPath = path.join("test", "expected", "build", "library.i", "bundle-library.h-build-manifest"); const resultBuildManifestPath = path.join(__dirname, "..", "..", "tmp", "build", "library.i", "bundle-library.h-build-manifest", ".ui5", "build-manifest.json"); + const ui5DataDir = isolatedUi5DataDir(t); setLogLevel("verbose"); const graph1 = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryHTree }); graph1.setTaskRepository(taskRepository); await graph1.build({ + ui5DataDir, destPath: libraryHDestPath, createBuildManifest: true }); @@ -964,10 +999,12 @@ test.serial("Build library.i, bundling library.h with build manifest", async (t) // Stub date because of timestamp in build-manifest.json const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); const graph2 = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: projectTree }); graph2.setTaskRepository(taskRepository); await graph2.build({ + ui5DataDir, destPath, createBuildManifest: true }); @@ -1030,6 +1067,7 @@ test.serial("Build library.l", async (t) => { const expectedPath = path.join("test", "expected", "build", "library.l", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryLTree }); graph.setTaskRepository(taskRepository); @@ -1052,6 +1090,7 @@ test.serial("Build theme.j even without an library", async (t) => { const expectedPath = "./test/expected/build/theme.j/dest"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeJTree }); graph.setTaskRepository(taskRepository); @@ -1073,6 +1112,7 @@ test.serial("Build theme.j even without an library with resources.json", async ( const expectedPath = "./test/expected/build/theme.j/dest-resources-json"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeJTree }); graph.setTaskRepository(taskRepository); @@ -1101,6 +1141,7 @@ test.serial("Build theme.j with build manifest", async (t) => { // Stub date because of timestamp in build-manifest.json const toISOStringStub = sinon.stub(Date.prototype, "toISOString").returns("2022-07-27T09:00:00.000Z"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeJTree }); graph.setTaskRepository(taskRepository); @@ -1158,6 +1199,7 @@ test.serial("Build library.ø", async (t) => { const expectedPath = path.join("test", "expected", "build", "library.ø", "dest"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryØTree }); graph.setTaskRepository(taskRepository); @@ -1187,6 +1229,7 @@ test.serial("Build library.coreBuildtime: replaceBuildtime", async (t) => { ]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryCoreBuildtimeTree }); graph.setTaskRepository(taskRepository); @@ -1211,6 +1254,7 @@ test.serial("Build library with theme configured for CSS variables", async (t) = const expectedPath = "./test/expected/build/theme.j/dest-css-variables"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeJTree }); graph.setTaskRepository(taskRepository); @@ -1233,6 +1277,7 @@ test.serial("Build library with theme configured for CSS variables and theme des const expectedPath = "./test/expected/build/theme.j/dest-css-variables-theme-designer-resources"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeJTree }); graph.setTaskRepository(taskRepository); @@ -1256,6 +1301,7 @@ test.serial("Build theme-library with CSS variables", async (t) => { const expectedPath = "./test/expected/build/theme.library.e/dest-css-variables"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeLibraryETree }); graph.setTaskRepository(taskRepository); @@ -1278,6 +1324,7 @@ test.serial("Build theme-library with CSS variables and theme designer resources const expectedPath = "./test/expected/build/theme.library.e/dest-css-variables-theme-designer-resources"; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: themeLibraryETree }); graph.setTaskRepository(taskRepository); @@ -1301,6 +1348,7 @@ test.serial("Build library.o with terminologies and supportedLocales", async (t) const expectedPath = path.join("test", "expected", "build", "library.o", "dest"); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: libraryOPath }); graph.setTaskRepository(taskRepository); @@ -1322,6 +1370,7 @@ test.serial("Build application.o with terminologies and supportedLocales", async const expectedPath = path.join("test", "expected", "build", "application.o", "dest"); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationOPath }); graph.setTaskRepository(taskRepository); diff --git a/packages/builder/test/lib/builder/sourceMaps.js b/packages/builder/test/lib/builder/sourceMaps.js index 307ae5aee8f..979f3ba029f 100644 --- a/packages/builder/test/lib/builder/sourceMaps.js +++ b/packages/builder/test/lib/builder/sourceMaps.js @@ -60,6 +60,7 @@ test.serial("Verify source maps (test.application)", async (t) => { const destURL = t.context.destURL = new URL("./dest-standard-build/", applicationDestRootURL); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: fileURLToPath(applicationURL) }); graph.setTaskRepository(taskRepository); diff --git a/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js index c265f581cea..5f64c60c1a7 100644 --- a/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js +++ b/packages/builder/test/lib/tasks/bundlers/generateLibraryPreload.integration.js @@ -21,6 +21,7 @@ test.serial("integration: build library.d with library preload", async (t) => { const includedTasks = ["generateLibraryPreload"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryDTree }); graph.setTaskRepository(taskRepository); @@ -75,6 +76,7 @@ test.serial("integration: build library.d-minified with library preload", async const includedTasks = ["generateLibraryPreload"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryDMinifiedTree }); graph.setTaskRepository(taskRepository); @@ -129,6 +131,7 @@ test.serial("integration: build sap.ui.core with library preload", async (t) => const includedTasks = ["minify", "generateLibraryPreload"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: sapUiCoreTree }); graph.setTaskRepository(taskRepository); @@ -333,6 +336,7 @@ test.serial("integration: build library.n without enabled string bundling", asyn const includedTasks = ["generateLibraryPreload"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryNTree }); graph.setTaskRepository(taskRepository); @@ -379,6 +383,7 @@ test.serial("integration: build library.n with enabled string bundling", async ( const includedTasks = ["generateLibraryPreload"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: libraryNTreeLegacy }); graph.setTaskRepository(taskRepository); diff --git a/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js index 2ab851a34c7..9aac122528b 100644 --- a/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js +++ b/packages/builder/test/lib/tasks/bundlers/generateStandaloneAppBundle.integration.js @@ -22,6 +22,7 @@ test.serial("integration: build application.b standalone", async (t) => { const includedTasks = ["minify", "generateStandaloneAppBundle"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationBTree }); @@ -186,6 +187,7 @@ test("integration: build application.n standalone without enabled string bundlin const includedTasks = ["minify", "generateStandaloneAppBundle"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationNTree }); @@ -230,6 +232,7 @@ test("integration: build application.n standalone with enabled string bundling", const includedTasks = ["minify", "generateStandaloneAppBundle"]; const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationNTreeLegacy }); diff --git a/packages/builder/test/lib/tasks/generateCachebusterInfo.js b/packages/builder/test/lib/tasks/generateCachebusterInfo.js index 39f4a6d7380..1deaacf6f7b 100644 --- a/packages/builder/test/lib/tasks/generateCachebusterInfo.js +++ b/packages/builder/test/lib/tasks/generateCachebusterInfo.js @@ -19,6 +19,7 @@ test("integration: Build application.g", async (t) => { const cleanupCacheBusterInfo = (fileContent) => fileContent.replace(/(:\s+)(\d+)/g, ": 0"); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationGTree }); @@ -58,6 +59,7 @@ test("integration: Build application.g with cachebuster using hashes", async (t) const cleanupCacheBusterInfo = (fileContent) => fileContent.replace(/(:\s+)("[^"]+")/g, ": \"\""); const graph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: applicationGTreeWithCachebusterHash }); diff --git a/packages/cli/lib/cli/commands/build.js b/packages/cli/lib/cli/commands/build.js index af7d82890c9..af66141dde8 100644 --- a/packages/cli/lib/cli/commands/build.js +++ b/packages/cli/lib/cli/commands/build.js @@ -1,5 +1,6 @@ import baseMiddleware from "../middlewares/base.js"; import {applyProjectConfigOptions, applyWorkspaceOptions, applyBuildOptions, dedupeArray} from "../options.js"; +import {resolveUi5DataDir} from "@ui5/project/utils/dataDir"; import {getLogger} from "@ui5/logger"; const log = getLogger("cli:commands:build"); @@ -184,6 +185,7 @@ build.builder = function(cli) { async function handleBuild(argv) { const {graphFromStaticFile, graphFromPackageDependencies} = await import("@ui5/project/graph"); + const ui5DataDir = await resolveUi5DataDir({projectRootPath: process.cwd()}); const command = argv._[argv._.length - 1]; @@ -194,6 +196,7 @@ async function handleBuild(argv) { rootConfigPath: argv.config, versionOverride: argv.frameworkVersion, snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback + ui5DataDir, }); } else { graph = await graphFromPackageDependencies({ @@ -202,6 +205,7 @@ async function handleBuild(argv) { snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback workspaceConfigPath: argv.workspaceConfig, workspaceName: argv.workspace === false ? null : argv.workspace, + ui5DataDir, }); } const buildSettings = graph.getRoot().getBuilderSettings() || {}; @@ -229,6 +233,7 @@ async function handleBuild(argv) { cssVariables: argv["experimental-css-variables"], outputStyle: argv["output-style"], cache: argv["cache"], + ui5DataDir, }); } diff --git a/packages/cli/lib/cli/commands/serve.js b/packages/cli/lib/cli/commands/serve.js index e70924bc53f..fa6c13568fa 100644 --- a/packages/cli/lib/cli/commands/serve.js +++ b/packages/cli/lib/cli/commands/serve.js @@ -3,6 +3,7 @@ import os from "node:os"; import process from "node:process"; import baseMiddleware from "../middlewares/base.js"; import {applyProjectConfigOptions, applyWorkspaceOptions, applyBuildOptions, dedupeArray} from "../options.js"; +import {resolveUi5DataDir} from "@ui5/project/utils/dataDir"; import {getLogger} from "@ui5/logger"; const log = getLogger("cli:commands:serve"); @@ -146,6 +147,7 @@ serve.handler = async function(argv) { }); const {graphFromStaticFile, graphFromPackageDependencies} = await import("@ui5/project/graph"); + const ui5DataDir = await resolveUi5DataDir({projectRootPath: process.cwd()}); let graph; if (argv.dependencyDefinition) { @@ -154,6 +156,7 @@ serve.handler = async function(argv) { rootConfigPath: argv.config, versionOverride: argv.frameworkVersion, snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback + ui5DataDir, }); } else { graph = await graphFromPackageDependencies({ @@ -162,6 +165,7 @@ serve.handler = async function(argv) { snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback workspaceConfigPath: argv.workspaceConfig, workspaceName: argv.workspace === false ? null : argv.workspace, + ui5DataDir, }); } @@ -210,6 +214,7 @@ serve.handler = async function(argv) { cache: argv.cache, includedTasks: argv["include-task"], excludedTasks: argv["exclude-task"], + ui5DataDir, }; if (serverConfig.h2) { diff --git a/packages/cli/lib/cli/commands/tree.js b/packages/cli/lib/cli/commands/tree.js index 3c2ec93c3e6..b5f25471d90 100644 --- a/packages/cli/lib/cli/commands/tree.js +++ b/packages/cli/lib/cli/commands/tree.js @@ -1,6 +1,7 @@ // Tree import baseMiddleware from "../middlewares/base.js"; import {applyProjectConfigOptions, applyWorkspaceOptions, dedupeArray} from "../options.js"; +import {resolveUi5DataDir} from "@ui5/project/utils/dataDir"; import chalk from "chalk"; import {getLogger} from "@ui5/logger"; const log = getLogger("cli:commands:tree"); @@ -70,6 +71,7 @@ tree.handler = async function(argv) { startTime = process.hrtime(); } const {graphFromStaticFile, graphFromPackageDependencies} = await import("@ui5/project/graph"); + const ui5DataDir = await resolveUi5DataDir({projectRootPath: process.cwd()}); let graph; if (argv.dependencyDefinition) { graph = await graphFromStaticFile({ @@ -77,6 +79,7 @@ tree.handler = async function(argv) { rootConfigPath: argv.config, versionOverride: argv.frameworkVersion, snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback + ui5DataDir, }); } else { graph = await graphFromPackageDependencies({ @@ -85,6 +88,7 @@ tree.handler = async function(argv) { snapshotCache: argv.snapshotCache ?? argv.cacheMode ?? "Default", // Use cacheMode as fallback workspaceConfigPath: argv.workspaceConfig, workspaceName: argv.workspace === false ? null : argv.workspace, + ui5DataDir, }); } diff --git a/packages/cli/lib/framework/utils.js b/packages/cli/lib/framework/utils.js index d5e272eda05..38527abbba5 100644 --- a/packages/cli/lib/framework/utils.js +++ b/packages/cli/lib/framework/utils.js @@ -2,14 +2,21 @@ import {graphFromStaticFile, graphFromPackageDependencies} from "@ui5/project/gr import {resolveUi5DataDir} from "@ui5/project/utils/dataDir"; export async function getRootProjectConfiguration(projectGraphOptions) { + const cwd = projectGraphOptions.cwd || process.cwd(); + const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); + let graph; if (projectGraphOptions.dependencyDefinition) { graph = await graphFromStaticFile({ + cwd, + ui5DataDir, filePath: projectGraphOptions.dependencyDefinition, resolveFrameworkDependencies: false }); } else { graph = await graphFromPackageDependencies({ + cwd, + ui5DataDir, rootConfigPath: projectGraphOptions.config, resolveFrameworkDependencies: false }); @@ -42,10 +49,8 @@ export async function createFrameworkResolverInstance({frameworkName, frameworkV export async function frameworkResolverResolveVersion({frameworkName, frameworkVersion}, {cwd}) { const Resolver = await utils.getFrameworkResolver(frameworkName, frameworkVersion); - return Resolver.resolveVersion(frameworkVersion, { - cwd, - ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd}) - }); + const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); + return Resolver.resolveVersion(frameworkVersion, ui5DataDir, {cwd}); } const utils = { diff --git a/packages/cli/test/lib/cli/commands/build.js b/packages/cli/test/lib/cli/commands/build.js index 8234a6506bb..66d16ee5e55 100644 --- a/packages/cli/test/lib/cli/commands/build.js +++ b/packages/cli/test/lib/cli/commands/build.js @@ -64,6 +64,7 @@ function getDefaultBuilderArgs() { test.beforeEach(async (t) => { t.context.argv = getDefaultArgv(); t.context.expectedBuilderArgs = getDefaultBuilderArgs(); + t.context.ui5DataDir = "/resolved/ui5-data-dir"; t.context.builder = sinon.stub().resolves(); t.context.getBuilderSettings = sinon.stub().returns(undefined); @@ -75,15 +76,20 @@ test.beforeEach(async (t) => { build: t.context.builder }; t.context.expectedBuilderArgs.graph = fakeGraph; + t.context.expectedBuilderArgs.ui5DataDir = t.context.ui5DataDir; t.context.ProjectGraphStub = sinon.stub().resolves(fakeGraph); t.context.graphFromPackageDependenciesStub = sinon.stub().resolves(fakeGraph); t.context.graphFromStaticFileStub = sinon.stub().resolves(fakeGraph); + t.context.resolveUi5DataDirStub = sinon.stub().resolves(t.context.ui5DataDir); t.context.build = await esmock.p("../../../../lib/cli/commands/build.js", { "@ui5/project/graph": { graphFromPackageDependencies: t.context.graphFromPackageDependenciesStub, graphFromStaticFile: t.context.graphFromStaticFileStub }, + "@ui5/project/utils/dataDir": { + resolveUi5DataDir: t.context.resolveUi5DataDirStub + }, "@ui5/project/graph/ProjectGraph": t.context.ProjectGraphStub }); }); @@ -138,6 +144,7 @@ test.serial("ui5 build --framework-version", async (t) => { workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -157,6 +164,7 @@ test.serial("ui5 build --snapshot-cache", async (t) => { workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Off", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -176,6 +184,7 @@ test.serial("ui5 build --config", async (t) => { workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -195,6 +204,7 @@ test.serial("ui5 build --workspace", async (t) => { workspaceConfigPath: undefined, workspaceName: "dolphin", snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -214,6 +224,7 @@ test.serial("ui5 build --no-workspace", async (t) => { workspaceConfigPath: undefined, workspaceName: null, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -234,6 +245,7 @@ test.serial("ui5 build --workspace-config", async (t) => { workspaceConfigPath: fakePath, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromPackageDependencies got called with expected arguments" ); }); @@ -252,6 +264,7 @@ test.serial("ui5 build --dependency-definition", async (t) => { rootConfigPath: undefined, versionOverride: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromStaticFile got called with expected arguments" ); }); @@ -271,6 +284,7 @@ test.serial("ui5 build --dependency-definition --config", async (t) => { rootConfigPath: "ui5-test.yaml", versionOverride: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromStaticFile got called with expected arguments" ); }); @@ -291,6 +305,7 @@ test.serial("ui5 build --dependency-definition --config --framework-version", as rootConfigPath: "ui5-test.yaml", versionOverride: "1.99.0", snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }, "generateProjectGraph.graphFromStaticFile got called with expected arguments" ); }); diff --git a/packages/cli/test/lib/cli/commands/serve.js b/packages/cli/test/lib/cli/commands/serve.js index ffab26787f0..75d4e41c753 100644 --- a/packages/cli/test/lib/cli/commands/serve.js +++ b/packages/cli/test/lib/cli/commands/serve.js @@ -34,6 +34,7 @@ function getDefaultArgv() { test.beforeEach(async (t) => { t.context.argv = getDefaultArgv(); + t.context.ui5DataDir = "/resolved/ui5-data-dir"; // server.serve is the CLI-facing synchronization point now that the handler // no longer writes "Server started" to stdout. Test cases await `serverServed` @@ -68,6 +69,7 @@ test.beforeEach(async (t) => { graphFromStaticFile: sinon.stub().resolves(t.context.fakeGraph), graphFromPackageDependencies: sinon.stub().resolves(t.context.fakeGraph) }; + t.context.resolveUi5DataDirStub = sinon.stub().resolves(t.context.ui5DataDir); // Capture stray writes to stderr/stdout so failing assertions surface the // actual output instead of ava's timeout diagnostics. @@ -85,6 +87,9 @@ test.beforeEach(async (t) => { "@ui5/server": t.context.server, "@ui5/server/internal/sslUtil": t.context.sslUtil, "@ui5/project/graph": t.context.graph, + "@ui5/project/utils/dataDir": { + resolveUi5DataDir: t.context.resolveUi5DataDirStub + }, "open": t.context.open }); }); @@ -106,6 +111,7 @@ test.serial("ui5 serve: default", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); t.is(server.serve.callCount, 1); @@ -125,6 +131,7 @@ test.serial("ui5 serve: default", async (t) => { liveReload: true, includedTasks: undefined, excludedTasks: undefined, + ui5DataDir: "/resolved/ui5-data-dir", } ]); t.is(typeof server.serve.getCall(0).args[2], "function"); @@ -169,6 +176,7 @@ test.serial("ui5 serve --h2", async (t) => { liveReload: true, includedTasks: undefined, excludedTasks: undefined, + ui5DataDir: "/resolved/ui5-data-dir", } ]); @@ -204,6 +212,7 @@ test.serial("ui5 serve --accept-remote-connections", async (t) => { liveReload: true, includedTasks: undefined, excludedTasks: undefined, + ui5DataDir: "/resolved/ui5-data-dir", } ]); }); @@ -257,6 +266,7 @@ test.serial("ui5 serve --config", async (t) => { rootConfigPath: fakePath, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); @@ -273,7 +283,7 @@ test.serial("ui5 serve --dependency-definition", async (t) => { t.is(graph.graphFromStaticFile.callCount, 1); t.deepEqual(graph.graphFromStaticFile.getCall(0).args, [{ filePath: fakePath, versionOverride: undefined, - snapshotCache: "Default", rootConfigPath: undefined + snapshotCache: "Default", ui5DataDir: "/resolved/ui5-data-dir", rootConfigPath: undefined }]); }); @@ -292,7 +302,7 @@ test.serial("ui5 serve --dependency-definition / --config", async (t) => { t.is(graph.graphFromStaticFile.callCount, 1); t.deepEqual(graph.graphFromStaticFile.getCall(0).args, [{ filePath: fakeDependenciesPath, versionOverride: undefined, - snapshotCache: "Default", rootConfigPath: fakeConfigPath + snapshotCache: "Default", ui5DataDir: "/resolved/ui5-data-dir", rootConfigPath: fakeConfigPath }]); }); @@ -308,6 +318,7 @@ test.serial("ui5 serve --framework-version", async (t) => { rootConfigPath: undefined, versionOverride: "1.234.5", workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); @@ -323,6 +334,7 @@ test.serial("ui5 serve --snapshotCache", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Force", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); @@ -338,6 +350,7 @@ test.serial("ui5 serve --workspace", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: "dolphin", snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); @@ -353,6 +366,7 @@ test.serial("ui5 serve --no-workspace", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: null, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); @@ -369,6 +383,7 @@ test.serial("ui5 serve --workspace-config", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: fakePath, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: "/resolved/ui5-data-dir", }]); }); diff --git a/packages/cli/test/lib/cli/commands/tree.js b/packages/cli/test/lib/cli/commands/tree.js index e08b338d236..3f3c19f8e4b 100644 --- a/packages/cli/test/lib/cli/commands/tree.js +++ b/packages/cli/test/lib/cli/commands/tree.js @@ -25,6 +25,7 @@ function getDefaultArgv() { test.beforeEach(async (t) => { t.context.argv = getDefaultArgv(); + t.context.ui5DataDir = path.join(path.sep, "resolved", "ui5-data-dir"); t.context.traverseBreadthFirst = sinon.stub(); t.context.getExtensionNames = sinon.stub().returns([]); @@ -38,6 +39,7 @@ test.beforeEach(async (t) => { graphFromStaticFile: sinon.stub().resolves(fakeGraph), graphFromPackageDependencies: sinon.stub().resolves(fakeGraph) }; + t.context.resolveUi5DataDirStub = sinon.stub().resolves(t.context.ui5DataDir); t.context.consoleOutput = ""; t.context.processStderrWrite = sinon.stub(process.stderr, "write").callsFake((message) => { @@ -48,7 +50,10 @@ test.beforeEach(async (t) => { }); t.context.tree = await esmock.p("../../../../lib/cli/commands/tree.js", { - "@ui5/project/graph": t.context.graph + "@ui5/project/graph": t.context.graph, + "@ui5/project/utils/dataDir": { + resolveUi5DataDir: t.context.resolveUi5DataDirStub + } }); }); test.afterEach.always((t) => { @@ -81,6 +86,7 @@ test.serial("ui5 tree (Without dependencies)", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -151,6 +157,7 @@ test.serial("ui5 tree", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -231,6 +238,7 @@ test.serial("ui5 tree --flat", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -308,6 +316,7 @@ test.serial("ui5 tree --level 1", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -398,6 +407,7 @@ test.serial("ui5 tree (With extensions)", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -444,6 +454,7 @@ test.serial("ui5 tree --perf", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -485,6 +496,7 @@ test.serial("ui5 tree --framework-version", async (t) => { rootConfigPath: undefined, versionOverride: "1.234.5", workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -524,6 +536,7 @@ test.serial("ui5 tree --snapshot-cache", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Force", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -564,6 +577,7 @@ test.serial("ui5 tree --config", async (t) => { rootConfigPath: fakePath, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -603,6 +617,7 @@ test.serial("ui5 tree --workspace", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: "dolphin", snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -642,6 +657,7 @@ test.serial("ui5 tree --no-workspace", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: undefined, workspaceName: null, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -682,6 +698,7 @@ test.serial("ui5 tree --workspace-config", async (t) => { rootConfigPath: undefined, versionOverride: undefined, workspaceConfigPath: fakePath, workspaceName: undefined, snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir, }]); t.is(t.context.consoleOutput, @@ -719,7 +736,11 @@ test.serial("ui5 tree --dependency-definition", async (t) => { t.is(graph.graphFromPackageDependencies.callCount, 0); t.is(graph.graphFromStaticFile.callCount, 1); t.deepEqual(graph.graphFromStaticFile.getCall(0).args, [{ - filePath: fakePath, rootConfigPath: undefined, versionOverride: undefined, snapshotCache: "Default" + filePath: fakePath, + rootConfigPath: undefined, + versionOverride: undefined, + snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir }]); t.is(t.context.consoleOutput, @@ -762,6 +783,7 @@ test.serial("ui5 tree --dependency-definition --config", async (t) => { filePath: fakeDepDefPath, rootConfigPath: fakeConfigPath, versionOverride: undefined, - snapshotCache: "Default" + snapshotCache: "Default", + ui5DataDir: t.context.ui5DataDir }], "graphFromStaticFile got called with --config forwarded as rootConfigPath"); }); diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index 48c7a4ba990..5f0d0856194 100644 --- a/packages/cli/test/lib/framework/utils.js +++ b/packages/cli/test/lib/framework/utils.js @@ -64,6 +64,8 @@ test.serial("getRootProjectConfiguration: dependencyDefinition", async (t) => { t.is(graphFromStaticFile.callCount, 1); t.deepEqual(graphFromStaticFile.getCall(0).args, [{ + cwd: process.cwd(), + ui5DataDir: "my-default-ui5-data-dir", filePath: "foo", resolveFrameworkDependencies: false }]); @@ -86,6 +88,8 @@ test.serial("getRootProjectConfiguration: config", async (t) => { t.is(graphFromPackageDependencies.callCount, 1); t.deepEqual(graphFromPackageDependencies.getCall(0).args, [{ + cwd: process.cwd(), + ui5DataDir: "my-default-ui5-data-dir", rootConfigPath: "foo", resolveFrameworkDependencies: false }]); @@ -230,9 +234,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { t.is(resolveVersionStub.callCount, 1); t.deepEqual(resolveVersionStub.getCall(0).args, [ "latest", - { - cwd: "my-project-path", - ui5DataDir: "my-default-ui5-data-dir" - } + "my-default-ui5-data-dir", + {cwd: "my-project-path"} ]); }); diff --git a/packages/project/lib/build/ProjectBuilder.js b/packages/project/lib/build/ProjectBuilder.js index 5966472866a..b70eabb62c3 100644 --- a/packages/project/lib/build/ProjectBuilder.js +++ b/packages/project/lib/build/ProjectBuilder.js @@ -123,7 +123,7 @@ class ProjectBuilder { } this._graph = graph; - this._buildContext = new BuildContext(graph, taskRepository, buildConfig, {ui5DataDir}); + this._buildContext = new BuildContext(graph, taskRepository, buildConfig, ui5DataDir); this.#log = new BuildLogger("ProjectBuilder"); } diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index a4b5f12aad4..dec1dcc5676 100644 --- a/packages/project/lib/build/cache/CacheManager.js +++ b/packages/project/lib/build/cache/CacheManager.js @@ -1,7 +1,6 @@ import path from "node:path"; import {getLogger} from "@ui5/logger"; import BuildCacheStorage from "./BuildCacheStorage.js"; -import os from "node:os"; const log = getLogger("build:cache:CacheManager"); @@ -57,22 +56,16 @@ export default class CacheManager { * Factory method to create or retrieve a CacheManager instance * * Returns a singleton CacheManager for the determined cache directory. - * The cache directory is resolved in this order: - * 1. Explicit ui5DataDir option (resolved relative to cwd) - * 2. UI5_DATA_DIR environment variable or ui5DataDir from configuration file - * (resolved relative to cwd via {@link resolveUi5DataDir}) - * 3. Default: ~/.ui5/ * * @public - * @param {object} [options] - * @param {string} [options.ui5DataDir] Explicit UI5 data directory. When provided, - * environment variable, configuration file, and home-directory fallbacks are skipped. + * @param {string} ui5DataDir Resolved UI5 data directory. * @returns {Promise} Singleton CacheManager instance for the cache directory */ - static async create({ui5DataDir} = {}) { - ui5DataDir = path.resolve( - ui5DataDir || path.join(os.homedir(), ".ui5") - ); + static async create(ui5DataDir) { + if (!ui5DataDir) { + throw new Error("CacheManager: Missing parameter \"ui5DataDir\""); + } + ui5DataDir = path.resolve(ui5DataDir); const cacheDir = path.join(ui5DataDir, "buildCache"); log.verbose(`Using build cache directory: ${cacheDir}`); diff --git a/packages/project/lib/build/helpers/BuildContext.js b/packages/project/lib/build/helpers/BuildContext.js index fe857a0df16..71f9e95fa39 100644 --- a/packages/project/lib/build/helpers/BuildContext.js +++ b/packages/project/lib/build/helpers/BuildContext.js @@ -5,7 +5,6 @@ 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 @@ -24,7 +23,7 @@ class BuildContext { outputStyle = OutputStyleEnum.Default, includedTasks = [], excludedTasks = [], cache = Cache.Default, - } = {}, {ui5DataDir} = {}) { + } = {}, ui5DataDir) { if (!graph) { throw new Error(`Missing parameter 'graph'`); } @@ -171,10 +170,10 @@ class BuildContext { if (this.#cacheManager) { return this.#cacheManager; } - this.#cacheManager = await CacheManager.create({ - ui5DataDir: this._ui5DataDir ?? - await resolveUi5DataDir({projectRootPath: this.getRootProject().getRootPath()}), - }); + if (!this._ui5DataDir) { + throw new Error("BuildContext: Missing parameter \"ui5DataDir\" for cache-enabled builds"); + } + this.#cacheManager = await CacheManager.create(this._ui5DataDir); return this.#cacheManager; } diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js index 653e6f7901b..ed2af07bf23 100644 --- a/packages/project/lib/graph/ProjectGraph.js +++ b/packages/project/lib/graph/ProjectGraph.js @@ -716,10 +716,8 @@ class ProjectGraph { * Processes build results into a specific directory structure. * @param {module:@ui5/project/build/cache/Cache} [parameters.cache=Default] * Cache mode to use for building UI5 projects - * @param {string} [parameters.ui5DataDir] - * Explicit UI5 data directory to use for the build cache. Overrides the - * UI5_DATA_DIR environment variable, the UI5 configuration file, - * and the default of ~/.ui5. + * @param {string} parameters.ui5DataDir + * Resolved UI5 data directory to use for the build cache. * @returns {Promise} Promise resolving to undefined once build has finished */ async build({ @@ -732,6 +730,9 @@ class ProjectGraph { cache = Cache.Default, ui5DataDir, }) { + if (!ui5DataDir) { + throw new Error("ProjectGraph#build: Missing parameter \"ui5DataDir\""); + } this.seal(); // Do not allow further changes to the graph if (this._builtOrServed) { throw new Error( @@ -768,6 +769,9 @@ class ProjectGraph { cache = Cache.Default, ui5DataDir, }) { + if (!ui5DataDir) { + throw new Error("ProjectGraph#serve: Missing parameter \"ui5DataDir\""); + } this.seal(); // Do not allow further changes to the graph if (this._builtOrServed) { throw new Error( diff --git a/packages/project/lib/graph/graph.js b/packages/project/lib/graph/graph.js index bf1bd5c3583..fa16498b18f 100644 --- a/packages/project/lib/graph/graph.js +++ b/packages/project/lib/graph/graph.js @@ -33,6 +33,8 @@ const log = getLogger("generateProjectGraph"); * Name of the workspace configuration that should be used. "default" if not provided. * @param {module:@ui5/project/ui5Framework/maven/SnapshotCache} [options.snapshotCache] * Snapshot cache mode to use when consuming SNAPSHOT versions of a framework + * @param {string} options.ui5DataDir + * Resolved UI5 data directory to use for framework metadata and package resolution. * @param {string} [options.workspaceConfigPath=ui5-workspace.yaml] * Workspace configuration file to use if no object has been provided * @param {@ui5/project/graph/Workspace~Configuration} [options.workspaceConfiguration] @@ -44,8 +46,12 @@ export async function graphFromPackageDependencies({ cwd, rootConfiguration, rootConfigPath, versionOverride, snapshotCache, resolveFrameworkDependencies = true, workspaceName="default", - workspaceConfiguration, workspaceConfigPath = "ui5-workspace.yaml" + workspaceConfiguration, workspaceConfigPath = "ui5-workspace.yaml", + ui5DataDir }) { + if (!ui5DataDir) { + throw new Error("graphFromPackageDependencies: Missing parameter \"ui5DataDir\""); + } log.verbose(`Creating project graph using npm provider...`); const { default: NpmProvider @@ -73,7 +79,7 @@ export async function graphFromPackageDependencies({ const projectGraph = await projectGraphBuilder(provider, workspace); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache, workspace}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache, workspace, ui5DataDir}); } return projectGraph; @@ -100,6 +106,8 @@ export async function graphFromPackageDependencies({ * @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project * @param {module:@ui5/project/ui5Framework/maven/SnapshotCache} [options.snapshotCache] * Snapshot cache mode to use when consuming SNAPSHOT versions of a framework + * @param {string} options.ui5DataDir + * Resolved UI5 data directory to use for framework metadata and package resolution. * @param {string} [options.resolveFrameworkDependencies=true] * Whether framework dependencies should be added to the graph * @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance @@ -107,8 +115,12 @@ export async function graphFromPackageDependencies({ export async function graphFromStaticFile({ filePath = "projectDependencies.yaml", cwd, rootConfiguration, rootConfigPath, - versionOverride, snapshotCache, resolveFrameworkDependencies = true + versionOverride, snapshotCache, resolveFrameworkDependencies = true, + ui5DataDir }) { + if (!ui5DataDir) { + throw new Error("graphFromStaticFile: Missing parameter \"ui5DataDir\""); + } log.verbose(`Creating project graph using static file...`); const { default: DependencyTreeProvider @@ -128,7 +140,7 @@ export async function graphFromStaticFile({ const projectGraph = await projectGraphBuilder(provider); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache, ui5DataDir}); } return projectGraph; @@ -152,6 +164,8 @@ export async function graphFromStaticFile({ * @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project * @param {module:@ui5/project/ui5Framework/maven/SnapshotCache} [options.snapshotCache] * Snapshot cache mode to use when consuming SNAPSHOT versions of a framework + * @param {string} options.ui5DataDir + * Resolved UI5 data directory to use for framework metadata and package resolution. * @param {string} [options.resolveFrameworkDependencies=true] * Whether framework dependencies should be added to the graph * @returns {Promise<@ui5/project/graph/ProjectGraph>} Promise resolving to a Project Graph instance @@ -159,8 +173,12 @@ export async function graphFromStaticFile({ export async function graphFromObject({ dependencyTree, cwd, rootConfiguration, rootConfigPath, - versionOverride, snapshotCache, resolveFrameworkDependencies = true + versionOverride, snapshotCache, resolveFrameworkDependencies = true, + ui5DataDir }) { + if (!ui5DataDir) { + throw new Error("graphFromObject: Missing parameter \"ui5DataDir\""); + } log.verbose(`Creating project graph using object...`); const { default: DependencyTreeProvider @@ -178,7 +196,7 @@ export async function graphFromObject({ const projectGraph = await projectGraphBuilder(dependencyTreeProvider); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, snapshotCache, ui5DataDir}); } return projectGraph; diff --git a/packages/project/lib/graph/helpers/ui5Framework.js b/packages/project/lib/graph/helpers/ui5Framework.js index 06921dc5b23..32466500816 100644 --- a/packages/project/lib/graph/helpers/ui5Framework.js +++ b/packages/project/lib/graph/helpers/ui5Framework.js @@ -2,7 +2,6 @@ import Module from "../Module.js"; import ProjectGraph from "../ProjectGraph.js"; import {getLogger} from "@ui5/logger"; const log = getLogger("graph:helpers:ui5Framework"); -import {resolveUi5DataDir} from "../../utils/dataDir.js"; class ProjectProcessor { constructor({libraryMetadata, graph, workspace}) { @@ -283,13 +282,17 @@ export default { * version * @param {module:@ui5/project/ui5Framework/maven/SnapshotCache} [options.snapshotCache] * Snapshot cache mode to use when consuming SNAPSHOT versions of a framework + * @param {string} options.ui5DataDir Resolved UI5 data directory to use for framework + * metadata and package resolution. * @param {@ui5/project/graph/Workspace} [options.workspace] * Optional workspace instance to use for overriding node resolutions + * @throws {Error} If framework libraries are referenced but no options.ui5DataDir + * is provided. * @returns {Promise<@ui5/project/graph/ProjectGraph>} * Promise resolving with the given graph instance to allow method chaining */ enrichProjectGraph: async function(projectGraph, options = {}) { - const {workspace, snapshotCache} = options; + const {workspace, snapshotCache, ui5DataDir} = options; const rootProject = projectGraph.getRoot(); const frameworkName = rootProject.getFrameworkName(); const frameworkVersion = rootProject.getFrameworkVersion(); @@ -338,6 +341,10 @@ export default { return projectGraph; } + if (!ui5DataDir) { + throw new Error("ui5Framework.enrichProjectGraph: Missing parameter \"ui5DataDir\""); + } + let Resolver; if (version && version.toLowerCase().endsWith("-snapshot")) { Resolver = (await import("../../ui5Framework/Sapui5MavenSnapshotResolver.js")).default; @@ -347,15 +354,8 @@ export default { Resolver = (await import("../../ui5Framework/Sapui5Resolver.js")).default; } - // 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, { - ui5DataDir, - cwd - }); + version = await Resolver.resolveVersion(options.versionOverride, ui5DataDir, {cwd}); log.info( `Overriding configured ${frameworkName} version ` + `${frameworkVersion} with version ${version}` diff --git a/packages/project/lib/ui5Framework/AbstractResolver.js b/packages/project/lib/ui5Framework/AbstractResolver.js index fb2aaf0c46e..750242ff0ce 100644 --- a/packages/project/lib/ui5Framework/AbstractResolver.js +++ b/packages/project/lib/ui5Framework/AbstractResolver.js @@ -210,21 +210,26 @@ class AbstractResolver { }; } - static async resolveVersion(version, {ui5DataDir, cwd} = {}) { + static async resolveVersion(version, ui5DataDir, {cwd} = {}) { // Don't allow nullish values // An empty string is a valid semver range that converts to "*", which should not be supported if (!version) { throw new Error(`Framework version specifier "${version}" is incorrect or not supported`); } - const spec = await this._getVersionSpec(version, {ui5DataDir, cwd}); + if (!ui5DataDir) { + throw new Error(`${this.name}: Missing parameter "ui5DataDir"`); + } + ui5DataDir = path.resolve(ui5DataDir); + + const spec = await this._getVersionSpec(version, ui5DataDir, {cwd}); // 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`); } - const versions = await this.fetchAllVersions({ui5DataDir, cwd}); + const versions = await this.fetchAllVersions(ui5DataDir, {cwd}); 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. @@ -251,7 +256,7 @@ class AbstractResolver { return resolvedVersion; } - static async _getVersionSpec(version, {ui5DataDir, cwd}) { + static async _getVersionSpec(version, ui5DataDir, {cwd} = {}) { if (this._isSnapshotVersionOrRange(version)) { const versionMatch = version.match(VERSION_RANGE_REGEXP); if (versionMatch) { @@ -273,7 +278,7 @@ class AbstractResolver { return null; } - const allTags = await this.fetchAllTags({ui5DataDir, cwd}); + const allTags = await this.fetchAllTags(ui5DataDir, {cwd}); if (!allTags) { // Resolver doesn't support tags (e.g. Sapui5MavenSnapshotResolver) @@ -309,10 +314,10 @@ class AbstractResolver { async handleLibrary(libraryName) { throw new Error("AbstractResolver: handleLibrary must be implemented!"); } - static fetchAllVersions(options) { + static fetchAllVersions(ui5DataDir, options) { throw new Error("AbstractResolver: static fetchAllVersions must be implemented!"); } - static fetchAllTags(options) { + static fetchAllTags(ui5DataDir, options) { return null; } } diff --git a/packages/project/lib/ui5Framework/Openui5Resolver.js b/packages/project/lib/ui5Framework/Openui5Resolver.js index 72eefe02a3e..bf51d1f2e50 100644 --- a/packages/project/lib/ui5Framework/Openui5Resolver.js +++ b/packages/project/lib/ui5Framework/Openui5Resolver.js @@ -1,5 +1,4 @@ import path from "node:path"; -import os from "node:os"; import AbstractResolver from "./AbstractResolver.js"; import Installer from "./npm/Installer.js"; @@ -84,22 +83,23 @@ class Openui5Resolver extends AbstractResolver { }) }; } - static async fetchAllVersions(options) { - const installer = this._getInstaller(options); + static async fetchAllVersions(ui5DataDir, {cwd} = {}) { + const installer = this._getInstaller(ui5DataDir, {cwd}); return await installer.fetchPackageVersions({pkgName: OPENUI5_CORE_PACKAGE}); } - static async fetchAllTags(options) { - const installer = this._getInstaller(options); + static async fetchAllTags(ui5DataDir, {cwd} = {}) { + const installer = this._getInstaller(ui5DataDir, {cwd}); return installer.fetchPackageDistTags({pkgName: OPENUI5_CORE_PACKAGE}); } - static _getInstaller({ui5DataDir, cwd} = {}) { + static _getInstaller(ui5DataDir, {cwd} = {}) { + if (!ui5DataDir) { + throw new Error(`${this.name}: Missing parameter "ui5DataDir"`); + } return new Installer({ cwd: cwd ? path.resolve(cwd) : process.cwd(), - ui5DataDir: - ui5DataDir ? path.resolve(ui5DataDir) : - path.join(os.homedir(), ".ui5") + ui5DataDir: path.resolve(ui5DataDir) }); } } diff --git a/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js b/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js index c56b4f7cba0..310f68a8882 100644 --- a/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js +++ b/packages/project/lib/ui5Framework/Sapui5MavenSnapshotResolver.js @@ -144,12 +144,13 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { }; } - static async fetchAllVersions({ui5DataDir, cwd, snapshotEndpointUrl} = {}) { + static async fetchAllVersions(ui5DataDir, {cwd, snapshotEndpointUrl} = {}) { + if (!ui5DataDir) { + throw new Error(`${this.name}: Missing parameter "ui5DataDir"`); + } const installer = new Installer({ cwd: cwd ? path.resolve(cwd) : process.cwd(), - ui5DataDir: path.resolve( - ui5DataDir || path.join(os.homedir(), ".ui5") - ), + ui5DataDir: path.resolve(ui5DataDir), snapshotEndpointUrlCb: Sapui5MavenSnapshotResolver._createSnapshotEndpointUrlCallback(snapshotEndpointUrl), }); return await installer.fetchPackageVersions({ diff --git a/packages/project/lib/ui5Framework/Sapui5Resolver.js b/packages/project/lib/ui5Framework/Sapui5Resolver.js index 0593cf734d7..980e7d29100 100644 --- a/packages/project/lib/ui5Framework/Sapui5Resolver.js +++ b/packages/project/lib/ui5Framework/Sapui5Resolver.js @@ -1,5 +1,4 @@ import path from "node:path"; -import os from "node:os"; import semver from "semver"; import AbstractResolver from "./AbstractResolver.js"; import Installer from "./npm/Installer.js"; @@ -106,22 +105,23 @@ class Sapui5Resolver extends AbstractResolver { }) }; } - static async fetchAllVersions(options) { - const installer = this._getInstaller(options); + static async fetchAllVersions(ui5DataDir, {cwd} = {}) { + const installer = this._getInstaller(ui5DataDir, {cwd}); return await installer.fetchPackageVersions({pkgName: DIST_PKG_NAME}); } - static async fetchAllTags(options) { - const installer = this._getInstaller(options); + static async fetchAllTags(ui5DataDir, {cwd} = {}) { + const installer = this._getInstaller(ui5DataDir, {cwd}); return installer.fetchPackageDistTags({pkgName: DIST_PKG_NAME}); } - static _getInstaller({ui5DataDir, cwd} = {}) { + static _getInstaller(ui5DataDir, {cwd} = {}) { + if (!ui5DataDir) { + throw new Error(`${this.name}: Missing parameter "ui5DataDir"`); + } return new Installer({ cwd: cwd ? path.resolve(cwd) : process.cwd(), - ui5DataDir: - ui5DataDir ? path.resolve(ui5DataDir) : - path.join(os.homedir(), ".ui5") + ui5DataDir: path.resolve(ui5DataDir) }); } } diff --git a/packages/project/test/lib/build/BuildServer.integration.js b/packages/project/test/lib/build/BuildServer.integration.js index a450d5a037c..46472df436b 100644 --- a/packages/project/test/lib/build/BuildServer.integration.js +++ b/packages/project/test/lib/build/BuildServer.integration.js @@ -1245,6 +1245,7 @@ class FixtureTester { async serveProject({graphConfig = {}, config = {}, expectBuildErrors = false} = {}) { const graph = this.graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", ...graphConfig, cwd: this.fixturePath, }); diff --git a/packages/project/test/lib/build/ProjectBuilder.integration.js b/packages/project/test/lib/build/ProjectBuilder.integration.js index c754b7213f1..a7ee2cfa835 100644 --- a/packages/project/test/lib/build/ProjectBuilder.integration.js +++ b/packages/project/test/lib/build/ProjectBuilder.integration.js @@ -2719,6 +2719,7 @@ class FixtureTester { this._sinon.resetHistory(); const graph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", ...graphConfig, cwd: this.fixturePath, }); diff --git a/packages/project/test/lib/build/helpers/BuildContext.js b/packages/project/test/lib/build/helpers/BuildContext.js index 16832b98679..ea1d04c6e6e 100644 --- a/packages/project/test/lib/build/helpers/BuildContext.js +++ b/packages/project/test/lib/build/helpers/BuildContext.js @@ -315,7 +315,7 @@ test("getProjectContext", async (t) => { .returns({getType: () => "library", getRootPath: () => ""}); const graph = {getRoot: rootProjectStub, getProject: () => "project"}; - const buildContext = new BuildContext(graph, "taskRepository"); + const buildContext = new BuildContext(graph, "taskRepository", undefined, "/ui5DataDir"); const projectBuildContext = await buildContext.getProjectContext("project"); t.is(t.context.ProjectBuildContextCreateStub.callCount, 1); @@ -350,7 +350,7 @@ test("getCacheManager: Creates and caches CacheManager for default cache mode", const graph = { getRoot: () => ({getType: () => "library", getRootPath: () => "/some/path"}), }; - const buildContext = new BuildContext(graph, "taskRepository"); + const buildContext = new BuildContext(graph, "taskRepository", undefined, "/ui5DataDir"); const cacheManager1 = await buildContext.getCacheManager(); t.is(cacheManager1, cacheManagerInstance, "Returned CacheManager instance"); diff --git a/packages/project/test/lib/build/helpers/composeProjectList.js b/packages/project/test/lib/build/helpers/composeProjectList.js index 8556efcdfbb..906133c6bc5 100644 --- a/packages/project/test/lib/build/helpers/composeProjectList.js +++ b/packages/project/test/lib/build/helpers/composeProjectList.js @@ -80,7 +80,7 @@ test.serial("_getFlattenedDependencyTree", async (t) => { }] }] }; - const graph = await graphFromObject({dependencyTree: tree}); + const graph = await graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}); t.deepEqual(await _getFlattenedDependencyTree(graph), { "library.e": ["library.d", "library.a", "library.b", "library.c"], @@ -162,7 +162,7 @@ async function assertCreateDependencyLists(t, { }] }; - const graph = await graphFromObject({dependencyTree: tree}); + const graph = await graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}); const {includedDependencies, excludedDependencies} = await t.context.composeProjectList(graph, { includeAllDependencies, diff --git a/packages/project/test/lib/graph/graph.integration.js b/packages/project/test/lib/graph/graph.integration.js index fcff4e57957..0424d82630c 100644 --- a/packages/project/test/lib/graph/graph.integration.js +++ b/packages/project/test/lib/graph/graph.integration.js @@ -45,6 +45,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => { const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -66,7 +67,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => { t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -94,6 +95,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -116,7 +118,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -140,6 +142,7 @@ test.serial("graphFromPackageDependencies with workspace object not matching wor const {graphFromPackageDependencies} = t.context.graph; await t.throwsAsync(graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -169,6 +172,7 @@ test.serial("graphFromPackageDependencies with workspace file", async (t) => { const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: libraryHPath, rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -208,6 +212,7 @@ test.serial("graphFromPackageDependencies with workspace file at custom path", a const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -220,7 +225,7 @@ test.serial("graphFromPackageDependencies with workspace file at custom path", a t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath" }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -248,6 +253,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -261,7 +267,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath" }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -278,6 +284,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", workspace: null, - snapshotCache: "Force" + snapshotCache: "Force", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); diff --git a/packages/project/test/lib/graph/graph.js b/packages/project/test/lib/graph/graph.js index 799033a59db..f901f88b98a 100644 --- a/packages/project/test/lib/graph/graph.js +++ b/packages/project/test/lib/graph/graph.js @@ -54,6 +54,7 @@ test.serial("graphFromPackageDependencies", async (t) => { const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -67,7 +68,7 @@ test.serial("graphFromPackageDependencies", async (t) => { t.is(createWorkspaceStub.callCount, 0, "createWorkspace did not get called"); t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath" }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -84,7 +85,8 @@ test.serial("graphFromPackageDependencies", async (t) => { t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", workspace: undefined, - snapshotCache: "Off" + snapshotCache: "Off", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); @@ -96,6 +98,7 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -108,7 +111,7 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { t.is(createWorkspaceStub.callCount, 1, "createWorkspace got called once"); t.deepEqual(createWorkspaceStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), name: "dolphin", configPath: "ui5-workspace.yaml", configObject: undefined, @@ -116,7 +119,7 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -133,7 +136,8 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", workspace: "workspace", - snapshotCache: "Off" + snapshotCache: "Off", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); @@ -144,6 +148,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => { const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -156,7 +161,7 @@ test.serial("graphFromPackageDependencies with workspace object", async (t) => { t.is(createWorkspaceStub.callCount, 1, "createWorkspace got called once"); t.deepEqual(createWorkspaceStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), configPath: "ui5-workspace.yaml", name: null, configObject: "workspaceConfiguration" @@ -170,6 +175,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -182,7 +188,7 @@ test.serial("graphFromPackageDependencies with workspace object and workspace na t.is(createWorkspaceStub.callCount, 1, "createWorkspace got called once"); t.deepEqual(createWorkspaceStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), name: "dolphin", configPath: "ui5-workspace.yaml", configObject: "workspaceConfiguration" @@ -196,6 +202,7 @@ test.serial("graphFromPackageDependencies with workspace path and workspace name const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -208,7 +215,7 @@ test.serial("graphFromPackageDependencies with workspace path and workspace name t.is(createWorkspaceStub.callCount, 1, "createWorkspace got called once"); t.deepEqual(createWorkspaceStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), name: "dolphin", configPath: "workspaceConfigurationPath", configObject: undefined @@ -226,6 +233,7 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { createWorkspaceStub.resolves(null); const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -238,7 +246,7 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { t.is(createWorkspaceStub.callCount, 1, "createWorkspace got called once"); t.deepEqual(createWorkspaceStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), name: "dolphin", configPath: "ui5-workspace.yaml", configObject: undefined, @@ -246,7 +254,7 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { t.is(npmProviderConstructorStub.callCount, 1, "NPM provider constructor got called once"); t.deepEqual(npmProviderConstructorStub.getCall(0).args[0], { - cwd: path.join(__dirname, "..", "..", "..", "cwd"), + cwd: path.resolve("cwd"), rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", }, "Created NodePackageDependencies provider instance with correct parameters"); @@ -263,7 +271,8 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", workspace: null, - snapshotCache: "Off" + snapshotCache: "Off", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); @@ -272,6 +281,7 @@ test.serial("graphFromPackageDependencies: Do not resolve framework dependencies const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -288,6 +298,7 @@ test.serial("graphFromPackageDependencies: Default workspace name", async (t) => const {graphFromPackageDependencies} = t.context.graph; const res = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -312,6 +323,7 @@ test.serial("graphFromStaticFile", async (t) => { .resolves("dependencyTree"); const res = await graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", filePath: "file/path", rootConfiguration: "rootConfiguration", @@ -323,7 +335,7 @@ test.serial("graphFromStaticFile", async (t) => { t.is(res, "graph"); t.is(readDependencyConfigFileStub.callCount, 1, "_readDependencyConfigFile got called once"); - t.is(readDependencyConfigFileStub.getCall(0).args[0], path.join(__dirname, "..", "..", "..", "cwd"), + t.is(readDependencyConfigFileStub.getCall(0).args[0], path.resolve("cwd"), "_readDependencyConfigFile got called with correct directory"); t.is(readDependencyConfigFileStub.getCall(0).args[1], "file/path", "_readDependencyConfigFile got called with correct file path"); @@ -344,7 +356,8 @@ test.serial("graphFromStaticFile", async (t) => { "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - snapshotCache: "Off" + snapshotCache: "Off", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); @@ -356,6 +369,7 @@ test.serial("graphFromStaticFile: Do not resolve framework dependencies", async .resolves("dependencyTree"); const res = await graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", filePath: "filePath", rootConfiguration: "rootConfiguration", @@ -376,6 +390,7 @@ test.serial("usingObject", async (t) => { const {graphFromObject} = t.context.graph; const res = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: "dependencyTree", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", @@ -401,7 +416,8 @@ test.serial("usingObject", async (t) => { "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - snapshotCache: "Off" + snapshotCache: "Off", + ui5DataDir: "/path/to/ui5-data-dir" }, "enrichProjectGraph got called with correct options"); }); @@ -409,6 +425,7 @@ test.serial("usingObject: Do not resolve framework dependencies", async (t) => { const {enrichProjectGraphStub} = t.context; const {graphFromObject} = t.context.graph; const res = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: "cwd", filePath: "filePath", rootConfiguration: "rootConfiguration", diff --git a/packages/project/test/lib/graph/graphFromObject.js b/packages/project/test/lib/graph/graphFromObject.js index e331fdb4033..49733fe8fc9 100644 --- a/packages/project/test/lib/graph/graphFromObject.js +++ b/packages/project/test/lib/graph/graphFromObject.js @@ -48,14 +48,20 @@ test.afterEach.always((t) => { test("Application A", async (t) => { const {graphFromObject} = t.context; - const projectGraph = await graphFromObject({dependencyTree: getApplicationATree()}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: getApplicationATree() + }); const rootProject = projectGraph.getRoot(); t.is(rootProject.getName(), "application.a", "Returned correct root project"); }); test("Application A: Traverse project graph breadth first", async (t) => { const {graphFromObject} = t.context; - const projectGraph = await graphFromObject({dependencyTree: getApplicationATree()}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: getApplicationATree() + }); const callbackStub = t.context.sinon.stub().resolves(); await projectGraph.traverseBreadthFirst(callbackStub); @@ -74,7 +80,10 @@ test("Application A: Traverse project graph breadth first", async (t) => { test("Application Cycle A: Traverse project graph breadth first with cycles", async (t) => { const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: applicationCycleATreeIncDeduped}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: applicationCycleATreeIncDeduped + }); const callbackStub = sinon.stub().resolves(); const error = await t.throwsAsync(projectGraph.traverseBreadthFirst(callbackStub)); @@ -96,7 +105,10 @@ test("Application Cycle A: Traverse project graph breadth first with cycles", as test("Application Cycle B: Traverse project graph breadth first with cycles", async (t) => { const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: applicationCycleBTreeIncDeduped}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: applicationCycleBTreeIncDeduped + }); const callbackStub = sinon.stub().resolves(); await projectGraph.traverseBreadthFirst(callbackStub); @@ -115,7 +127,10 @@ test("Application Cycle B: Traverse project graph breadth first with cycles", as test("Application A: Traverse project graph depth first", async (t) => { const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: getApplicationATree()}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: getApplicationATree() + }); const callbackStub = sinon.stub().resolves(); await projectGraph.traverseDepthFirst(callbackStub); @@ -136,7 +151,10 @@ test("Application A: Traverse project graph depth first", async (t) => { test("Application Cycle A: Traverse project graph depth first with cycles", async (t) => { const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: applicationCycleATreeIncDeduped}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: applicationCycleATreeIncDeduped + }); const callbackStub = sinon.stub().resolves(); const error = await t.throwsAsync(projectGraph.traverseDepthFirst(callbackStub)); @@ -150,7 +168,10 @@ test("Application Cycle A: Traverse project graph depth first with cycles", asyn test("Application Cycle B: Traverse project graph depth first with cycles", async (t) => { const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: applicationCycleBTreeIncDeduped}); + const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: applicationCycleBTreeIncDeduped + }); const callbackStub = sinon.stub().resolves(); const error = await t.throwsAsync(projectGraph.traverseDepthFirst(callbackStub)); @@ -179,7 +200,7 @@ async function _testBasicGraphCreation(t, tree, expectedOrder, bfs) { throw new Error("Test error: Parameter 'bfs' must be specified"); } const {graphFromObject, sinon} = t.context; - const projectGraph = await graphFromObject({dependencyTree: tree}); + const projectGraph = await graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}); const callbackStub = sinon.stub().resolves(); if (bfs) { await projectGraph.traverseBreadthFirst(callbackStub); @@ -258,7 +279,7 @@ test("Project with inline configuration for two projects", async (t) => { }] }; - await t.throwsAsync(graphFromObject({dependencyTree: tree}), + await t.throwsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}), { message: `Found 2 configurations of kind 'project' for module application.a.id. ` + @@ -315,7 +336,7 @@ test("Missing configuration file for root project", async (t) => { path: "/non-existent", dependencies: [] }; - await t.throwsAsync(graphFromObject({dependencyTree: tree}), + await t.throwsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}), { message: "Failed to create a UI5 project from module application.a.id at /non-existent. " + @@ -330,7 +351,7 @@ test("Missing id for root project", async (t) => { path: path.join(__dirname, "fixtures/application.a"), dependencies: [] }; - await t.throwsAsync(graphFromObject({dependencyTree: tree}), + await t.throwsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}), {message: "Could not create Module: Missing or empty parameter 'id'"}, "Rejected with error"); }); @@ -349,7 +370,7 @@ test("No type configured for root project", async (t) => { } } }; - const error = await t.throwsAsync(graphFromObject({dependencyTree: tree})); + const error = await t.throwsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree})); t.is(error.message, `Unable to create Specification instance: Unknown specification type 'undefined'`); }); @@ -361,7 +382,7 @@ test("Missing dependencies", async (t) => { version: "1.0.0", path: applicationAPath }); - await t.notThrowsAsync(graphFromObject({dependencyTree: tree}), + await t.notThrowsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}), "Gracefully accepted project with no dependencies attribute"); }); @@ -377,7 +398,7 @@ test("Missing second-level dependencies", async (t) => { path: path.join(applicationAPath, "node_modules", "library.d") }] }); - await t.notThrowsAsync(graphFromObject({dependencyTree: tree}), + await t.notThrowsAsync(graphFromObject({ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: tree}), "Gracefully accepted project with no dependencies attribute"); }); @@ -1268,7 +1289,10 @@ test("Project with project-shim extension with invalid dependency configuration" dependencies: [] }] }; - const validationError = await t.throwsAsync(graphFromObject({dependencyTree: tree}), { + const validationError = await t.throwsAsync(graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: tree + }), { instanceOf: ValidationError }); t.true(validationError.message.includes("Configuration must have required property 'metadata'"), @@ -1552,7 +1576,10 @@ test("Project with unknown extension dependency inline configuration", async (t) dependencies: [], }], }; - const validationError = await t.throwsAsync(graphFromObject({dependencyTree: tree})); + const validationError = await t.throwsAsync(graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", + dependencyTree: tree + })); t.is(validationError.message, `Unable to create Specification instance: Unknown specification type 'phony-pony'`, "Should throw with expected error message"); @@ -1633,6 +1660,7 @@ test("Project with middleware extension dependency", async (t) => { test("rootConfiguration", async (t) => { const {graphFromObject} = t.context; const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: getApplicationATree(), rootConfiguration: { specVersion: "2.6", @@ -1654,6 +1682,7 @@ test("rootConfiguration", async (t) => { test("rootConfig", async (t) => { const {graphFromObject} = t.context; const projectGraph = await graphFromObject({ + ui5DataDir: "/path/to/ui5-data-dir", dependencyTree: getApplicationATree(), cwd: applicationAPath, rootConfigPath: "ui5-test-configPath.yaml", diff --git a/packages/project/test/lib/graph/graphFromPackageDependencies.js b/packages/project/test/lib/graph/graphFromPackageDependencies.js index 491cc442ed5..1de2a3fd7c0 100644 --- a/packages/project/test/lib/graph/graphFromPackageDependencies.js +++ b/packages/project/test/lib/graph/graphFromPackageDependencies.js @@ -15,13 +15,19 @@ test.afterEach.always((t) => { }); test("Application A", async (t) => { - const projectGraph = await graphFromPackageDependencies({cwd: applicationAPath}); + const projectGraph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", + cwd: applicationAPath + }); const rootProject = projectGraph.getRoot(); t.is(rootProject.getName(), "application.a", "Returned correct root project"); }); test("Application A: Traverse project graph breadth first", async (t) => { - const projectGraph = await graphFromPackageDependencies({cwd: applicationAPath}); + const projectGraph = await graphFromPackageDependencies({ + ui5DataDir: "/path/to/ui5-data-dir", + cwd: applicationAPath + }); const callbackStub = t.context.sinon.stub().resolves(); await projectGraph.traverseBreadthFirst(callbackStub); diff --git a/packages/project/test/lib/graph/graphFromStaticFile.js b/packages/project/test/lib/graph/graphFromStaticFile.js index acff5761ab3..7eca21416c3 100644 --- a/packages/project/test/lib/graph/graphFromStaticFile.js +++ b/packages/project/test/lib/graph/graphFromStaticFile.js @@ -20,6 +20,7 @@ test.afterEach.always((t) => { test("Application H: Traverse project graph breadth first", async (t) => { const projectGraph = await graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath }); const callbackStub = t.context.sinon.stub().resolves(); @@ -37,6 +38,7 @@ test("Application H: Traverse project graph breadth first", async (t) => { test("Throws error if file not found", async (t) => { const err = await t.throwsAsync(graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: notExistingPath })); t.is(err.message, @@ -48,6 +50,7 @@ test("Throws error if file not found", async (t) => { test("Throws for missing id", async (t) => { const err = await t.throwsAsync(graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath, filePath: "projectDependencies-missing-id.yaml" })); @@ -60,6 +63,7 @@ test("Throws for missing id", async (t) => { test("Throws for missing version", async (t) => { const err = await t.throwsAsync(graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath, filePath: "projectDependencies-missing-version.yaml" })); @@ -72,6 +76,7 @@ test("Throws for missing version", async (t) => { test("Throws for missing path", async (t) => { const err = await t.throwsAsync(graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath, filePath: "projectDependencies-missing-path.yaml" })); @@ -84,6 +89,7 @@ test("Throws for missing path", async (t) => { test("rootConfiguration", async (t) => { const projectGraph = await graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath, rootConfiguration: { specVersion: "2.6", @@ -103,6 +109,7 @@ test("rootConfiguration", async (t) => { test("rootConfig", async (t) => { const projectGraph = await graphFromStaticFile({ + ui5DataDir: "/path/to/ui5-data-dir", cwd: applicationHPath, rootConfigPath: "../application.a/ui5-test-configPath.yaml" }); diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.integration.js b/packages/project/test/lib/graph/helpers/ui5Framework.integration.js index 13d867c969b..e08b2bc58d7 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.integration.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.integration.js @@ -11,6 +11,7 @@ const __dirname = import.meta.dirname; const fakeBaseDir = path.join(__dirname, "fake-tmp"); const ui5FrameworkBaseDir = path.join(fakeBaseDir, "homedir", ".ui5", "framework"); const ui5PackagesBaseDir = path.join(ui5FrameworkBaseDir, "packages"); +const defaultUi5DataDir = path.join(fakeBaseDir, "homedir", ".ui5"); test.beforeEach(async (t) => { const sinon = t.context.sinon = sinonGlobal.createSandbox(); @@ -136,9 +137,6 @@ test.beforeEach(async (t) => { "../../../../lib/ui5Framework/Openui5Resolver.js": t.context.Openui5Resolver, "../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5Resolver, "../../../../lib/config/Configuration.js": t.context.Configuration, - "../../../../lib/utils/dataDir.js": { - resolveUi5DataDir: sinon.stub().resolves(path.join(fakeBaseDir, "homedir", ".ui5")) - } }); t.context.projectGraphBuilder = await esmock.p("../../../../lib/graph/projectGraphBuilder.js", { @@ -496,9 +494,9 @@ function defineTest(testName, { getModuleByProjectName }; - await ui5Framework.enrichProjectGraph(projectGraph, {workspace}); + await ui5Framework.enrichProjectGraph(projectGraph, {workspace, ui5DataDir: defaultUi5DataDir}); } else { - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); } const callbackStub = sinon.stub().resolves(); @@ -707,7 +705,7 @@ function defineErrorTest(testName, { const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); await t.throwsAsync(async () => { - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); }, {message: expectedErrorMessage}); }); } @@ -785,7 +783,7 @@ test.serial("ui5Framework helper should not fail when no framework configuration }; const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); t.is(projectGraph, projectGraph, "Returned same graph without error"); }); @@ -813,7 +811,7 @@ test.serial("ui5Framework translator should not try to install anything when no const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); t.is(pacote.extract.callCount, 0, "No package should be extracted"); t.is(pacote.manifest.callCount, 0, "No manifest should be requested"); @@ -841,7 +839,7 @@ test.serial("ui5Framework helper shouldn't throw when framework version and libr const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); t.is(logStub.verbose.callCount, 5); t.deepEqual(logStub.verbose.getCall(0).args, [ @@ -918,7 +916,7 @@ test.serial( }); await t.throwsAsync(async () => { - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, {ui5DataDir: defaultUi5DataDir}); }, { message: `Failed to resolve library does.not.exist: Could not find library "does.not.exist"`}); }); diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index 3d9e91a7820..22176471ee9 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -13,6 +13,14 @@ const applicationAPath = path.join(__dirname, "..", "..", "..", "fixtures", "app const libraryDPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.d"); const libraryEPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.e"); const libraryFPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.f"); +const defaultUi5DataDir = path.resolve("fake-ui5-data-dir"); + +function withUi5DataDir(options = {}) { + return { + ui5DataDir: defaultUi5DataDir, + ...options + }; +} test.beforeEach(async (t) => { // Tests either rely on not having UI5_DATA_DIR defined, or explicitly define it @@ -123,7 +131,7 @@ test.serial("enrichProjectGraph", async (t) => { const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); @@ -191,7 +199,7 @@ test.serial("enrichProjectGraph: without framework configuration", async (t) => const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(projectGraph.getSize(), 1, "Project graph should remain unchanged"); t.is(log.verbose.callCount, 1); t.deepEqual(log.verbose.getCall(0).args, [ @@ -239,9 +247,9 @@ test.serial("enrichProjectGraph SNAPSHOT", async (t) => { const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph, { + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({ snapshotCache: SnapshotCache.Force - }); + })); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); @@ -332,7 +340,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride: "1.99"}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "1.99"})); t.is(Sapui5ResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { @@ -394,7 +402,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride: "1.99-SNAPSHOT"}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "1.99-SNAPSHOT"})); t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { @@ -457,7 +465,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride: "latest-snapshot"}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "latest-snapshot"})); t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { @@ -498,9 +506,9 @@ test.serial("enrichProjectGraph shouldn't throw when no framework version and no const projectGraph = await projectGraphBuilder(provider); // Framework override is fine, even if no framework version is configured - await ui5Framework.enrichProjectGraph(projectGraph, { + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({ versionOverride: "1.75.0" - }); + })); t.is(Sapui5ResolverResolveVersionStub.callCount, 0, "resolveVersion should not be called when no libraries are provided"); @@ -534,7 +542,7 @@ test.serial("enrichProjectGraph should skip framework project without version", const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(projectGraph.getSize(), 1, "Project graph should remain unchanged"); }); @@ -622,7 +630,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(projectGraph.getSize(), 3, "Project graph should remain unchanged"); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGrap should be called once"); @@ -721,7 +729,7 @@ test.serial("enrichProjectGraph should resolve framework project " + const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride: "3.4.5"}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "3.4.5"})); t.is(projectGraph.getSize(), 3, "Project graph should remain unchanged"); t.is(Sapui5ResolverResolveVersionStub.callCount, 1); @@ -777,7 +785,7 @@ test.serial("enrichProjectGraph should skip framework project when all dependenc const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(projectGraph.getSize(), 2, "Project graph should remain unchanged"); }); @@ -811,7 +819,7 @@ test.serial("enrichProjectGraph should throw for framework project with dependen const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - const err = await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph)); + const err = await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir())); t.is(err.message, installError.message); }); @@ -844,7 +852,7 @@ test.serial("enrichProjectGraph should throw for incorrect framework name", asyn const projectGraph = await projectGraphBuilder(provider); sinon.stub(projectGraph.getRoot(), "getFrameworkName").returns("Pony5"); - const err = await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph)); + const err = await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir())); t.is(err.message, `Unknown framework.name "Pony5" for project application.a. Must be "OpenUI5" or "SAPUI5"`, "Threw with expected error message"); }); @@ -866,7 +874,7 @@ test.serial("enrichProjectGraph should ignore root project without framework con const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()); t.is(projectGraph.getSize(), 1, "Project graph should remain unchanged"); }); @@ -941,7 +949,7 @@ test.serial("enrichProjectGraph should throw error when projectGraph contains a const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider); - await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph), { + await t.throwsAsync(ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir()), { message: `Duplicate framework dependency definition(s) found for project application.a: sap.ui.core.\n` + `Framework libraries should only be referenced via ui5.yaml configuration. Neither the root project, ` + `nor any of its dependencies should include them as direct dependencies (e.g. via package.json).` @@ -994,7 +1002,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider, {workspace}); - await ui5Framework.enrichProjectGraph(projectGraph, {workspace}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({workspace})); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ @@ -1053,7 +1061,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case const provider = new DependencyTreeProvider({dependencyTree}); const projectGraph = await projectGraphBuilder(provider, {workspace}); - await ui5Framework.enrichProjectGraph(projectGraph, {workspace}); + await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({workspace})); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ @@ -1110,7 +1118,9 @@ test.serial("enrichProjectGraph should use UI5 data dir from env var", async (t) const expectedUi5DataDir = path.resolve(dependencyTree.path, "./ui5-data-dir-from-env-var"); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, { + ui5DataDir: expectedUi5DataDir + }); t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{ @@ -1166,7 +1176,9 @@ test.serial("enrichProjectGraph should use UI5 data dir from configuration", asy const expectedUi5DataDir = path.resolve(dependencyTree.path, "./ui5-data-dir-from-config"); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, { + ui5DataDir: expectedUi5DataDir + }); t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{ @@ -1222,7 +1234,9 @@ test.serial("enrichProjectGraph should use absolute UI5 data dir from configurat const expectedUi5DataDir = path.resolve("/absolute-ui5-data-dir-from-config"); - await ui5Framework.enrichProjectGraph(projectGraph); + await ui5Framework.enrichProjectGraph(projectGraph, { + ui5DataDir: expectedUi5DataDir + }); t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{ diff --git a/packages/project/test/lib/ui5framework/AbstractResolver.js b/packages/project/test/lib/ui5framework/AbstractResolver.js index fe3065433d6..9f5b1ebff1e 100644 --- a/packages/project/test/lib/ui5framework/AbstractResolver.js +++ b/packages/project/test/lib/ui5framework/AbstractResolver.js @@ -556,18 +556,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest'", async ( const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR'", async (t) => { @@ -575,18 +572,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR'", async (t const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR-SNAPSHOT'", async (t) => { @@ -594,18 +588,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR-SNAPSHOT'", const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1-SNAPSHOT", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR'", async (t) => { @@ -613,18 +604,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR'", as const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1.75", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.75", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR-SNAPSHOT'", async (t) => { @@ -632,18 +620,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR-SNAPS const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1.79-SNAPSHOT", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.79-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH'", async (t) => { @@ -651,18 +636,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1.75.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.75.0", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.75.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH-SNAPSHOT'", async (t) => { @@ -670,18 +652,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1.79.0-SNAPSHOT", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.79.0-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion does not include prereleases for 'latest' version", async (t) => { @@ -689,18 +668,15 @@ test.serial("AbstractResolver: Static resolveVersion does not include prerelease const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.78.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'latest-snapshot'", async (t) => { @@ -708,18 +684,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest-snapshot'" const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0-SNAPSHOT", "1.75.1-SNAPSHOT", "1.76.0-SNAPSHOT", "1.76.1-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("latest-snapshot", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("latest-snapshot", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.76.1-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion includes non-prereleases for 'latest-snapshot'", async (t) => { @@ -729,42 +702,28 @@ test.serial("AbstractResolver: Static resolveVersion includes non-prereleases fo const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT", "1.79.1"]); - const version = await MyResolver.resolveVersion("latest-snapshot", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("latest-snapshot", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.79.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion without options", async (t) => { const {MyResolver} = t.context; - const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") - .returns(["1.75.0"]); - - await MyResolver.resolveVersion("1.75.0"); - - t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: undefined, - ui5DataDir: undefined - }], "fetchAllVersions should be called with expected arguments"); + const error = await t.throwsAsync(MyResolver.resolveVersion("1.75.0")); + t.is(error.message, "MyResolver: Missing parameter \"ui5DataDir\""); }); test.serial("AbstractResolver: Static resolveVersion throws error for 'lts'", async (t) => { const {MyResolver} = t.context; const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions"); - const error = await t.throwsAsync(MyResolver.resolveVersion("lts", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(MyResolver.resolveVersion("lts", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "lts" is incorrect or not supported`); @@ -776,18 +735,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves '1.x'", async (t) const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("1.x", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.x", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '1.75.x'", async (t) => { @@ -795,18 +751,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves '1.75.x'", async ( const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("1.75.x", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("1.75.x", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '^1.75.0'", async (t) => { @@ -814,18 +767,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves '^1.75.0'", async const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("^1.75.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("^1.75.0", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '~1.75.0'", async (t) => { @@ -833,18 +783,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves '~1.75.0'", async const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("~1.75.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("~1.75.0", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '> 1.75.0 < 1.75.3'", async (t) => { @@ -852,18 +799,15 @@ test.serial("AbstractResolver: Static resolveVersion resolves '> 1.75.0 < 1.75.3 const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.75.2", "1.75.3"]); - const version = await MyResolver.resolveVersion("> 1.75.0 < 1.75.3", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("> 1.75.0 < 1.75.3", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "1.75.2", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'x.x.x-SNAPSHOT'", async (t) => { @@ -871,20 +815,17 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'x.x.x-SNAPSHOT'", const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0-SNAPSHOT", "1.76.0-SNAPSHOT", "1.77.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("x.x.x-SNAPSHOT", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("x.x.x-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); // All ranges ending with -SNAPSHOT should use "includePrerelease" in order to // properly match prerelease (i.e. -SNAPSHOT) versions. t.is(version, "1.77.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '^2.0.0-SNAPSHOT'", async (t) => { @@ -892,20 +833,17 @@ test.serial("AbstractResolver: Static resolveVersion resolves '^2.0.0-SNAPSHOT'" const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["2.0.0-SNAPSHOT", "2.0.1-SNAPSHOT", "2.1.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("^2.0.0-SNAPSHOT", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("^2.0.0-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); // All ranges ending with -SNAPSHOT should use "includePrerelease" in order to // properly match prerelease (i.e. -SNAPSHOT) versions. t.is(version, "2.1.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '2.x.x-alpha'", async (t) => { @@ -913,20 +851,17 @@ test.serial("AbstractResolver: Static resolveVersion resolves '2.x.x-alpha'", as const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["2.0.0-alpha", "2.0.1-alpha", "2.1.0-alpha"]); - const version = await MyResolver.resolveVersion("^2.0.0-alpha", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("^2.0.0-alpha", "/ui5DataDir", {cwd: "/cwd"}); // Prerelease ranges other than -SNAPSHOT should not use "includePrerelease" // and therefore not match pre-releases like normal versions t.is(version, "2.0.0-alpha", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'next' using tags", async (t) => { @@ -939,23 +874,20 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'next' using tags" "next": "2.0.0" }); - const version = await MyResolver.resolveVersion("next", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("next", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "2.0.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'next' to a pre-release using tags", async (t) => { @@ -968,23 +900,20 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'next' to a pre-re "next": "2.0.0-SNAPSHOT" }); - const version = await MyResolver.resolveVersion("next", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version = await MyResolver.resolveVersion("next", "/ui5DataDir", {cwd: "/cwd"}); t.is(version, "2.0.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); @@ -998,34 +927,28 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest' using tag // Resolver does not support tags (resolves with "null" instead of an object) // 'latest' should resolve to the highest version available - const version1 = await MyResolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version1 = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); t.is(version1, "2.0.0", "Resolved version should be correct"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); - t.deepEqual(fetchAllVersionsStub.getCall(0).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); // Change behavior of Resolver to support tags, so that version should be used now // instead of the highest version fetchAllTagsStub.resolves({ "latest": "1.76.0" }); - const version2 = await MyResolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }); + const version2 = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); t.is(version2, "1.76.0", "Resolved version should be correct"); t.is(fetchAllTagsStub.callCount, 2, "fetchAllTagsStub should be called twice"); - t.deepEqual(fetchAllVersionsStub.getCall(1).args, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(1).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion throws error for empty string", async (t) => { @@ -1033,10 +956,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for empty stri sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(MyResolver.resolveVersion("", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "" is incorrect or not supported`); }); @@ -1046,10 +966,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for invalid ta sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("%20", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(MyResolver.resolveVersion("%20", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "%20" is incorrect or not supported`); }); @@ -1061,10 +978,9 @@ test.serial("AbstractResolver: Static resolveVersion throws error for non-existi sinon.stub(MyResolver, "fetchAllTags") .resolves({"latest": "1.76.0"}); - const error = await t.throwsAsync(MyResolver.resolveVersion("this-tag-does-not-exist", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync( + MyResolver.resolveVersion("this-tag-does-not-exist", "/ui5DataDir", {cwd: "/cwd"}) + ); t.is(error.message, `Could not resolve framework version via tag 'this-tag-does-not-exist'. ` + `Make sure the tag is available in the configured registry.` @@ -1076,10 +992,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for version no sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("1.74.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(MyResolver.resolveVersion("1.74.0", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.74.0. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1095,10 +1008,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.50.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.50.0", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.50.0. Note that OpenUI5 framework libraries can only be ` + @@ -1115,10 +1025,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns(["1.76.0", "1.76.1", "1.90.0"]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.75.0", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.75.0", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.75.0. Note that SAPUI5 framework libraries can only be ` + @@ -1135,10 +1042,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version latest. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1154,10 +1058,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("latest", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version latest. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1173,10 +1074,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.99", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.99", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.99. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1192,10 +1090,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.99", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.99", "/ui5DataDir", {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.99. ` + `Make sure the version is valid and available in the configured registry.`); diff --git a/packages/project/test/lib/ui5framework/Openui5Resolver.integration.js b/packages/project/test/lib/ui5framework/Openui5Resolver.integration.js index e8049f0a412..6728a944c59 100644 --- a/packages/project/test/lib/ui5framework/Openui5Resolver.integration.js +++ b/packages/project/test/lib/ui5framework/Openui5Resolver.integration.js @@ -126,7 +126,9 @@ test.serial("resolveVersion", async (t) => { // Generic testing without and with options argument const optionsArguments = [ - undefined, + { + ui5DataDir: defaultUi5DataDir + }, { cwd: path.join(fakeBaseDir, "custom-cwd"), ui5DataDir: path.join(fakeBaseDir, "custom-datadir", ".ui5") @@ -138,49 +140,49 @@ test.serial("resolveVersion", async (t) => { pacote.packument.resetHistory(); // Ranges - t.is(await Openui5Resolver.resolveVersion("1", options), "1.120.1"); - t.is(await Openui5Resolver.resolveVersion("1.120", options), "1.120.1"); - t.is(await Openui5Resolver.resolveVersion("1.x", options), "1.120.1"); - t.is(await Openui5Resolver.resolveVersion("1.x.x", options), "1.120.1"); - t.is(await Openui5Resolver.resolveVersion("^1", options), "1.120.1"); - t.is(await Openui5Resolver.resolveVersion("*", options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("1", options.ui5DataDir, options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("1.120", options.ui5DataDir, options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("1.x", options.ui5DataDir, options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("1.x.x", options.ui5DataDir, options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("^1", options.ui5DataDir, options), "1.120.1"); + t.is(await Openui5Resolver.resolveVersion("*", options.ui5DataDir, options), "1.120.1"); // Tags - t.is(await Openui5Resolver.resolveVersion("latest", options), "1.120.0"); - t.is(await Openui5Resolver.resolveVersion("next", options), "2.0.0-rc.1"); - t.is(await Openui5Resolver.resolveVersion("not-a-snapshot", options), "1.118.0"); + t.is(await Openui5Resolver.resolveVersion("latest", options.ui5DataDir, options), "1.120.0"); + t.is(await Openui5Resolver.resolveVersion("next", options.ui5DataDir, options), "2.0.0-rc.1"); + t.is(await Openui5Resolver.resolveVersion("not-a-snapshot", options.ui5DataDir, options), "1.118.0"); // Exact versions - t.is(await Openui5Resolver.resolveVersion("1.118.0", options), "1.118.0"); - t.is(await Openui5Resolver.resolveVersion("2.0.0-rc.1", options), "2.0.0-rc.1"); - t.is(await Openui5Resolver.resolveVersion("1.123.4-SNAPSHOT", options), "1.123.4-SNAPSHOT"); + t.is(await Openui5Resolver.resolveVersion("1.118.0", options.ui5DataDir, options), "1.118.0"); + t.is(await Openui5Resolver.resolveVersion("2.0.0-rc.1", options.ui5DataDir, options), "2.0.0-rc.1"); + t.is(await Openui5Resolver.resolveVersion("1.123.4-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); // SNAPSHOT ranges - t.is(await Openui5Resolver.resolveVersion("1-SNAPSHOT", options), "1.123.4-SNAPSHOT"); - t.is(await Openui5Resolver.resolveVersion("1.123-SNAPSHOT", options), "1.123.4-SNAPSHOT"); + t.is(await Openui5Resolver.resolveVersion("1-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); + t.is(await Openui5Resolver.resolveVersion("1.123-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); // Error cases - await t.throwsAsync(Openui5Resolver.resolveVersion("", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("", options.ui5DataDir, options), { message: `Framework version specifier "" is incorrect or not supported` }); - await t.throwsAsync(Openui5Resolver.resolveVersion("tag-does-not-exist", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("tag-does-not-exist", options.ui5DataDir, options), { message: `Could not resolve framework version via tag 'tag-does-not-exist'. ` + `Make sure the tag is available in the configured registry.` }); - await t.throwsAsync(Openui5Resolver.resolveVersion("invalid-tag-%20", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("invalid-tag-%20", options.ui5DataDir, options), { message: `Framework version specifier "invalid-tag-%20" is incorrect or not supported` }); - await t.throwsAsync(Openui5Resolver.resolveVersion("1.999.9", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("1.999.9", options.ui5DataDir, options), { message: `Could not resolve framework version 1.999.9. ` + `Make sure the version is valid and available in the configured registry.` }); - await t.throwsAsync(Openui5Resolver.resolveVersion("1.0.0", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("1.0.0", options.ui5DataDir, options), { message: `Could not resolve framework version 1.0.0. ` + `Note that OpenUI5 framework libraries can only be consumed by the UI5 CLI ` + `starting with OpenUI5 v1.52.5` }); - await t.throwsAsync(Openui5Resolver.resolveVersion("^999", options), { + await t.throwsAsync(Openui5Resolver.resolveVersion("^999", options.ui5DataDir, options), { message: `Could not resolve framework version ^999. ` + `Make sure the version is valid and available in the configured registry.` }); diff --git a/packages/project/test/lib/ui5framework/Openui5Resolver.js b/packages/project/test/lib/ui5framework/Openui5Resolver.js index 278a4077d74..db3a4526ade 100644 --- a/packages/project/test/lib/ui5framework/Openui5Resolver.js +++ b/packages/project/test/lib/ui5framework/Openui5Resolver.js @@ -2,7 +2,6 @@ import test from "ava"; import sinon from "sinon"; import esmock from "esmock"; import path from "node:path"; -import os from "node:os"; test.beforeEach(async (t) => { t.context.InstallerStub = sinon.stub(); @@ -151,7 +150,7 @@ test.serial("Openui5Resolver: Static _getInstaller", (t) => { ui5DataDir: "/ui5DataDir" }; - const installer = Openui5Resolver._getInstaller(options); + const installer = Openui5Resolver._getInstaller(options.ui5DataDir, 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"); @@ -165,15 +164,11 @@ test.serial("Openui5Resolver: Static _getInstaller", (t) => { test.serial("Openui5Resolver: Static _getInstaller without options", (t) => { const {Openui5Resolver} = t.context; - 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: process.cwd(), - ui5DataDir: path.join(os.homedir(), ".ui5") - }], "Installer should be called with expected arguments"); + const err = t.throws(() => { + Openui5Resolver._getInstaller(); + }); + t.is(err.message, "Openui5Resolver: Missing parameter \"ui5DataDir\""); + t.is(t.context.InstallerStub.callCount, 0, "Installer should not be called"); }); test.serial("Openui5Resolver: Static fetchAllVersions", async (t) => { @@ -185,7 +180,9 @@ test.serial("Openui5Resolver: Static fetchAllVersions", async (t) => { const getInstallerSpy = sinon.spy(Openui5Resolver, "_getInstaller"); - const versions = await Openui5Resolver.fetchAllVersions(); + const versions = await Openui5Resolver.fetchAllVersions("/ui5DataDir", { + cwd: "/cwd" + }); t.deepEqual(versions, expectedVersions, "Fetched versions should be correct"); @@ -194,7 +191,10 @@ test.serial("Openui5Resolver: Static fetchAllVersions", async (t) => { "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"); + t.deepEqual(getInstallerSpy.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "_getInstaller should be called with expected arguments"); }); test.serial("Openui5Resolver: Static fetchAllTags", async (t) => { @@ -206,7 +206,9 @@ test.serial("Openui5Resolver: Static fetchAllTags", async (t) => { const getInstallerSpy = sinon.spy(Openui5Resolver, "_getInstaller"); - const tags = await Openui5Resolver.fetchAllTags(); + const tags = await Openui5Resolver.fetchAllTags("/ui5DataDir", { + cwd: "/cwd" + }); t.deepEqual(tags, expectedTags, "Fetched tags should be correct"); @@ -215,5 +217,8 @@ test.serial("Openui5Resolver: Static fetchAllTags", async (t) => { "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"); + t.deepEqual(getInstallerSpy.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "_getInstaller should be called with expected arguments"); }); diff --git a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.integration.js b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.integration.js index c82fc42ab9a..56f9cf38b48 100644 --- a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.integration.js +++ b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.integration.js @@ -93,6 +93,9 @@ test.afterEach.always((t) => { test.serial("resolveVersion", async (t) => { const {Sapui5MavenSnapshotResolver, makeFetchHappen, logStub, sinon} = t.context; + const options = { + ui5DataDir: path.join(fakeBaseDir, "homedir", ".ui5") + }; makeFetchHappen.withArgs("_SNAPSHOT_URL_/com/sap/ui5/dist/sapui5-sdk-dist/maven-metadata.xml") .resolves({ @@ -117,37 +120,52 @@ test.serial("resolveVersion", async (t) => { // Exact SNAPSHOT versions - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("1.123.4-SNAPSHOT"), "1.123.4-SNAPSHOT"); - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("2.0.1-SNAPSHOT"), "2.0.1-SNAPSHOT"); + t.is( + await Sapui5MavenSnapshotResolver.resolveVersion("1.123.4-SNAPSHOT", options.ui5DataDir, options), + "1.123.4-SNAPSHOT" + ); + t.is( + await Sapui5MavenSnapshotResolver.resolveVersion("2.0.1-SNAPSHOT", options.ui5DataDir, options), + "2.0.1-SNAPSHOT" + ); // latest-snapshot - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("latest-snapshot"), "2.1.2-SNAPSHOT"); + t.is( + await Sapui5MavenSnapshotResolver.resolveVersion("latest-snapshot", options.ui5DataDir, options), + "2.1.2-SNAPSHOT" + ); // SNAPSHOT ranges - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("1-SNAPSHOT"), "1.123.4-SNAPSHOT"); - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("2-SNAPSHOT"), "2.1.2-SNAPSHOT"); - t.is(await Sapui5MavenSnapshotResolver.resolveVersion("1.123-SNAPSHOT"), "1.123.4-SNAPSHOT"); + t.is( + await Sapui5MavenSnapshotResolver.resolveVersion("1-SNAPSHOT", options.ui5DataDir, options), + "1.123.4-SNAPSHOT" + ); + t.is(await Sapui5MavenSnapshotResolver.resolveVersion("2-SNAPSHOT", options.ui5DataDir, options), "2.1.2-SNAPSHOT"); + t.is( + await Sapui5MavenSnapshotResolver.resolveVersion("1.123-SNAPSHOT", options.ui5DataDir, options), + "1.123.4-SNAPSHOT" + ); // Error cases - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion(""), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("", options.ui5DataDir, options), { message: `Framework version specifier "" is incorrect or not supported` }); - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("tag-does-not-exist"), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("tag-does-not-exist", options.ui5DataDir, options), { message: `Framework version specifier "tag-does-not-exist" is incorrect or not supported` }); - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("invalid-tag-%20"), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("invalid-tag-%20", options.ui5DataDir, options), { message: `Framework version specifier "invalid-tag-%20" is incorrect or not supported` }); - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("1.999.9"), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("1.999.9", options.ui5DataDir, options), { message: `Could not resolve framework version 1.999.9. ` + `Make sure the version is valid and available in the configured registry.` }); - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("1.0.0-SNAPSHOT"), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("1.0.0-SNAPSHOT", options.ui5DataDir, options), { message: `Could not resolve framework version 1.0.0-SNAPSHOT. ` + `Make sure the version is valid and available in the configured registry.` }); - await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("3-SNAPSHOT"), { + await t.throwsAsync(Sapui5MavenSnapshotResolver.resolveVersion("3-SNAPSHOT", options.ui5DataDir, options), { message: `Could not resolve framework version 3-SNAPSHOT. ` + `Make sure the version is valid and available in the configured registry.` }); diff --git a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js index a88836ae2fd..96ec4def9d1 100644 --- a/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js +++ b/packages/project/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js @@ -2,7 +2,6 @@ import test from "ava"; import sinon from "sinon"; import esmock from "esmock"; import path from "node:path"; -import os from "node:os"; test.beforeEach(async (t) => { t.context.InstallerStub = sinon.stub(); @@ -379,7 +378,7 @@ test.serial("Sapui5MavenSnapshotResolver: Static fetchAllVersions", async (t) => sinon.stub(Sapui5MavenSnapshotResolver, "_createSnapshotEndpointUrlCallback") .returns("snapshotEndpointUrlCallback"); - const versions = await Sapui5MavenSnapshotResolver.fetchAllVersions(options); + const versions = await Sapui5MavenSnapshotResolver.fetchAllVersions(options.ui5DataDir, options); t.deepEqual(versions, expectedVersions, "Fetched versions should be correct"); @@ -402,28 +401,9 @@ test.serial("Sapui5MavenSnapshotResolver: Static fetchAllVersions", async (t) => test.serial("Sapui5MavenSnapshotResolver: Static fetchAllVersions without options", async (t) => { const {Sapui5MavenSnapshotResolver} = t.context; - const expectedVersions = ["1.75.0-SNAPSHOT", "1.75.1-SNAPSHOT", "1.76.0-SNAPSHOT"]; - - t.context.fetchPackageVersionsStub.returns(expectedVersions); - sinon.stub(Sapui5MavenSnapshotResolver, "_createSnapshotEndpointUrlCallback") - .returns("snapshotEndpointUrlCallback"); - - const versions = await Sapui5MavenSnapshotResolver.fetchAllVersions(); - - t.deepEqual(versions, expectedVersions, "Fetched versions should be correct"); - - t.is(t.context.fetchPackageVersionsStub.callCount, 1, "fetchPackageVersions should be called once"); - t.deepEqual(t.context.fetchPackageVersionsStub.getCall(0).args, - [{artifactId: "sapui5-sdk-dist", groupId: "com.sap.ui5.dist"}], - "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(), - snapshotEndpointUrlCb: "snapshotEndpointUrlCallback", - ui5DataDir: path.join(os.homedir(), ".ui5") - }], "Installer should be called with expected arguments"); + const err = await t.throwsAsync(Sapui5MavenSnapshotResolver.fetchAllVersions()); + t.is(err.message, "Sapui5MavenSnapshotResolver: Missing parameter \"ui5DataDir\""); + t.is(t.context.InstallerStub.callCount, 0, "Installer should not be called"); }); test.serial("_createSnapshotEndpointUrlCallback: Environment variable", async (t) => { diff --git a/packages/project/test/lib/ui5framework/Sapui5Resolver.integration.js b/packages/project/test/lib/ui5framework/Sapui5Resolver.integration.js index 3b04805ad42..3dd59ba0b20 100644 --- a/packages/project/test/lib/ui5framework/Sapui5Resolver.integration.js +++ b/packages/project/test/lib/ui5framework/Sapui5Resolver.integration.js @@ -126,7 +126,9 @@ test.serial("resolveVersion", async (t) => { // Generic testing without and with options argument const optionsArguments = [ - undefined, + { + ui5DataDir: defaultUi5DataDir + }, { cwd: path.join(fakeBaseDir, "custom-cwd"), ui5DataDir: path.join(fakeBaseDir, "custom-datadir", ".ui5") @@ -138,49 +140,49 @@ test.serial("resolveVersion", async (t) => { pacote.packument.resetHistory(); // Ranges - t.is(await Sapui5Resolver.resolveVersion("1", options), "1.120.1"); - t.is(await Sapui5Resolver.resolveVersion("1.120", options), "1.120.1"); - t.is(await Sapui5Resolver.resolveVersion("1.x", options), "1.120.1"); - t.is(await Sapui5Resolver.resolveVersion("1.x.x", options), "1.120.1"); - t.is(await Sapui5Resolver.resolveVersion("^1", options), "1.120.1"); - t.is(await Sapui5Resolver.resolveVersion("*", options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("1", options.ui5DataDir, options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("1.120", options.ui5DataDir, options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("1.x", options.ui5DataDir, options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("1.x.x", options.ui5DataDir, options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("^1", options.ui5DataDir, options), "1.120.1"); + t.is(await Sapui5Resolver.resolveVersion("*", options.ui5DataDir, options), "1.120.1"); // Tags - t.is(await Sapui5Resolver.resolveVersion("latest", options), "1.120.0"); - t.is(await Sapui5Resolver.resolveVersion("next", options), "2.0.0-rc.1"); - t.is(await Sapui5Resolver.resolveVersion("not-a-snapshot", options), "1.118.0"); + t.is(await Sapui5Resolver.resolveVersion("latest", options.ui5DataDir, options), "1.120.0"); + t.is(await Sapui5Resolver.resolveVersion("next", options.ui5DataDir, options), "2.0.0-rc.1"); + t.is(await Sapui5Resolver.resolveVersion("not-a-snapshot", options.ui5DataDir, options), "1.118.0"); // Exact versions - t.is(await Sapui5Resolver.resolveVersion("1.118.0", options), "1.118.0"); - t.is(await Sapui5Resolver.resolveVersion("2.0.0-rc.1", options), "2.0.0-rc.1"); - t.is(await Sapui5Resolver.resolveVersion("1.123.4-SNAPSHOT", options), "1.123.4-SNAPSHOT"); + t.is(await Sapui5Resolver.resolveVersion("1.118.0", options.ui5DataDir, options), "1.118.0"); + t.is(await Sapui5Resolver.resolveVersion("2.0.0-rc.1", options.ui5DataDir, options), "2.0.0-rc.1"); + t.is(await Sapui5Resolver.resolveVersion("1.123.4-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); // SNAPSHOT ranges - t.is(await Sapui5Resolver.resolveVersion("1-SNAPSHOT", options), "1.123.4-SNAPSHOT"); - t.is(await Sapui5Resolver.resolveVersion("1.123-SNAPSHOT", options), "1.123.4-SNAPSHOT"); + t.is(await Sapui5Resolver.resolveVersion("1-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); + t.is(await Sapui5Resolver.resolveVersion("1.123-SNAPSHOT", options.ui5DataDir, options), "1.123.4-SNAPSHOT"); // Error cases - await t.throwsAsync(Sapui5Resolver.resolveVersion("", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("", options.ui5DataDir, options), { message: `Framework version specifier "" is incorrect or not supported` }); - await t.throwsAsync(Sapui5Resolver.resolveVersion("tag-does-not-exist", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("tag-does-not-exist", options.ui5DataDir, options), { message: `Could not resolve framework version via tag 'tag-does-not-exist'. ` + `Make sure the tag is available in the configured registry.` }); - await t.throwsAsync(Sapui5Resolver.resolveVersion("invalid-tag-%20", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("invalid-tag-%20", options.ui5DataDir, options), { message: `Framework version specifier "invalid-tag-%20" is incorrect or not supported` }); - await t.throwsAsync(Sapui5Resolver.resolveVersion("1.999.9", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("1.999.9", options.ui5DataDir, options), { message: `Could not resolve framework version 1.999.9. ` + `Make sure the version is valid and available in the configured registry.` }); - await t.throwsAsync(Sapui5Resolver.resolveVersion("1.0.0", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("1.0.0", options.ui5DataDir, options), { message: `Could not resolve framework version 1.0.0. ` + `Note that SAPUI5 framework libraries can only be consumed by the UI5 CLI ` + `starting with SAPUI5 v1.76.0` }); - await t.throwsAsync(Sapui5Resolver.resolveVersion("^999", options), { + await t.throwsAsync(Sapui5Resolver.resolveVersion("^999", options.ui5DataDir, options), { message: `Could not resolve framework version ^999. ` + `Make sure the version is valid and available in the configured registry.` }); diff --git a/packages/project/test/lib/ui5framework/Sapui5Resolver.js b/packages/project/test/lib/ui5framework/Sapui5Resolver.js index 99c5bad9c44..d709ae7b810 100644 --- a/packages/project/test/lib/ui5framework/Sapui5Resolver.js +++ b/packages/project/test/lib/ui5framework/Sapui5Resolver.js @@ -2,7 +2,6 @@ import test from "ava"; import sinon from "sinon"; import esmock from "esmock"; import path from "node:path"; -import os from "node:os"; import Openui5Resolver from "../../../lib/ui5Framework/Openui5Resolver.js"; test.beforeEach(async (t) => { @@ -137,7 +136,7 @@ test.serial("Sapui5Resolver: Static _getInstaller", (t) => { ui5DataDir: "/ui5DataDir" }; - const installer = Sapui5Resolver._getInstaller(options); + const installer = Sapui5Resolver._getInstaller(options.ui5DataDir, 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"); @@ -151,15 +150,11 @@ test.serial("Sapui5Resolver: Static _getInstaller", (t) => { test.serial("Sapui5Resolver: Static _getInstaller without options", (t) => { const {Sapui5Resolver} = t.context; - const installer = Sapui5Resolver._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: process.cwd(), - ui5DataDir: path.join(os.homedir(), ".ui5") - }], "Installer should be called with expected arguments"); + const err = t.throws(() => { + Sapui5Resolver._getInstaller(); + }); + t.is(err.message, "Sapui5Resolver: Missing parameter \"ui5DataDir\""); + t.is(t.context.InstallerStub.callCount, 0, "Installer should not be called"); }); test.serial("Sapui5Resolver: Static fetchAllVersions", async (t) => { @@ -171,7 +166,9 @@ test.serial("Sapui5Resolver: Static fetchAllVersions", async (t) => { const getInstallerSpy = sinon.spy(Sapui5Resolver, "_getInstaller"); - const versions = await Sapui5Resolver.fetchAllVersions(); + const versions = await Sapui5Resolver.fetchAllVersions("/ui5DataDir", { + cwd: "/cwd" + }); t.deepEqual(versions, expectedVersions, "Fetched versions should be correct"); @@ -179,7 +176,10 @@ test.serial("Sapui5Resolver: Static fetchAllVersions", async (t) => { t.deepEqual(t.context.fetchPackageVersionsStub.getCall(0).args, [{pkgName: "@sapui5/distribution-metadata"}], "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"); + t.deepEqual(getInstallerSpy.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "_getInstaller should be called with expected arguments"); }); test.serial("Sapui5Resolver: Static fetchAllTags", async (t) => { @@ -191,7 +191,9 @@ test.serial("Sapui5Resolver: Static fetchAllTags", async (t) => { const getInstallerSpy = sinon.spy(Sapui5Resolver, "_getInstaller"); - const tags = await Sapui5Resolver.fetchAllTags(); + const tags = await Sapui5Resolver.fetchAllTags("/ui5DataDir", { + cwd: "/cwd" + }); t.deepEqual(tags, expectedTags, "Fetched tags should be correct"); @@ -200,7 +202,10 @@ test.serial("Sapui5Resolver: Static fetchAllTags", async (t) => { "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"); + t.deepEqual(getInstallerSpy.getCall(0).args, [ + "/ui5DataDir", + {cwd: "/cwd"} + ], "_getInstaller should be called with expected arguments"); }); test.serial( diff --git a/packages/server/lib/server.js b/packages/server/lib/server.js index 201b9f3a485..0d5ef77eaa9 100644 --- a/packages/server/lib/server.js +++ b/packages/server/lib/server.js @@ -135,9 +135,7 @@ async function _addSsl({app, key, cert}) { * @param {boolean} [options.serveCSPReports=false] Enable CSP reports serving for request url * '/.ui5/csp/csp-reports.json' * @param {string} [options.cache="Default"] Cache mode to use for building UI5 projects. - * @param {string} [options.ui5DataDir] Explicit UI5 data directory to use for the build cache. - * Overrides the UI5_DATA_DIR environment variable, - * the UI5 configuration file, and the default of ~/.ui5. + * @param {string} options.ui5DataDir Resolved UI5 data directory to use for the build cache. * @param {string[]} [options.includedTasks] A list of tasks to be added to the default execution set. * Takes precedence over excludedTasks. * @param {string[]} [options.excludedTasks] A list of tasks to be excluded from the default task @@ -154,6 +152,9 @@ export async function serve(graph, { simpleIndex = false, liveReload = false, serveCSPReports = false, cache = "Default", ui5DataDir, includedTasks, excludedTasks, }, error) { + if (!ui5DataDir) { + throw new Error("server.serve: Missing parameter \"ui5DataDir\""); + } const rootProject = graph.getRoot(); const readers = []; diff --git a/packages/server/test/lib/server/acceptRemoteConnections.js b/packages/server/test/lib/server/acceptRemoteConnections.js index 6bee8bb778c..45604578c36 100644 --- a/packages/server/test/lib/server/acceptRemoteConnections.js +++ b/packages/server/test/lib/server/acceptRemoteConnections.js @@ -9,14 +9,16 @@ let server; // Start server before running tests test.before(async (t) => { + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); server = await serve(graph, { + ui5DataDir, port: 3334, acceptRemoteConnections: true, - ui5DataDir: isolatedUi5DataDir(t), }); request = supertest("http://127.0.0.1:3334"); diff --git a/packages/server/test/lib/server/caching.js b/packages/server/test/lib/server/caching.js index ae5931ed7b6..f5a6c7ef3a8 100644 --- a/packages/server/test/lib/server/caching.js +++ b/packages/server/test/lib/server/caching.js @@ -9,13 +9,15 @@ let server; // Start server before running tests test.before(async (t) => { + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); server = await serve(graph, { + ui5DataDir, port: 3350, - ui5DataDir: isolatedUi5DataDir(t), }); request = supertest("http://localhost:3350"); }); diff --git a/packages/server/test/lib/server/h2.js b/packages/server/test/lib/server/h2.js index 6a7510e6616..bed5b324c01 100644 --- a/packages/server/test/lib/server/h2.js +++ b/packages/server/test/lib/server/h2.js @@ -17,8 +17,10 @@ if (nodeVersion < 24) { // Start server before running tests test.before(async (t) => { process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const sslPath = path.join(process.cwd(), "./test/fixtures/ssl/"); @@ -27,11 +29,11 @@ if (nodeVersion < 24) { path.join(sslPath, "server.crt"), ); server = await serve(graph, { + ui5DataDir, port: 3366, h2: true, key, cert, - ui5DataDir: isolatedUi5DataDir(t), }); request = supertest("https://localhost:3366"); }); diff --git a/packages/server/test/lib/server/main.js b/packages/server/test/lib/server/main.js index 8e2e0849f0c..d4b60208114 100644 --- a/packages/server/test/lib/server/main.js +++ b/packages/server/test/lib/server/main.js @@ -14,7 +14,9 @@ const SOURCE_MAPPING_URL = "//" + "# sourceMappingURL"; // Start server before running tests test.before(async (t) => { + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); @@ -25,8 +27,8 @@ test.before(async (t) => { }; server = await serve(graph, { + ui5DataDir, port: 3333, - ui5DataDir: isolatedUi5DataDir(t), }); request = supertest("http://localhost:3333"); }); @@ -379,14 +381,16 @@ async (t) => { test("Stop server", async (t) => { const port = 3351; const request = supertest(`http://localhost:${port}`); + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult = await serve(graph, { + ui5DataDir, port: port, - ui5DataDir: isolatedUi5DataDir(t), }); const res = await request.get("/resources/library/a/.library"); @@ -510,16 +514,18 @@ test("CSP (defaults)", async (t) => { test("CSP (sap policies)", async (t) => { const port = 3400; const request = supertest(`http://localhost:${port}`); + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult = await serve(graph, { + ui5DataDir, port, sendSAPTargetCSP: true, simpleIndex: false, - ui5DataDir: isolatedUi5DataDir(t), }); const [ @@ -625,16 +631,18 @@ test("CSP (sap policies)", async (t) => { test("CSP serveCSPReports", async (t) => { const port = 3450; const request = supertest(`http://localhost:${port}`); + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult = await serve(graph, { + ui5DataDir, port, serveCSPReports: true, simpleIndex: false, - ui5DataDir: isolatedUi5DataDir(t), }); const cspReport = { @@ -680,17 +688,19 @@ test("CSP serveCSPReports", async (t) => { test("CSP with ignore paths", async (t) => { const port = 3500; const request = supertest(`http://localhost:${port}`); + const ui5DataDir = isolatedUi5DataDir(t); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult = await serve(graph, { + ui5DataDir, port, serveCSPReports: true, sendSAPTargetCSP: true, simpleIndex: false, - ui5DataDir: isolatedUi5DataDir(t), }); const testrunnerRequest1 = request.get("/test-resources/sap/ui/qunit/testrunner.html"); const testrunnerRequest2 = request.get("/index.html") diff --git a/packages/server/test/lib/server/ports.js b/packages/server/test/lib/server/ports.js index 0dee288e112..2e4f30aa40e 100644 --- a/packages/server/test/lib/server/ports.js +++ b/packages/server/test/lib/server/ports.js @@ -18,6 +18,7 @@ test.afterEach.always((t) => { test.serial("Start server - Port is already taken and an error occurs", async (t) => { t.plan(6); const port = 3360; + const ui5DataDir = isolatedUi5DataDir(t); const nodeServer = http.createServer((req, res) => { res.end(); }); @@ -30,12 +31,13 @@ test.serial("Start server - Port is already taken and an error occurs", async (t }); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const startServer = serve(graph, { + ui5DataDir, port, - ui5DataDir: isolatedUi5DataDir(t), }); const error = await t.throwsAsync(startServer); @@ -72,6 +74,7 @@ test.serial("Start server together with node server - Port is already taken and t.plan(2); const port = 3370; const nextFoundPort = 3371; + const ui5DataDir = isolatedUi5DataDir(t); const nodeServer = http.createServer((req, res) => { res.end(); }); @@ -83,12 +86,13 @@ test.serial("Start server together with node server - Port is already taken and }); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const server = await serve(graph, { + ui5DataDir, port, changePortIfInUse: true, - ui5DataDir: isolatedUi5DataDir(t), }); t.deepEqual(server.port, nextFoundPort, "Resolves with correct port"); @@ -106,6 +110,7 @@ test.serial("Start server - Port can not be determined and an error occurs", asy const {sinon} = t.context; t.plan(2); + const ui5DataDir = isolatedUi5DataDir(t); const portscannerFake = function(port, portMax, host, callback) { return new Promise((resolve) => { callback(new Error("testError"), false); @@ -115,12 +120,13 @@ test.serial("Start server - Port can not be determined and an error occurs", asy const portScannerStub = sinon.stub(portscanner, "findAPortNotInUse").callsFake(portscannerFake); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const startServer = serve(graph, { + ui5DataDir, port: 3990, changePortIfInUse: true, - ui5DataDir: isolatedUi5DataDir(t), }); const error = await t.throwsAsync(startServer); @@ -136,6 +142,7 @@ test.serial( t.plan(6); const portStart = 4000; const portRange = 31; + const ui5DataDir = isolatedUi5DataDir(t); const servers = []; const serversStart = []; let port; @@ -156,13 +163,14 @@ test.serial( await Promise.all(serversStart); const graph = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const startServer = serve(graph, { + ui5DataDir, port: portStart, changePortIfInUse: true, - ui5DataDir: isolatedUi5DataDir(t), }); const error = await t.throwsAsync(startServer); @@ -202,6 +210,7 @@ test.serial("Start server twice - Port is already taken and the next one is used const nextFoundPort = 3381; const ui5DataDir = isolatedUi5DataDir(t); const graph1 = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult1 = await serve(graph1, { @@ -212,6 +221,7 @@ test.serial("Start server twice - Port is already taken and the next one is used t.deepEqual(serveResult1.port, port, "Resolves with correct port"); const graph2 = await graphFromPackageDependencies({ + ui5DataDir, cwd: "./test/fixtures/application.a" }); const serveResult2 = await serve(graph2, { diff --git a/packages/server/test/lib/server/server.js b/packages/server/test/lib/server/server.js index fc85c81c5d2..b31766d28bd 100644 --- a/packages/server/test/lib/server/server.js +++ b/packages/server/test/lib/server/server.js @@ -6,7 +6,8 @@ import {EventEmitter} from "node:events"; function createMockGraph(mockBuildServer) { const mockProject = { getName: sinon.stub().returns("test.project"), - getSourceReader: sinon.stub().returns({}) + getSourceReader: sinon.stub().returns({}), + getRootPath: sinon.stub().returns("/test/project") }; return { getRoot: sinon.stub().returns(mockProject), @@ -99,7 +100,10 @@ test("server.on('error') rejects the serve promise", async (t) => { const {serve} = await esmock("../../../lib/server.js", mocks); const graph = createMockGraph(mockBuildServer); - const error = await t.throwsAsync(serve(graph, {port: 3000})); + const error = await t.throwsAsync(serve(graph, { + ui5DataDir: "/path/to/ui5-data-dir", + port: 3000 + })); t.is(error, testError); }); @@ -114,7 +118,10 @@ test("buildServer 'error' event is forwarded to error callback", async (t) => { const graph = createMockGraph(mockBuildServer); const errorReceived = new Promise((resolve) => { - serve(graph, {port: 3000}, resolve).then(() => { + serve(graph, { + ui5DataDir: "/path/to/ui5-data-dir", + port: 3000 + }, resolve).then(() => { mockBuildServer.emit("error", testError); }); }); @@ -132,7 +139,10 @@ test("close() still calls server.close when buildServer.destroy() rejects", asyn const {serve} = await esmock("../../../lib/server.js", mocks); const graph = createMockGraph(mockBuildServer); - const result = await serve(graph, {port: 3000}); + const result = await serve(graph, { + ui5DataDir: "/path/to/ui5-data-dir", + port: 3000 + }); await new Promise((resolve) => { result.close(resolve); From 533260426b7d4d44f5c3fc67d7bb2608aac25588 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Thu, 23 Jul 2026 14:59:39 +0300 Subject: [PATCH 17/20] test: Fix failing scenarios for missed required arguments --- .../test/lib/graph/helpers/ui5Framework.js | 36 +++--- .../test/lib/ui5framework/AbstractResolver.js | 118 +++++++++--------- 2 files changed, 80 insertions(+), 74 deletions(-) diff --git a/packages/project/test/lib/graph/helpers/ui5Framework.js b/packages/project/test/lib/graph/helpers/ui5Framework.js index 22176471ee9..6e200d92635 100644 --- a/packages/project/test/lib/graph/helpers/ui5Framework.js +++ b/packages/project/test/lib/graph/helpers/ui5Framework.js @@ -343,10 +343,11 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "1.99"})); t.is(Sapui5ResolverResolveVersionStub.callCount, 1); - t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["1.99", { - cwd: dependencyTree.path, - ui5DataDir: path.resolve("fake-ui5-data-dir"), - }]); + t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, [ + "1.99", + path.resolve("fake-ui5-data-dir"), + {cwd: dependencyTree.path}, + ]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ @@ -405,10 +406,11 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "1.99-SNAPSHOT"})); t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); - t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["1.99-SNAPSHOT", { - cwd: dependencyTree.path, - ui5DataDir: path.resolve("fake-ui5-data-dir"), - }]); + t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, [ + "1.99-SNAPSHOT", + path.resolve("fake-ui5-data-dir"), + {cwd: dependencyTree.path}, + ]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, "Sapui5MavenSnapshotResolverStub#constructor should be called once"); @@ -468,10 +470,11 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot await ui5Framework.enrichProjectGraph(projectGraph, withUi5DataDir({versionOverride: "latest-snapshot"})); t.is(Sapui5MavenSnapshotResolverResolveVersionStub.callCount, 1); - t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, ["latest-snapshot", { - cwd: dependencyTree.path, - ui5DataDir: path.resolve("fake-ui5-data-dir"), - }]); + t.deepEqual(Sapui5MavenSnapshotResolverResolveVersionStub.getCall(0).args, [ + "latest-snapshot", + path.resolve("fake-ui5-data-dir"), + {cwd: dependencyTree.path}, + ]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, "Sapui5MavenSnapshotResolverStub#constructor should be called once"); @@ -733,10 +736,11 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(projectGraph.getSize(), 3, "Project graph should remain unchanged"); t.is(Sapui5ResolverResolveVersionStub.callCount, 1); - t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { - cwd: dependencyTree.path, - ui5DataDir: path.resolve("fake-ui5-data-dir"), - }]); + t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, [ + "3.4.5", + path.resolve("fake-ui5-data-dir"), + {cwd: dependencyTree.path}, + ]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); diff --git a/packages/project/test/lib/ui5framework/AbstractResolver.js b/packages/project/test/lib/ui5framework/AbstractResolver.js index 9f5b1ebff1e..6ccd0fd1fb2 100644 --- a/packages/project/test/lib/ui5framework/AbstractResolver.js +++ b/packages/project/test/lib/ui5framework/AbstractResolver.js @@ -3,13 +3,15 @@ import sinon from "sinon"; import path from "node:path"; import esmock from "esmock"; +const UI5_DATA_DIR = path.resolve("/ui5DataDir"); + test.beforeEach(async (t) => { t.context.AbstractResolver = await esmock.p("../../../lib/ui5Framework/AbstractResolver.js", {}); class MyResolver extends t.context.AbstractResolver { constructor(options = {}) { super({ - ui5DataDir: "/ui5DataDir", + ui5DataDir: UI5_DATA_DIR, ...options }); } @@ -556,13 +558,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest'", async ( const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -572,13 +574,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR'", async (t const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -588,13 +590,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR-SNAPSHOT'", const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1-SNAPSHOT", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -604,13 +606,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR'", as const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1.75", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.75", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -620,13 +622,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR-SNAPS const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1.79-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.79-SNAPSHOT", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -636,13 +638,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const version = await MyResolver.resolveVersion("1.75.0", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.75.0", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.75.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -652,13 +654,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.77.0-SNAPSHOT", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("1.79.0-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.79.0-SNAPSHOT", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.79.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -668,13 +670,13 @@ test.serial("AbstractResolver: Static resolveVersion does not include prerelease const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.78.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -684,13 +686,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest-snapshot'" const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0-SNAPSHOT", "1.75.1-SNAPSHOT", "1.76.0-SNAPSHOT", "1.76.1-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("latest-snapshot", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("latest-snapshot", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.76.1-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -702,13 +704,13 @@ test.serial("AbstractResolver: Static resolveVersion includes non-prereleases fo const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.76.0", "1.77.0", "1.78.0", "1.79.0-SNAPSHOT", "1.79.1"]); - const version = await MyResolver.resolveVersion("latest-snapshot", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("latest-snapshot", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.79.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -723,7 +725,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for 'lts'", as const {MyResolver} = t.context; const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions"); - const error = await t.throwsAsync(MyResolver.resolveVersion("lts", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(MyResolver.resolveVersion("lts", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "lts" is incorrect or not supported`); @@ -735,13 +737,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves '1.x'", async (t) const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("1.x", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.x", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -751,13 +753,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves '1.75.x'", async ( const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("1.75.x", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("1.75.x", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -767,13 +769,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves '^1.75.0'", async const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("^1.75.0", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("^1.75.0", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.76.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -783,13 +785,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves '~1.75.0'", async const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0", "2.0.0"]); - const version = await MyResolver.resolveVersion("~1.75.0", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("~1.75.0", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.75.1", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -799,13 +801,13 @@ test.serial("AbstractResolver: Static resolveVersion resolves '> 1.75.0 < 1.75.3 const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.75.2", "1.75.3"]); - const version = await MyResolver.resolveVersion("> 1.75.0 < 1.75.3", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("> 1.75.0 < 1.75.3", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "1.75.2", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -815,7 +817,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'x.x.x-SNAPSHOT'", const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0-SNAPSHOT", "1.76.0-SNAPSHOT", "1.77.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("x.x.x-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("x.x.x-SNAPSHOT", UI5_DATA_DIR, {cwd: "/cwd"}); // All ranges ending with -SNAPSHOT should use "includePrerelease" in order to // properly match prerelease (i.e. -SNAPSHOT) versions. @@ -823,7 +825,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'x.x.x-SNAPSHOT'", t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -833,7 +835,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves '^2.0.0-SNAPSHOT'" const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["2.0.0-SNAPSHOT", "2.0.1-SNAPSHOT", "2.1.0-SNAPSHOT"]); - const version = await MyResolver.resolveVersion("^2.0.0-SNAPSHOT", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("^2.0.0-SNAPSHOT", UI5_DATA_DIR, {cwd: "/cwd"}); // All ranges ending with -SNAPSHOT should use "includePrerelease" in order to // properly match prerelease (i.e. -SNAPSHOT) versions. @@ -841,7 +843,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves '^2.0.0-SNAPSHOT'" t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -851,7 +853,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves '2.x.x-alpha'", as const fetchAllVersionsStub = sinon.stub(MyResolver, "fetchAllVersions") .returns(["2.0.0-alpha", "2.0.1-alpha", "2.1.0-alpha"]); - const version = await MyResolver.resolveVersion("^2.0.0-alpha", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("^2.0.0-alpha", UI5_DATA_DIR, {cwd: "/cwd"}); // Prerelease ranges other than -SNAPSHOT should not use "includePrerelease" // and therefore not match pre-releases like normal versions @@ -859,7 +861,7 @@ test.serial("AbstractResolver: Static resolveVersion resolves '2.x.x-alpha'", as t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); }); @@ -874,18 +876,18 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'next' using tags" "next": "2.0.0" }); - const version = await MyResolver.resolveVersion("next", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("next", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "2.0.0", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllTags should be called with expected arguments"); }); @@ -900,18 +902,18 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'next' to a pre-re "next": "2.0.0-SNAPSHOT" }); - const version = await MyResolver.resolveVersion("next", "/ui5DataDir", {cwd: "/cwd"}); + const version = await MyResolver.resolveVersion("next", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version, "2.0.0-SNAPSHOT", "Resolved version should be correct"); t.is(fetchAllVersionsStub.callCount, 1, "fetchAllVersions should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllVersions should be called with expected arguments"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllTags should be called with expected arguments"); }); @@ -927,12 +929,12 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest' using tag // Resolver does not support tags (resolves with "null" instead of an object) // 'latest' should resolve to the highest version available - const version1 = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); + const version1 = await MyResolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version1, "2.0.0", "Resolved version should be correct"); t.is(fetchAllTagsStub.callCount, 1, "fetchAllTagsStub should be called once"); t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllTags should be called with expected arguments"); @@ -941,12 +943,12 @@ test.serial("AbstractResolver: Static resolveVersion resolves 'latest' using tag fetchAllTagsStub.resolves({ "latest": "1.76.0" }); - const version2 = await MyResolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"}); + const version2 = await MyResolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"}); t.is(version2, "1.76.0", "Resolved version should be correct"); t.is(fetchAllTagsStub.callCount, 2, "fetchAllTagsStub should be called twice"); t.deepEqual(fetchAllVersionsStub.getCall(1).args, [ - "/ui5DataDir", + UI5_DATA_DIR, {cwd: "/cwd"} ], "fetchAllTags should be called with expected arguments"); }); @@ -956,7 +958,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for empty stri sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(MyResolver.resolveVersion("", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "" is incorrect or not supported`); }); @@ -966,7 +968,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for invalid ta sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("%20", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(MyResolver.resolveVersion("%20", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "%20" is incorrect or not supported`); }); @@ -979,7 +981,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for non-existi .resolves({"latest": "1.76.0"}); const error = await t.throwsAsync( - MyResolver.resolveVersion("this-tag-does-not-exist", "/ui5DataDir", {cwd: "/cwd"}) + MyResolver.resolveVersion("this-tag-does-not-exist", UI5_DATA_DIR, {cwd: "/cwd"}) ); t.is(error.message, `Could not resolve framework version via tag 'this-tag-does-not-exist'. ` + @@ -992,7 +994,7 @@ test.serial("AbstractResolver: Static resolveVersion throws error for version no sinon.stub(MyResolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(MyResolver.resolveVersion("1.74.0", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(MyResolver.resolveVersion("1.74.0", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.74.0. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1008,7 +1010,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns(["1.75.0", "1.75.1", "1.76.0"]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.50.0", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.50.0", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.50.0. Note that OpenUI5 framework libraries can only be ` + @@ -1025,7 +1027,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns(["1.76.0", "1.76.1", "1.90.0"]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.75.0", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.75.0", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.75.0. Note that SAPUI5 framework libraries can only be ` + @@ -1042,7 +1044,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version latest. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1058,7 +1060,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("latest", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("latest", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version latest. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1074,7 +1076,7 @@ test.serial( sinon.stub(Openui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.99", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Openui5Resolver.resolveVersion("1.99", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.99. ` + `Make sure the version is valid and available in the configured registry.`); @@ -1090,7 +1092,7 @@ test.serial( sinon.stub(Sapui5Resolver, "fetchAllVersions") .returns([]); - const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.99", "/ui5DataDir", {cwd: "/cwd"})); + const error = await t.throwsAsync(Sapui5Resolver.resolveVersion("1.99", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Could not resolve framework version 1.99. ` + `Make sure the version is valid and available in the configured registry.`); From 84d969d14a71dd1805627e859ae798d70ffec8c0 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 24 Jul 2026 13:28:10 +0300 Subject: [PATCH 18/20] refactor: Adjust ui5DataDir argument --- packages/project/lib/build/ProjectBuilder.js | 11 +++--- .../project/lib/build/helpers/BuildContext.js | 7 ++-- .../project/lib/graph/helpers/ui5Framework.js | 6 ---- .../project/test/lib/build/ProjectBuilder.js | 36 ++++++++++--------- .../test/lib/build/helpers/BuildContext.js | 30 ++++++++-------- 5 files changed, 43 insertions(+), 47 deletions(-) diff --git a/packages/project/lib/build/ProjectBuilder.js b/packages/project/lib/build/ProjectBuilder.js index b70eabb62c3..18a4196afad 100644 --- a/packages/project/lib/build/ProjectBuilder.js +++ b/packages/project/lib/build/ProjectBuilder.js @@ -105,10 +105,8 @@ class ProjectBuilder { * @param {@ui5/project/graph/ProjectGraph} parameters.graph Project graph * @param {@ui5/project/build/ProjectBuilder~BuildConfiguration} [parameters.buildConfig] Build configuration * @param {@ui5/builder/tasks/taskRepository} parameters.taskRepository Task Repository module to use - * @param {string} [parameters.ui5DataDir] - * Explicit UI5 data directory to use for the build cache, overriding the - * UI5_DATA_DIR environment variable, the UI5 configuration file, - * and the default of ~/.ui5. + * @param {string} parameters.ui5DataDir + * Explicit UI5 data directory to use for the build cache. */ constructor({graph, buildConfig, taskRepository, ui5DataDir}) { if (!graph) { @@ -121,9 +119,12 @@ class ProjectBuilder { throw new Error( `Can not build project graph with root node ${graph.getRoot().getName()}: Graph is not sealed`); } + if (!ui5DataDir) { + throw new Error("ProjectBuilder: Missing parameter 'ui5DataDir'"); + } this._graph = graph; - this._buildContext = new BuildContext(graph, taskRepository, buildConfig, ui5DataDir); + this._buildContext = new BuildContext(graph, taskRepository, ui5DataDir, buildConfig); this.#log = new BuildLogger("ProjectBuilder"); } diff --git a/packages/project/lib/build/helpers/BuildContext.js b/packages/project/lib/build/helpers/BuildContext.js index 71f9e95fa39..5b999a92c70 100644 --- a/packages/project/lib/build/helpers/BuildContext.js +++ b/packages/project/lib/build/helpers/BuildContext.js @@ -15,7 +15,7 @@ import Cache from "../cache/Cache.js"; class BuildContext { #cacheManager; - constructor(graph, taskRepository, { // buildConfig + constructor(graph, taskRepository, ui5DataDir, { // buildConfig selfContained = false, cssVariables = false, jsdoc = false, @@ -23,7 +23,7 @@ class BuildContext { outputStyle = OutputStyleEnum.Default, includedTasks = [], excludedTasks = [], cache = Cache.Default, - } = {}, ui5DataDir) { + } = {}) { if (!graph) { throw new Error(`Missing parameter 'graph'`); } @@ -170,9 +170,6 @@ class BuildContext { if (this.#cacheManager) { return this.#cacheManager; } - if (!this._ui5DataDir) { - throw new Error("BuildContext: Missing parameter \"ui5DataDir\" for cache-enabled builds"); - } this.#cacheManager = await CacheManager.create(this._ui5DataDir); return this.#cacheManager; } diff --git a/packages/project/lib/graph/helpers/ui5Framework.js b/packages/project/lib/graph/helpers/ui5Framework.js index 32466500816..2accd6dab71 100644 --- a/packages/project/lib/graph/helpers/ui5Framework.js +++ b/packages/project/lib/graph/helpers/ui5Framework.js @@ -286,8 +286,6 @@ export default { * metadata and package resolution. * @param {@ui5/project/graph/Workspace} [options.workspace] * Optional workspace instance to use for overriding node resolutions - * @throws {Error} If framework libraries are referenced but no options.ui5DataDir - * is provided. * @returns {Promise<@ui5/project/graph/ProjectGraph>} * Promise resolving with the given graph instance to allow method chaining */ @@ -341,10 +339,6 @@ export default { return projectGraph; } - if (!ui5DataDir) { - throw new Error("ui5Framework.enrichProjectGraph: Missing parameter \"ui5DataDir\""); - } - let Resolver; if (version && version.toLowerCase().endsWith("-snapshot")) { Resolver = (await import("../../ui5Framework/Sapui5MavenSnapshotResolver.js")).default; diff --git a/packages/project/test/lib/build/ProjectBuilder.js b/packages/project/test/lib/build/ProjectBuilder.js index fa86a635fa2..a07e7716568 100644 --- a/packages/project/test/lib/build/ProjectBuilder.js +++ b/packages/project/test/lib/build/ProjectBuilder.js @@ -106,7 +106,7 @@ test("Missing graph parameters", (t) => { test("build", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProjectStub = sinon.stub().returns(true); sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); @@ -167,7 +167,7 @@ test("build", async (t) => { test("build: Conflicting dependency parameters", async (t) => { const {graph, taskRepository, ProjectBuilder} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const err = await t.throwsAsync(builder.buildToTarget({ destPath: "dest/path", @@ -185,7 +185,7 @@ test("build: Conflicting dependency parameters", async (t) => { test("build: Too many dependency parameters", async (t) => { const {graph, taskRepository, ProjectBuilder} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const err = await t.throwsAsync(builder.buildToTarget({ includedDependencies: ["dep a"], @@ -199,6 +199,7 @@ test("build: createBuildManifest in conjunction with dependencies", async (t) => const {graph, taskRepository, ProjectBuilder, sinon} = t.context; t.context.getRootTypeStub = sinon.stub().returns("library"); const builder = new ProjectBuilder({graph, taskRepository, + ui5DataDir: "/ui5DataDir", buildConfig: { createBuildManifest: true } @@ -220,7 +221,7 @@ test("build: createBuildManifest in conjunction with dependencies", async (t) => test("build: Failure", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProjectStub = sinon.stub().returns(true); sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); @@ -327,7 +328,7 @@ test.serial("build: Multiple projects", async (t) => { "@ui5/logger/internal/loggers/Build": CreateBuildLoggerMock }); - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProjectStub = sinon.stub().returns(true).onFirstCall().returns(false); sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); @@ -443,7 +444,7 @@ test.serial("_createProjectFilter with dependencyIncludes", async (t) => { "../../../lib/build/helpers/composeProjectList.js": composeProjectListStub }); - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProject = builder._createProjectFilter({ dependencyIncludes: "dependencyIncludes", @@ -474,7 +475,7 @@ test.serial("_createProjectFilter with explicit include/exclude", async (t) => { "../../../lib/build/helpers/composeProjectList.js": composeProjectListStub }); - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProject = builder._createProjectFilter({ explicitIncludes: "explicitIncludes", @@ -502,6 +503,7 @@ test("_writeResults", async (t) => { const {graph, taskRepository} = t.context; const builder = new ProjectBuilder({ graph, taskRepository, + ui5DataDir: "/ui5DataDir", buildConfig: { createBuildManifest: false, otherBuildConfig: "yes" @@ -583,6 +585,7 @@ test.serial("_writeResults: Create build manifest", async (t) => { const builder = new ProjectBuilder({ graph, taskRepository, + ui5DataDir: "/ui5DataDir", buildConfig: { createBuildManifest: true, otherBuildConfig: "yes" @@ -685,6 +688,7 @@ test.serial("_writeResults: Flat build output", async (t) => { const builder = new ProjectBuilder({ graph, taskRepository, + ui5DataDir: "/ui5DataDir", buildConfig: { outputStyle: OutputStyleEnum.Flat, otherBuildConfig: "yes" @@ -753,7 +757,7 @@ test.serial("_writeResults: Flat build output", async (t) => { test("_executeCleanupTasks", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const executeCleanupTasksStub = sinon.stub(builder._buildContext, "executeCleanupTasks"); await builder._executeCleanupTasks(); @@ -782,8 +786,8 @@ test("instantiate new logger for every ProjectBuilder", async (t) => { "@ui5/logger/internal/loggers/Build": createBuildLoggerMockSpy }); - new ProjectBuilder({graph, taskRepository}); - new ProjectBuilder({graph, taskRepository}); + new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); + new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); t.is(createBuildLoggerMockSpy.callCount, 2, "BuildLogger is instantiated for every ProjectBuilder instance"); }); @@ -798,7 +802,7 @@ test("_registerCleanupSigHooks/_deregisterCleanupSigHooks", (t) => { const listenersBefore = getProcessListenerCount(); const {graph, taskRepository, ProjectBuilder} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const signals = builder._registerCleanupSigHooks(); @@ -816,7 +820,7 @@ test("_registerCleanupSigHooks/_deregisterCleanupSigHooks", (t) => { test("_getElapsedTime", (t) => { const {graph, taskRepository, ProjectBuilder} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const res = builder._getElapsedTime(process.hrtime()); t.truthy(res, "Returned a value"); @@ -825,7 +829,7 @@ test("_getElapsedTime", (t) => { test("validateCaches: initializes contexts via getRequiredProjectContexts and invokes callback per project", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); // Background validation must be able to initialize contexts for never-built projects // itself, otherwise the post-(initial-)build pass over dependencies skipped by the @@ -871,7 +875,7 @@ test("validateCaches: initializes contexts via getRequiredProjectContexts and in test("validateCaches: aborts on signal", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const controller = new AbortController(); const validateCacheB = sinon.stub().callsFake(() => { @@ -906,7 +910,7 @@ test("validateCaches: aborts on signal", async (t) => { test("validateCaches: rejects re-entry while a build is running", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); // Simulate an in-flight build by manually flipping the private flag via a build call. // Easiest: kick off two validateCaches in parallel and assert the second rejects. @@ -935,7 +939,7 @@ test("validateCaches: rejects re-entry while a build is running", async (t) => { test("validateCaches: willValidate fires before each project's validateCache call", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const events = []; const ctxB = { diff --git a/packages/project/test/lib/build/helpers/BuildContext.js b/packages/project/test/lib/build/helpers/BuildContext.js index ea1d04c6e6e..68b2fa6386a 100644 --- a/packages/project/test/lib/build/helpers/BuildContext.js +++ b/packages/project/test/lib/build/helpers/BuildContext.js @@ -100,7 +100,7 @@ test("getBuildConfig: Custom values", (t) => { getType: () => "library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { selfContained: true, outputStyle: OutputStyleEnum.Namespace, cssVariables: true, @@ -133,7 +133,7 @@ test("createBuildManifest not supported for type application", (t) => { getType: () => "application" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true }); }); @@ -152,7 +152,7 @@ test("createBuildManifest not supported for type module", (t) => { getType: () => "module" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true }); }); @@ -171,7 +171,7 @@ test("createBuildManifest not supported for self-contained build", (t) => { getType: () => "library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true, selfContained: true }); @@ -191,7 +191,7 @@ test("createBuildManifest supported for css-variables build", (t) => { getType: () => "library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true, cssVariables: true }); @@ -208,7 +208,7 @@ test("createBuildManifest supported for jsdoc build", (t) => { getType: () => "library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true, jsdoc: true }); @@ -225,7 +225,7 @@ test("outputStyle='Namespace' supported for type application", (t) => { getType: () => "application" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { outputStyle: OutputStyleEnum.Namespace }); }); @@ -241,7 +241,7 @@ test("outputStyle='Flat' not supported for type theme-library", (t) => { getType: () => "theme-library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { outputStyle: OutputStyleEnum.Flat }); }); @@ -261,7 +261,7 @@ test("outputStyle='Flat' not supported for type module", (t) => { getType: () => "module" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { outputStyle: OutputStyleEnum.Flat }); }); @@ -281,7 +281,7 @@ test("outputStyle='Flat' not supported for createBuildManifest build", (t) => { getType: () => "library" }; } - }, "taskRepository", { + }, "taskRepository", undefined, { createBuildManifest: true, outputStyle: OutputStyleEnum.Flat }); @@ -297,7 +297,7 @@ test("getOption", (t) => { const graph = { getRoot: () => ({getType: () => "library"}), }; - const buildContext = new BuildContext(graph, "taskRepository", { + const buildContext = new BuildContext(graph, "taskRepository", undefined, { cssVariables: "value", }); @@ -315,7 +315,7 @@ test("getProjectContext", async (t) => { .returns({getType: () => "library", getRootPath: () => ""}); const graph = {getRoot: rootProjectStub, getProject: () => "project"}; - const buildContext = new BuildContext(graph, "taskRepository", undefined, "/ui5DataDir"); + const buildContext = new BuildContext(graph, "taskRepository", "/ui5DataDir"); const projectBuildContext = await buildContext.getProjectContext("project"); t.is(t.context.ProjectBuildContextCreateStub.callCount, 1); @@ -330,7 +330,7 @@ test("getCacheManager: Returns null when cache mode is 'Off'", async (t) => { const graph = { getRoot: () => ({getType: () => "library", getRootPath: () => ""}), }; - const buildContext = new BuildContext(graph, "taskRepository", { + const buildContext = new BuildContext(graph, "taskRepository", undefined, { cache: "Off" }); @@ -350,7 +350,7 @@ test("getCacheManager: Creates and caches CacheManager for default cache mode", const graph = { getRoot: () => ({getType: () => "library", getRootPath: () => "/some/path"}), }; - const buildContext = new BuildContext(graph, "taskRepository", undefined, "/ui5DataDir"); + const buildContext = new BuildContext(graph, "taskRepository", "/ui5DataDir"); const cacheManager1 = await buildContext.getCacheManager(); t.is(cacheManager1, cacheManagerInstance, "Returned CacheManager instance"); @@ -369,7 +369,7 @@ test("closeCacheManager: No-op when cache mode is 'Off'", async (t) => { const graph = { getRoot: () => ({getType: () => "library", getRootPath: () => ""}), }; - const buildContext = new BuildContext(graph, "taskRepository", { + const buildContext = new BuildContext(graph, "taskRepository", undefined, { cache: "Off" }); From 503f059ef827a6e53b95d40e4fa0aba0ab5d8472 Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 24 Jul 2026 15:56:33 +0300 Subject: [PATCH 19/20] docs: Provide missing JSDoc --- packages/project/lib/graph/ProjectGraph.js | 26 +++++++++++++++++++ .../lib/ui5Framework/AbstractResolver.js | 12 +++++++++ 2 files changed, 38 insertions(+) diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js index ed2af07bf23..fd2de6723db 100644 --- a/packages/project/lib/graph/ProjectGraph.js +++ b/packages/project/lib/graph/ProjectGraph.js @@ -761,6 +761,32 @@ class ProjectGraph { }); } + /** + * Starts a build server for the graph. + * + * @public + * @param {object} parameters Serve parameters + * @param {boolean} [parameters.initialBuildRootProject=false] + * Whether to build the root project in the initial build. + * @param {Array.} [parameters.initialBuildIncludedDependencies=[]] + * List of names of dependencies to include in the initial build. + * @param {Array.} [parameters.initialBuildExcludedDependencies=[]] + * List of names of dependencies to exclude from the initial build. + * @param {boolean} [parameters.selfContained=false] Flag to activate self contained build + * @param {boolean} [parameters.cssVariables=false] Flag to activate CSS variables generation + * @param {boolean} [parameters.jsdoc=false] Flag to activate JSDoc build + * @param {boolean} [parameters.createBuildManifest=false] + * Whether to create a build manifest file for the root project. + * This is currently only supported for projects of type 'library' and 'theme-library' + * @param {Array.} [parameters.includedTasks=[]] List of tasks to be included + * @param {Array.} [parameters.excludedTasks=[]] List of tasks to be excluded. + * @param {module:@ui5/project/build/cache/Cache} [parameters.cache=Default] + * Cache mode to use for building UI5 projects + * @param {string} parameters.ui5DataDir + * Resolved UI5 data directory to use for the build cache. + * @returns {Promise} Promise resolving to a build server instance + */ + async serve({ initialBuildRootProject = false, initialBuildIncludedDependencies = [], initialBuildExcludedDependencies = [], diff --git a/packages/project/lib/ui5Framework/AbstractResolver.js b/packages/project/lib/ui5Framework/AbstractResolver.js index 750242ff0ce..729335d561e 100644 --- a/packages/project/lib/ui5Framework/AbstractResolver.js +++ b/packages/project/lib/ui5Framework/AbstractResolver.js @@ -210,6 +210,18 @@ class AbstractResolver { }; } + /** + * Resolves a framework version specifier to an exact available framework version. + * + * @public + * @static + * @param {string} version Framework version or semver range to resolve + * @param {string} ui5DataDir Resolved UI5 home directory location used for framework metadata and packages + * @param {object} [options] Additional options + * @param {string} [options.cwd=process.cwd()] Current working directory + * @returns {Promise} Promise resolving to the resolved framework version + */ + static async resolveVersion(version, ui5DataDir, {cwd} = {}) { // Don't allow nullish values // An empty string is a valid semver range that converts to "*", which should not be supported From 52675bd4da728a3d78814ca0accdecf563ce62cd Mon Sep 17 00:00:00 2001 From: d3xter666 Date: Fri, 24 Jul 2026 16:21:32 +0300 Subject: [PATCH 20/20] test: Fix failing tests --- packages/project/test/lib/build/ProjectBuilder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/project/test/lib/build/ProjectBuilder.js b/packages/project/test/lib/build/ProjectBuilder.js index a07e7716568..6389e043afd 100644 --- a/packages/project/test/lib/build/ProjectBuilder.js +++ b/packages/project/test/lib/build/ProjectBuilder.js @@ -266,7 +266,7 @@ test("build: Failure", async (t) => { test("build: Abort does not reset the project", async (t) => { const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - const builder = new ProjectBuilder({graph, taskRepository}); + const builder = new ProjectBuilder({graph, taskRepository, ui5DataDir: "/ui5DataDir"}); const filterProjectStub = sinon.stub().returns(true); sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub);