diff --git a/internal/documentation/docs/updates/migrate-v5.md b/internal/documentation/docs/updates/migrate-v5.md index c9db7b58dac..55ca44fc58c 100644 --- a/internal/documentation/docs/updates/migrate-v5.md +++ b/internal/documentation/docs/updates/migrate-v5.md @@ -27,6 +27,10 @@ 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`** + +- **@ui5/project: UI5 framework resolver static APIs now require explicit `ui5DataDir`** + ## Node.js and npm Version Support @@ -107,6 +111,71 @@ 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`. + +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. + +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; +} +``` +::: + +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 799c8a35253..38527abbba5 100644 --- a/packages/cli/lib/framework/utils.js +++ b/packages/cli/lib/framework/utils.js @@ -1,16 +1,22 @@ -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) { + 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 }); @@ -37,26 +43,14 @@ export async function createFrameworkResolverInstance({frameworkName, frameworkV return new Resolver({ cwd, version: frameworkVersion, - ui5DataDir: await utils.getUi5DataDir({cwd}) + ui5DataDir: await resolveUi5DataDir({projectRootPath: cwd}) }); } export async function frameworkResolverResolveVersion({frameworkName, frameworkVersion}, {cwd}) { const Resolver = await utils.getFrameworkResolver(frameworkName, frameworkVersion); - return Resolver.resolveVersion(frameworkVersion, { - cwd, - ui5DataDir: await utils.getUi5DataDir({cwd}) - }); -} - -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 ui5DataDir = await resolveUi5DataDir({projectRootPath: cwd}); + return Resolver.resolveVersion(frameworkVersion, ui5DataDir, {cwd}); } const utils = { @@ -64,7 +58,6 @@ const utils = { getFrameworkResolver, createFrameworkResolverInstance, frameworkResolverResolveVersion, - getUi5DataDir }; let _utils; // For mocking of functions in unit tests and testing internal functions 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 0ac6f4aaf37..5f0d0856194 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; }); @@ -68,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 }]); @@ -90,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 }]); @@ -142,13 +142,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 +163,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 +172,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 +198,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 +214,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", @@ -244,54 +234,7 @@ test.serial("frameworkResolverResolveVersion", async (t) => { t.is(resolveVersionStub.callCount, 1); t.deepEqual(resolveVersionStub.getCall(0).args, [ "latest", - { - cwd: "my-project-path", - ui5DataDir: undefined - } + "my-default-ui5-data-dir", + {cwd: "my-project-path"} ]); }); - -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/ProjectBuilder.js b/packages/project/lib/build/ProjectBuilder.js index 5966472866a..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/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js index e1e37a6f521..dec1dcc5676 100644 --- a/packages/project/lib/build/cache/CacheManager.js +++ b/packages/project/lib/build/cache/CacheManager.js @@ -1,6 +1,4 @@ import path from "node:path"; -import os from "node:os"; -import Configuration from "../../config/Configuration.js"; import {getLogger} from "@ui5/logger"; import BuildCacheStorage from "./BuildCacheStorage.js"; @@ -58,33 +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 (resolved relative to cwd) - * 3. ui5DataDir from UI5 configuration file - * 4. 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. + * @param {string} ui5DataDir Resolved UI5 data directory. * @returns {Promise} Singleton CacheManager instance for the cache directory */ - static async create(cwd, {ui5DataDir} = {}) { + static async create(ui5DataDir) { if (!ui5DataDir) { - // ENV var should take precedence over the dataDir from the configuration. - ui5DataDir = process.env.UI5_DATA_DIR; - if (!ui5DataDir) { - const config = await Configuration.fromFile(); - ui5DataDir = config.getUi5DataDir(); - } - } - if (ui5DataDir) { - ui5DataDir = path.resolve(cwd, ui5DataDir); - } else { - ui5DataDir = path.join(os.homedir(), ".ui5"); + 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 59821282710..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,7 @@ 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(this._ui5DataDir); return this.#cacheManager; } diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js index 653e6f7901b..fd2de6723db 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( @@ -760,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 = [], @@ -768,6 +795,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 660cc78427e..2accd6dab71 100644 --- a/packages/project/lib/graph/helpers/ui5Framework.js +++ b/packages/project/lib/graph/helpers/ui5Framework.js @@ -2,8 +2,6 @@ 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"; class ProjectProcessor { constructor({libraryMetadata, graph, workspace}) { @@ -284,13 +282,15 @@ 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 * @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(); @@ -348,21 +348,8 @@ 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); - } - 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 266d4bcd1a6..729335d561e 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 @@ -208,21 +210,38 @@ class AbstractResolver { }; } - static async resolveVersion(version, {ui5DataDir, cwd} = {}) { + /** + * 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 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. @@ -249,7 +268,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) { @@ -271,7 +290,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) @@ -307,10 +326,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 a6a9c4fc02a..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"; @@ -18,8 +17,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 @@ -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 01c4b843541..310f68a8882 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 */ @@ -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 300020dce25..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"; @@ -21,8 +20,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 +74,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 { @@ -105,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/lib/utils/dataDir.js b/packages/project/lib/utils/dataDir.js new file mode 100644 index 00000000000..fd0ae59a6b8 --- /dev/null +++ b/packages/project/lib/utils/dataDir.js @@ -0,0 +1,42 @@ +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: + *
    + *
  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. + * + * @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 + * 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 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/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/ProjectBuilder.js b/packages/project/test/lib/build/ProjectBuilder.js index fa86a635fa2..6389e043afd 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); @@ -265,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); @@ -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 16832b98679..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"); + 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"); + 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" }); 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 93096d50109..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(); @@ -135,7 +136,7 @@ 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, }); t.context.projectGraphBuilder = await esmock.p("../../../../lib/graph/projectGraphBuilder.js", { @@ -493,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(); @@ -704,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}); }); } @@ -782,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"); }); @@ -810,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"); @@ -838,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, [ @@ -915,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 b134ac187ac..6e200d92635 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 @@ -54,7 +62,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 +74,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; @@ -122,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"); @@ -131,7 +140,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"); @@ -190,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, [ @@ -238,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"); @@ -331,20 +340,21 @@ 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", { - cwd: dependencyTree.path, - ui5DataDir: undefined, - }]); + 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, [{ 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"); }); @@ -393,13 +403,14 @@ 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", { - cwd: dependencyTree.path, - ui5DataDir: undefined, - }]); + 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"); @@ -407,7 +418,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"); }); @@ -456,13 +467,14 @@ 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", { - cwd: dependencyTree.path, - ui5DataDir: undefined, - }]); + 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"); @@ -470,7 +482,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"); }); @@ -497,9 +509,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"); @@ -533,7 +545,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"); }); @@ -621,7 +633,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"); @@ -630,7 +642,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"); }); @@ -720,14 +732,15 @@ 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); - t.deepEqual(Sapui5ResolverResolveVersionStub.getCall(0).args, ["3.4.5", { - cwd: dependencyTree.path, - ui5DataDir: undefined, - }]); + 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"); @@ -735,7 +748,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"); }); @@ -776,7 +789,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"); }); @@ -810,7 +823,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); }); @@ -843,7 +856,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"); }); @@ -865,7 +878,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"); }); @@ -940,7 +953,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).` @@ -993,14 +1006,14 @@ 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, [{ 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); @@ -1052,13 +1065,13 @@ 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, [{ 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"); @@ -1109,7 +1122,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, [{ @@ -1165,7 +1180,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, [{ @@ -1221,7 +1238,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/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/ui5framework/AbstractResolver.js b/packages/project/test/lib/ui5framework/AbstractResolver.js index 7156df0a545..6ccd0fd1fb2 100644 --- a/packages/project/test/lib/ui5framework/AbstractResolver.js +++ b/packages/project/test/lib/ui5framework/AbstractResolver.js @@ -1,18 +1,21 @@ import test from "ava"; import sinon from "sinon"; import path from "node:path"; -import os from "node:os"; import esmock from "esmock"; +const UI5_DATA_DIR = path.resolve("/ui5DataDir"); + 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: UI5_DATA_DIR, + ...options + }); + } + static async fetchAllVersions() {} } @@ -118,24 +121,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) => { @@ -560,18 +558,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR'", async (t) => { @@ -579,18 +574,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR-SNAPSHOT'", async (t) => { @@ -598,18 +590,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR'", async (t) => { @@ -617,18 +606,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR-SNAPSHOT'", async (t) => { @@ -636,18 +622,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH'", async (t) => { @@ -655,18 +638,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'MAJOR.MINOR.PATCH-SNAPSHOT'", async (t) => { @@ -674,18 +654,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion does not include prereleases for 'latest' version", async (t) => { @@ -693,18 +670,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'latest-snapshot'", async (t) => { @@ -712,18 +686,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion includes non-prereleases for 'latest-snapshot'", async (t) => { @@ -733,42 +704,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {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", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "lts" is incorrect or not supported`); @@ -780,18 +737,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '1.75.x'", async (t) => { @@ -799,18 +753,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '^1.75.0'", async (t) => { @@ -818,18 +769,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '~1.75.0'", async (t) => { @@ -837,18 +785,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '> 1.75.0 < 1.75.3'", async (t) => { @@ -856,18 +801,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'x.x.x-SNAPSHOT'", async (t) => { @@ -875,20 +817,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", UI5_DATA_DIR, {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, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '^2.0.0-SNAPSHOT'", async (t) => { @@ -896,20 +835,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", UI5_DATA_DIR, {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, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves '2.x.x-alpha'", async (t) => { @@ -917,20 +853,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", UI5_DATA_DIR, {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, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllVersions should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'next' using tags", async (t) => { @@ -943,23 +876,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion resolves 'next' to a pre-release using tags", async (t) => { @@ -972,23 +902,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllVersions should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); @@ -1002,34 +929,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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(0).args, [ + UI5_DATA_DIR, + {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", 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, [{ - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - }], "fetchAllTags should be called with expected arguments"); + t.deepEqual(fetchAllVersionsStub.getCall(1).args, [ + UI5_DATA_DIR, + {cwd: "/cwd"} + ], "fetchAllTags should be called with expected arguments"); }); test.serial("AbstractResolver: Static resolveVersion throws error for empty string", async (t) => { @@ -1037,10 +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("", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + const error = await t.throwsAsync(MyResolver.resolveVersion("", UI5_DATA_DIR, {cwd: "/cwd"})); t.is(error.message, `Framework version specifier "" is incorrect or not supported`); }); @@ -1050,10 +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", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + 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`); }); @@ -1065,10 +980,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", UI5_DATA_DIR, {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.` @@ -1080,10 +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", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + 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.`); @@ -1099,10 +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", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + 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 ` + @@ -1119,10 +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", { - cwd: "/cwd", - ui5DataDir: "/ui5DataDir" - })); + 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 ` + @@ -1139,10 +1044,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", 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.`); @@ -1158,10 +1060,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", 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.`); @@ -1177,10 +1076,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", 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.`); @@ -1196,10 +1092,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", 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.`); 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 34d756987da..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(); @@ -44,7 +43,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 +104,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"); @@ -149,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"); @@ -163,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) => { @@ -183,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"); @@ -192,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) => { @@ -204,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"); @@ -213,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 ed9a4de6cd9..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(); @@ -63,7 +62,8 @@ test.serial( const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.75.0", + ui5DataDir: "/ui5DataDir" }); const expectedMetadata = { @@ -114,7 +114,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 +133,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 +188,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 +243,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 +298,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 +352,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({}); @@ -372,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"); @@ -395,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 63288d356ed..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) => { @@ -37,7 +36,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 +88,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"); @@ -135,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"); @@ -149,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) => { @@ -169,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"); @@ -177,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) => { @@ -189,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"); @@ -198,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( @@ -207,7 +214,8 @@ test.serial( const resolver = new Sapui5Resolver({ cwd: "/test-project/", - version: "1.77.7" + version: "1.77.7", + ui5DataDir: "/ui5DataDir" }); const openui5LibraryMetadata = { 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")); +}); 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);