diff --git a/packages/cli/lib/framework/utils.js b/packages/cli/lib/framework/utils.js index 799c8a35253..d5e272eda05 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({projectRootPath: cwd}) }); } @@ -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({projectRootPath: cwd}) }); } -async function getUi5DataDir({cwd}) { - // ENV var should take precedence over the dataDir from the configuration. - let ui5DataDir = process.env.UI5_DATA_DIR; - if (!ui5DataDir) { - const config = await Configuration.fromFile(); - ui5DataDir = config.getUi5DataDir(); - } - return ui5DataDir ? path.resolve(cwd, ui5DataDir) : undefined; -} - const utils = { getRootProjectConfiguration, getFrameworkResolver, createFrameworkResolverInstance, frameworkResolverResolveVersion, - getUi5DataDir }; let _utils; // For mocking of functions in unit tests and testing internal functions diff --git a/packages/cli/test/lib/framework/utils.js b/packages/cli/test/lib/framework/utils.js index 0ac6f4aaf37..48c7a4ba990 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("my-default-ui5-data-dir"); 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; }); @@ -142,13 +138,13 @@ test.serial("getFrameworkResolver: Invalid framework.name", async (t) => { }); }); -test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => { +test.serial("createFrameworkResolverInstance: Without explicit ui5DataDir (uses resolved default)", async (t) => { const {createFrameworkResolverInstance} = t.context.utils; - const {sinon} = t.context; + const {sinon, resolveUi5DataDirStub} = t.context; const ResolverStub = sinon.stub().returns({}); sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub); - sinon.stub(t.context._utils, "getUi5DataDir").resolves(undefined); + resolveUi5DataDirStub.resolves("my-default-ui5-data-dir"); const result = await createFrameworkResolverInstance({ frameworkName: "", @@ -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); @@ -177,18 +168,18 @@ test.serial("createFrameworkResolverInstance: Without ui5DataDir", async (t) => { cwd: "my-project-path", version: "", - ui5DataDir: undefined + ui5DataDir: "my-default-ui5-data-dir" } ]); }); test.serial("createFrameworkResolverInstance: With ui5DataDir", async (t) => { const {createFrameworkResolverInstance} = t.context.utils; - const {sinon} = t.context; + const {sinon, resolveUi5DataDirStub} = t.context; const ResolverStub = sinon.stub().returns({}); sinon.stub(t.context._utils, "getFrameworkResolver").resolves(ResolverStub); - sinon.stub(t.context._utils, "getUi5DataDir").resolves("my-ui5-data-dir"); + resolveUi5DataDirStub.resolves("my-ui5-data-dir"); const result = await createFrameworkResolverInstance({ frameworkName: "", @@ -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("my-default-ui5-data-dir"); const result = await frameworkResolverResolveVersion({ frameworkName: "SAPUI5", @@ -246,52 +232,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { "latest", { cwd: "my-project-path", - ui5DataDir: undefined + ui5DataDir: "my-default-ui5-data-dir" } ]); }); - -test.serial("getUi5DataDir: no value defined", async (t) => { - const {ConfigurationGetUi5DataDirStub} = t.context; - const {getUi5DataDir} = t.context._utils; - - const result = await getUi5DataDir({ - cwd: path.resolve("foo") - }); - - t.is(result, undefined); - - t.is(ConfigurationGetUi5DataDirStub.callCount, 1); -}); - -test.serial("getUi5DataDir: from environment variable", async (t) => { - const {ConfigurationGetUi5DataDirStub} = t.context; - const {getUi5DataDir} = t.context._utils; - - // Environment variable must be preferred over configuration value - ConfigurationGetUi5DataDirStub.returns(".ui5-data-dir-from-configuration"); - process.env.UI5_DATA_DIR = ".ui5-data-dir-from-env-variable"; - - const result = await getUi5DataDir({ - cwd: path.resolve("foo") - }); - - t.is(result, path.join(path.resolve("foo"), ".ui5-data-dir-from-env-variable")); - - t.is(ConfigurationGetUi5DataDirStub.callCount, 0); -}); - -test.serial("getUi5DataDir: from Configuration", async (t) => { - const {ConfigurationGetUi5DataDirStub} = t.context; - const {getUi5DataDir} = t.context._utils; - - ConfigurationGetUi5DataDirStub.returns(".ui5-data-dir-from-configuration"); - - const result = await getUi5DataDir({ - cwd: path.resolve("foo") - }); - - t.is(result, path.join(path.resolve("foo"), ".ui5-data-dir-from-configuration")); - - t.is(ConfigurationGetUi5DataDirStub.callCount, 1); -}); diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index e1e37a6f521..9a4bbdd324d 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"; @@ -60,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 @@ -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..06921dc5b23 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,9 @@ export default { Resolver = (await import("../../ui5Framework/Sapui5Resolver.js")).default; } - // ENV var should take precedence over the dataDir from the configuration. - let ui5DataDir = process.env.UI5_DATA_DIR; - if (!ui5DataDir) { - const config = await Configuration.fromFile(); - ui5DataDir = config.getUi5DataDir(); - } - if (ui5DataDir) { - ui5DataDir = path.resolve(cwd, ui5DataDir); - } + // Resolve the UI5 data directory using the shared utility. + // Returns ~/.ui5 by default when no env var or configuration is set. + const ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); if (options.versionOverride) { version = await Resolver.resolveVersion(options.versionOverride, { diff --git a/packages/project/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js new file mode 100644 index 00000000000..1c20725a038 --- /dev/null +++ b/packages/project/lib/utils/dataDir.js @@ -0,0 +1,34 @@ +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. + *
+ * + * 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({projectRootPath} = {}) { + let ui5DataDir = process.env.UI5_DATA_DIR; + if (!ui5DataDir) { + const config = await Configuration.fromFile(); + ui5DataDir = config.getUi5DataDir(); + } + if (ui5DataDir) { + return path.resolve(projectRootPath ?? process.cwd(), ui5DataDir); + } + return path.resolve(os.homedir(), ".ui5"); +} diff --git a/packages/project/package.json b/packages/project/package.json index 313bc5db619..394b6a269aa 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/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..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(undefined); + t.context.getUi5DataDirStub = sinon.stub().returns(path.resolve("fake-ui5-data-dir")); t.context.ConfigurationStub = { fromFile: sinon.stub().resolves({ @@ -66,6 +66,7 @@ test.beforeEach(async (t) => { "@ui5/logger": ui5Logger, "../../../../lib/ui5Framework/Sapui5Resolver.js": t.context.Sapui5ResolverStub, "../../../../lib/ui5Framework/Sapui5MavenSnapshotResolver.js": t.context.Sapui5MavenSnapshotResolverStub, + }, { "../../../../lib/config/Configuration.js": t.context.ConfigurationStub, }); t.context.utils = t.context.ui5Framework._utils; @@ -131,7 +132,7 @@ test.serial("enrichProjectGraph", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); @@ -336,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: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -344,7 +345,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -398,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: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -407,7 +408,7 @@ test.serial("enrichProjectGraph: With versionOverride containing snapshot versio snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -461,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: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5MavenSnapshotResolverStub.callCount, 1, @@ -470,7 +471,7 @@ test.serial("enrichProjectGraph: With versionOverride containing latest-snapshot snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9-SNAPSHOT", - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -630,7 +631,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an snapshotCache: undefined, cwd: dependencyTree.path, version: "1.2.3", - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -726,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: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), }]); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); @@ -735,7 +736,7 @@ test.serial("enrichProjectGraph should resolve framework project " + snapshotCache: undefined, cwd: dependencyTree.path, version: "1.99.9", - ui5DataDir: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), providedLibraryMetadata: undefined }], "Sapui5Resolver#constructor should be called with expected args"); }); @@ -1000,7 +1001,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works snapshotCache: undefined, cwd: dependencyTree.path, version: "1.111.1", - ui5DataDir: undefined, + 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); @@ -1058,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: undefined, + ui5DataDir: path.resolve("fake-ui5-data-dir"), 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..10472adbf86 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) @@ -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 new file mode 100644 index 00000000000..6ecc3324d9c --- /dev/null +++ b/packages/project/test/lib/utils/dataDir.js @@ -0,0 +1,130 @@ +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.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) => { + const {resolveUi5DataDir} = t.context; + process.env.UI5_DATA_DIR = path.resolve("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: absolute UI5_DATA_DIR ignores projectRootPath", async (t) => { + const {resolveUi5DataDir} = t.context; + 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: path.resolve("my", "project")}); + t.is(result, path.join(path.resolve("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: returns value from Configuration (absolute)", async (t) => { + const {resolveUi5DataDir} = t.context; + t.context.configGetUi5DataDirStub.returns(path.resolve("config", "data", "dir")); + const result = await resolveUi5DataDir(); + 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(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: path.resolve("my", "project")}); + t.is(result, path.join(path.resolve("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: env var takes precedence over Configuration", async (t) => { + const {resolveUi5DataDir} = t.context; + 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("custom", "data", "dir")); +}); + +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(); + t.is(result, path.resolve(process.cwd(), "relative/data")); +});