diff --git a/lib/graph/graph.js b/lib/graph/graph.js index 3cfe3a843..42de10ee6 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 * @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 * @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 * @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..9ae312790 100644 --- a/lib/graph/helpers/ui5Framework.js +++ b/lib/graph/helpers/ui5Framework.js @@ -280,10 +280,10 @@ 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 * @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 */ @@ -369,7 +369,7 @@ export default { cwd: rootProject.getRootPath(), version, providedLibraryMetadata, - ...options.resolverConfig, + cacheMode: options.cacheMode }); let startTime; diff --git a/lib/ui5Framework/Sapui5MavenSnapshotResolver.js b/lib/ui5Framework/Sapui5MavenSnapshotResolver.js index f276a9727..95e540f00 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,6 +136,7 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { snapshotEndpointUrl = process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT_URL || snapshotEndpointUrl; if (!snapshotEndpointUrl) { + // Return an unexecuted promise instead of the resolved URL here. // 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 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..7dedf243e 100644 --- a/test/lib/graph/graph.integration.js +++ b/test/lib/graph/graph.integration.js @@ -253,7 +253,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: "force" }); t.is(res, "graph"); @@ -276,6 +277,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..5c3c469ca 100644 --- a/test/lib/graph/graph.js +++ b/test/lib/graph/graph.js @@ -57,7 +57,8 @@ test.serial("graphFromPackageDependencies", async (t) => { cwd: "cwd", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "off" }); t.is(res, "graph"); @@ -81,7 +82,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 +100,7 @@ test.serial("graphFromPackageDependencies with workspace name", async (t) => { rootConfigPath: "/rootConfigPath", versionOverride: "versionOverride", workspaceName: "dolphin", + cacheMode: "off" }); t.is(res, "graph"); @@ -128,7 +131,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 +229,7 @@ test.serial("graphFromPackageDependencies with empty workspace", async (t) => { rootConfigPath: "/rootConfigPath", versionOverride: "versionOverride", workspaceName: "dolphin", + cacheMode: "off" }); t.is(res, "graph"); @@ -255,7 +260,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 +296,8 @@ test.serial("graphFromStaticFile", async (t) => { filePath: "file/path", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "off" }); t.is(res, "graph"); @@ -316,7 +323,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 +359,8 @@ test.serial("usingObject", async (t) => { dependencyTree: "dependencyTree", rootConfiguration: "rootConfiguration", rootConfigPath: "/rootConfigPath", - versionOverride: "versionOverride" + versionOverride: "versionOverride", + cacheMode: "off" }); t.is(res, "graph"); @@ -371,7 +380,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..ab0457019 100644 --- a/test/lib/graph/helpers/ui5Framework.js +++ b/test/lib/graph/helpers/ui5Framework.js @@ -107,6 +107,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 +178,7 @@ 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__"; + process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT_URL = "__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: "force" + }); t.is(getFrameworkLibrariesFromGraphStub.callCount, 1, "getFrameworkLibrariesFromGraph should be called once"); @@ -263,7 +266,7 @@ test.serial("enrichProjectGraph SNAPSHOT", async (t) => { "application.a" ], "Traversed graph in correct order"); - delete process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT; + delete process.env.UI5_MAVEN_SNAPSHOT_ENDPOINT_URL; }); test.serial("enrichProjectGraph: With versionOverride", async (t) => { @@ -313,6 +316,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 +455,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 +536,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 +800,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 +858,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());