diff --git a/lib/graph/graph.js b/lib/graph/graph.js index 3cfe3a843..e3a5140ac 100644 --- a/lib/graph/graph.js +++ b/lib/graph/graph.js @@ -31,6 +31,8 @@ const log = getLogger("generateProjectGraph"); * Whether framework dependencies should be added to the graph * @param {string} [options.workspaceName] * Name of the workspace configuration that should be used if any is provided + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode] + * Cache mode to use when consuming SNAPSHOT versions of a framework * @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] @@ -40,7 +42,7 @@ const log = getLogger("generateProjectGraph"); */ export async function graphFromPackageDependencies({ cwd, rootConfiguration, rootConfigPath, - versionOverride, resolveFrameworkDependencies = true, + versionOverride, cacheMode, resolveFrameworkDependencies = true, workspaceName /* TODO 4.0: default workspaceName to "default" ? */, workspaceConfiguration, workspaceConfigPath = "ui5-workspace.yaml" }) { @@ -71,7 +73,7 @@ export async function graphFromPackageDependencies({ const projectGraph = await projectGraphBuilder(provider, workspace); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, workspace}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode, workspace}); } return projectGraph; @@ -93,6 +95,8 @@ export async function graphFromPackageDependencies({ * Configuration file to use for the root module instead the default ui5.yaml. Either a path relative to * cwd or an absolute path. In both case, platform-specific path segment separators must be used. * @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode] + * Cache mode to use when consuming SNAPSHOT versions of a framework * @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 @@ -100,7 +104,7 @@ export async function graphFromPackageDependencies({ export async function graphFromStaticFile({ filePath = "projectDependencies.yaml", cwd, rootConfiguration, rootConfigPath, - versionOverride, resolveFrameworkDependencies = true + versionOverride, cacheMode, resolveFrameworkDependencies = true }) { log.verbose(`Creating project graph using static file...`); const { @@ -121,7 +125,7 @@ export async function graphFromStaticFile({ const projectGraph = await projectGraphBuilder(provider); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode}); } return projectGraph; @@ -143,6 +147,8 @@ export async function graphFromStaticFile({ * Configuration file to use for the root module instead the default ui5.yaml. Either a path relative to * cwd or an absolute path. In both case, platform-specific path segment separators must be used. * @param {string} [options.versionOverride] Framework version to use instead of the one defined in the root project + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode] + * Cache mode to use when consuming SNAPSHOT versions of a framework * @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 @@ -150,7 +156,7 @@ export async function graphFromStaticFile({ export async function graphFromObject({ dependencyTree, cwd, rootConfiguration, rootConfigPath, - versionOverride, resolveFrameworkDependencies = true + versionOverride, cacheMode, resolveFrameworkDependencies = true }) { log.verbose(`Creating project graph using object...`); const { @@ -169,7 +175,7 @@ export async function graphFromObject({ const projectGraph = await projectGraphBuilder(dependencyTreeProvider); if (resolveFrameworkDependencies) { - await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride}); + await ui5Framework.enrichProjectGraph(projectGraph, {versionOverride, cacheMode}); } return projectGraph; diff --git a/lib/graph/helpers/ui5Framework.js b/lib/graph/helpers/ui5Framework.js index 047764859..7c6bb2347 100644 --- a/lib/graph/helpers/ui5Framework.js +++ b/lib/graph/helpers/ui5Framework.js @@ -280,15 +280,15 @@ export default { * @param {object} [options] * @param {string} [options.versionOverride] Framework version to use instead of the root projects framework * version + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode] + * Cache mode to use when consuming SNAPSHOT versions of a framework * @param {@ui5/project/graph/Workspace} [options.workspace] * Optional workspace instance to use for overriding node resolutions - * @param {object} [options.resolverConfig] - * Optional configuration object to use for the resolvers * @returns {Promise<@ui5/project/graph/ProjectGraph>} * Promise resolving with the given graph instance to allow method chaining */ enrichProjectGraph: async function(projectGraph, options = {}) { - const {workspace} = options; + const {workspace, cacheMode} = options; const rootProject = projectGraph.getRoot(); const frameworkName = rootProject.getFrameworkName(); const frameworkVersion = rootProject.getFrameworkVersion(); @@ -369,7 +369,7 @@ export default { cwd: rootProject.getRootPath(), version, providedLibraryMetadata, - ...options.resolverConfig, + cacheMode }); let startTime; diff --git a/lib/ui5Framework/Sapui5MavenSnapshotResolver.js b/lib/ui5Framework/Sapui5MavenSnapshotResolver.js index f276a9727..64eefff79 100644 --- a/lib/ui5Framework/Sapui5MavenSnapshotResolver.js +++ b/lib/ui5Framework/Sapui5MavenSnapshotResolver.js @@ -33,8 +33,8 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { * @param {string} [options.cwd=process.cwd()] Current working directory * @param {string} [options.ui5HomeDir="~/.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.cacheMode="default"] Can be "default" (cache everything, invalidate after 9 hours), - * "off" (do not cache) and "force" (use cache only - no requests) + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [options.cacheMode=Default] + * Cache mode to use */ constructor(options) { super(options); @@ -136,10 +136,10 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { snapshotEndpointUrl = process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT_URL || snapshotEndpointUrl; if (!snapshotEndpointUrl) { - // If we resolve the settings.xml at this point, we'd need to always ask the end - // user for confirmation. In some cases where the resources are already cached, - // this is not necessary and we could skip it as a real request to the repository won't - // be made. + // Here we return a function which returns a promise that resolves with the URL. + // If we would already start resolving the settings.xml at this point, we'd need to always ask the + // end user for confirmation whether the resolved URL should be used. In some cases where the resources + // are already cached, this is actually not necessary and could be skipped return Sapui5MavenSnapshotResolver._resolveSnapshotEndpointUrl; } else { return () => Promise.resolve(snapshotEndpointUrl); diff --git a/lib/ui5Framework/maven/CacheMode.js b/lib/ui5Framework/maven/CacheMode.js new file mode 100644 index 000000000..5757ca3dd --- /dev/null +++ b/lib/ui5Framework/maven/CacheMode.js @@ -0,0 +1,18 @@ + + +/** + * Cache modes for maven consumption + * + * @public + * @readonly + * @enum {string} + * @property {string} Default Cache everything, invalidate after 9 hours + * @property {string} Force Use cache only + * @property {string} Off Do not use the cache + * @module @ui5/project/ui5Framework/maven/CacheMode + */ +export default { + Default: "Default", + Force: "Force", + Off: "Off" +}; diff --git a/lib/ui5Framework/maven/Installer.js b/lib/ui5Framework/maven/Installer.js index 57dad6fba..31af04d52 100644 --- a/lib/ui5Framework/maven/Installer.js +++ b/lib/ui5Framework/maven/Installer.js @@ -6,6 +6,7 @@ const StreamZip = _StreamZip.async; import {promisify} from "node:util"; import Registry from "./Registry.js"; import AbstractInstaller from "../AbstractInstaller.js"; +import CacheMode from "./CacheMode.js"; import {rimraf} from "rimraf"; const stat = promisify(fs.stat); const readFile = promisify(fs.readFile); @@ -26,10 +27,9 @@ class Installer extends AbstractInstaller { * @param {Function} parameters.snapshotEndpointUrlCb Callback that returns a Promise , * resolving to the Maven repository URL. * Example: https://registry.corp/vendor/build-snapshots/ - * @param {string} [parameters.cacheMode="default"] Can be "default" (cache everything, invalidate after 9 hours), - * "off" (do not cache), "force" (use cache only - no requests) + * @param {module:@ui5/project/ui5Framework/maven/CacheMode} [parameters.cacheMode=Default] Cache mode to use */ - constructor({ui5HomeDir, snapshotEndpointUrlCb, cacheMode = "default"}) { + constructor({ui5HomeDir, snapshotEndpointUrlCb, cacheMode = CacheMode.Default}) { super(ui5HomeDir); this._artifactsDir = path.join(ui5HomeDir, "framework", "artifacts"); @@ -43,6 +43,10 @@ class Installer extends AbstractInstaller { if (!this._snapshotEndpointUrlCb) { throw new Error(`Installer: Missing Snapshot-Endpoint URL callback parameter`); } + if (!Object.values(CacheMode).includes(cacheMode)) { + throw new Error(`Installer: Invalid value '${cacheMode}' for cacheMode parameter. ` + + `Must be one of ${Object.values(CacheMode).join(", ")}`); + } log.verbose(`Installing Maven artifacts to: ${this._artifactsDir}`); log.verbose(`Installing Packages to: ${this._packagesDir}`); @@ -119,7 +123,7 @@ class Installer extends AbstractInstaller { return this._synchronize("metadata-" + fsId, async () => { const localMetadata = await this._getLocalArtifactMetadata(fsId); - if (this._cacheMode === "force" && !localMetadata.revision) { + if (this._cacheMode === CacheMode.Force && !localMetadata.revision) { throw new Error(`Could not find artifact ` + `${logId} in local cache`); } @@ -127,8 +131,8 @@ class Installer extends AbstractInstaller { const now = new Date().getTime(); const timeSinceLastCheck = now - localMetadata.lastCheck; - if (this._cacheMode !== "force" && - (timeSinceLastCheck > CACHE_TIME || this._cacheMode === "off")) { + if (this._cacheMode !== CacheMode.Force && + (timeSinceLastCheck > CACHE_TIME || this._cacheMode === CacheMode.Off)) { // No cached metadata (-> timeSinceLastCheck equals time since 1970) or // too old metadata or disabled cache // => Retrieve metadata from repository diff --git a/package.json b/package.json index 472865f78..69e0f08d5 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "./ui5Framework/Sapui5MavenSnapshotResolver": "./lib/ui5Framework/Sapui5MavenSnapshotResolver.js", "./ui5Framework/Openui5Resolver": "./lib/ui5Framework/Openui5Resolver.js", "./ui5Framework/Sapui5Resolver": "./lib/ui5Framework/Sapui5Resolver.js", + "./ui5Framework/maven/CacheMode": "./lib/ui5Framework/maven/CacheMode.js", "./validation/validator": "./lib/validation/validator.js", "./validation/ValidationError": "./lib/validation/ValidationError.js", "./graph/ProjectGraph": "./lib/graph/ProjectGraph.js", diff --git a/test/lib/graph/graph.integration.js b/test/lib/graph/graph.integration.js index 8aa4d2a20..649c130fb 100644 --- a/test/lib/graph/graph.integration.js +++ b/test/lib/graph/graph.integration.js @@ -4,6 +4,7 @@ import path from "node:path"; import sinonGlobal from "sinon"; import esmock from "esmock"; import Workspace from "../../../lib/graph/Workspace.js"; +import CacheMode from "../../../lib/ui5Framework/maven/CacheMode.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesPath = path.join(__dirname, "..", "..", "fixtures"); @@ -253,7 +254,8 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom rootConfigPath: "/rootConfigPath", versionOverride: "versionOverride", workspaceName: "default", - workspaceConfigPath: path.join(libraryHPath, "custom-ui5-workspace.yaml") + workspaceConfigPath: path.join(libraryHPath, "custom-ui5-workspace.yaml"), + cacheMode: CacheMode.Force }); t.is(res, "graph"); @@ -276,6 +278,7 @@ test.serial("graphFromPackageDependencies with inactive workspace file at custom "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - workspace: null + workspace: null, + cacheMode: "Force" }, "enrichProjectGraph got called with correct options"); }); diff --git a/test/lib/graph/graph.js b/test/lib/graph/graph.js index a8a32e7d0..7fcf7815f 100644 --- a/test/lib/graph/graph.js +++ b/test/lib/graph/graph.js @@ -3,6 +3,7 @@ import {fileURLToPath} from "node:url"; import path from "node:path"; import sinonGlobal from "sinon"; import esmock from "esmock"; +import CacheMode from "../../../lib/ui5Framework/maven/CacheMode.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesPath = path.join(__dirname, "..", "..", "fixtures"); @@ -57,7 +58,8 @@ test.serial("graphFromPackageDependencies", async (t) => { cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: CacheMode.Off }); t.is(res, "graph"); @@ -81,7 +83,8 @@ test.serial("graphFromPackageDependencies", async (t) => { "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - workspace: undefined + workspace: undefined, + cacheMode: "Off" }, "enrichProjectGraph got called with correct options"); }); @@ -98,6 +101,7 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { rootConfigPath: "/rootConfigPath", versionOverride: "versionOverride", workspaceName: "dolphin", + cacheMode: CacheMode.Off }); t.is(res, "graph"); @@ -128,7 +132,8 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - workspace: "workspace" + workspace: "workspace", + cacheMode: "Off" }, "enrichProjectGraph got called with correct options"); }); @@ -225,6 +230,7 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { rootConfigPath: "/rootConfigPath", versionOverride: "versionOverride", workspaceName: "dolphin", + cacheMode: CacheMode.Off }); t.is(res, "graph"); @@ -255,7 +261,8 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { versionOverride: "versionOverride", - workspace: null + workspace: null, + cacheMode: "Off" }, "enrichProjectGraph got called with correct options"); }); @@ -290,7 +297,8 @@ test.serial("graphFromStaticFile", async (t) => { filePath: "file/path", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: CacheMode.Off }); t.is(res, "graph"); @@ -316,7 +324,8 @@ test.serial("graphFromStaticFile", async (t) => { t.is(enrichProjectGraphStub.getCall(0).args[0], "graph", "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "Off" }, "enrichProjectGraph got called with correct options"); }); @@ -351,7 +360,8 @@ test.serial("usingObject", async (t) => { dependencyTree: "dependencyTree", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "Off" }); t.is(res, "graph"); @@ -371,7 +381,8 @@ test.serial("usingObject", async (t) => { t.is(enrichProjectGraphStub.getCall(0).args[0], "graph", "enrichProjectGraph got called with graph"); t.deepEqual(enrichProjectGraphStub.getCall(0).args[1], { - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "Off" }, "enrichProjectGraph got called with correct options"); }); diff --git a/test/lib/graph/helpers/ui5Framework.js b/test/lib/graph/helpers/ui5Framework.js index 91009cec6..fd5070d6f 100644 --- a/test/lib/graph/helpers/ui5Framework.js +++ b/test/lib/graph/helpers/ui5Framework.js @@ -6,6 +6,7 @@ import esmock from "esmock"; import DependencyTreeProvider from "../../../../lib/graph/providers/DependencyTree.js"; import projectGraphBuilder from "../../../../lib/graph/projectGraphBuilder.js"; import Specification from "../../../../lib/specifications/Specification.js"; +import CacheMode from "../../../../lib/ui5Framework/maven/CacheMode.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -107,6 +108,7 @@ test.serial("enrichProjectGraph", async (t) => { t.is(t.context.Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(t.context.Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: dependencyTree.configuration.framework.version, providedLibraryMetadata: undefined @@ -177,7 +179,6 @@ test.serial("enrichProjectGraph: without framework configuration", async (t) => test.serial("enrichProjectGraph SNAPSHOT", async (t) => { const {sinon, ui5Framework, utils, Sapui5MavenSnapshotResolverInstallStub} = t.context; - process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT = "__url__"; const dependencyTree = { id: "test1", @@ -216,7 +217,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, { + cacheMode: CacheMode.Force + }); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); @@ -262,8 +265,6 @@ test.serial("enrichProjectGraph SNAPSHOT", async (t) => { t.deepEqual(callbackCalls, [ "application.a" ], "Traversed graph in correct order"); - - delete process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT; }); test.serial("enrichProjectGraph: With versionOverride", async (t) => { @@ -313,6 +314,7 @@ test.serial("enrichProjectGraph: With versionOverride", async (t) => { t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: "1.99.9", providedLibraryMetadata: undefined @@ -451,6 +453,7 @@ test.serial("enrichProjectGraph should resolve framework project with version an t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGrap should be called once"); t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: "1.2.3", providedLibraryMetadata: undefined @@ -531,6 +534,7 @@ test.serial("enrichProjectGraph should resolve framework project " + t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: "1.99.9", providedLibraryMetadata: undefined @@ -794,6 +798,7 @@ test.serial("enrichProjectGraph should use framework library metadata from works t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: "1.111.1", providedLibraryMetadata: workspaceFrameworkLibraryMetadata @@ -851,6 +856,7 @@ test.serial("enrichProjectGraph should allow omitting framework version in case t.is(Sapui5ResolverStub.callCount, 1, "Sapui5Resolver#constructor should be called once"); t.deepEqual(Sapui5ResolverStub.getCall(0).args, [{ + cacheMode: undefined, cwd: dependencyTree.path, version: undefined, providedLibraryMetadata: workspaceFrameworkLibraryMetadata diff --git a/test/lib/package-exports.js b/test/lib/package-exports.js index c3c7284d6..e1e9737f9 100644 --- a/test/lib/package-exports.js +++ b/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, 12); + t.is(Object.keys(packageJson.exports).length, 13); }); // Public API contract (exported modules) @@ -24,6 +24,7 @@ test("check number of exports", (t) => { "ui5Framework/Openui5Resolver", "ui5Framework/Sapui5Resolver", "ui5Framework/Sapui5MavenSnapshotResolver", + "ui5Framework/maven/CacheMode", "validation/validator", "validation/ValidationError", "graph/ProjectGraph", diff --git a/test/lib/ui5framework/maven/Installer.js b/test/lib/ui5framework/maven/Installer.js index 21451dfc8..659e9b08c 100644 --- a/test/lib/ui5framework/maven/Installer.js +++ b/test/lib/ui5framework/maven/Installer.js @@ -684,7 +684,7 @@ test.serial("_fetchArtifactMetadata: Cache available but disabled", async (t) => cwd: "/cwd/", ui5HomeDir: "/ui5Home/", snapshotEndpointUrlCb: () => {}, - cacheMode: "off" + cacheMode: "Off" }); sinon.stub(installer, "_synchronize").callsFake( async (pckg, callback) => await callback()); @@ -720,7 +720,7 @@ test.serial("_fetchArtifactMetadata: Cache outdated but enforced", async (t) => cwd: "/cwd/", ui5HomeDir: "/ui5Home/", snapshotEndpointUrlCb: () => {}, - cacheMode: "force" + cacheMode: "Force" }); sinon.stub(installer, "_synchronize").callsFake( async (pckg, callback) => await callback()); @@ -757,7 +757,7 @@ test.serial("_fetchArtifactMetadata throws", async (t) => { cwd: "/cwd/", ui5HomeDir: "/ui5Home/", snapshotEndpointUrlCb: () => {}, - cacheMode: "force" + cacheMode: "Force" }); sinon.stub(installer, "_synchronize").callsFake( async (pckg, callback) => await callback());