diff --git a/lib/ui5Framework/Sapui5MavenSnapshotResolver.js b/lib/ui5Framework/Sapui5MavenSnapshotResolver.js index 64eefff79..2d697b413 100644 --- a/lib/ui5Framework/Sapui5MavenSnapshotResolver.js +++ b/lib/ui5Framework/Sapui5MavenSnapshotResolver.js @@ -1,5 +1,6 @@ import path from "node:path"; import os from "node:os"; +import semver from "semver"; import AbstractResolver from "./AbstractResolver.js"; import Installer from "./maven/Installer.js"; import {getLogger} from "@ui5/logger"; @@ -50,6 +51,11 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { cacheMode, }); this._loadDistMetadata = null; + + // TODO 4.0: Remove support for legacy snapshot versions + this._isLegacySnapshotVersion = semver.lt(this._version, "1.116.0-SNAPSHOT", { + includePrerelease: true + }); } loadDistMetadata() { if (!this._loadDistMetadata) { @@ -95,8 +101,28 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { } const gav = metadata.gav.split(":"); let pkgName = metadata.npmPackageName; - if (!this._sources) { + + // Use "npm-dist" artifact by default + let classifier; + let extension; + if (this._sources) { + // Use npm-sources artifact if sources are requested + classifier = "npm-sources"; + extension = "zip"; + } else { + // Add "prebuilt" suffix to package name pkgName += "-prebuilt"; + + if (this._isLegacySnapshotVersion) { + // For legacy versions < 1.116.0-SNAPSHOT where npm-dist artifact is not + // yet available, use "default" JAR + classifier = null; + extension = "jar"; + } else { + // Use "npm-dist" artifact by default + classifier = "npm-dist"; + extension = "zip"; + } } return { @@ -112,8 +138,8 @@ class Sapui5MavenSnapshotResolver extends AbstractResolver { groupId: gav[0], artifactId: gav[1], version: metadata.version, - classifier: this._sources ? "npm-sources" : null, - extension: this._sources ? "zip" : "jar", + classifier, + extension, }), }; } diff --git a/lib/ui5Framework/maven/Installer.js b/lib/ui5Framework/maven/Installer.js index 53d9e6737..cd004b2cb 100644 --- a/lib/ui5Framework/maven/Installer.js +++ b/lib/ui5Framework/maven/Installer.js @@ -443,11 +443,7 @@ class Installer extends AbstractInstaller { } async _projectExists(targetDir) { - const markers = await Promise.all([ - this._pathExists(path.join(targetDir, "package.json")), - this._pathExists(path.join(targetDir, ".ui5", "build-manifest.json")) - ]); - return markers.includes(true); + return this._pathExists(path.join(targetDir, "package.json")); } async _pathExists(targetPath) { diff --git a/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js b/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js index 33f6a5411..54fe3260a 100644 --- a/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js +++ b/test/lib/ui5framework/Sapui5MavenSnapshotResolver.js @@ -132,7 +132,61 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary", async (t) => { const resolver = new Sapui5MavenSnapshotResolver({ cwd: "/test-project/", - version: "1.75.0" + version: "1.116.0-SNAPSHOT" + }); + + const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); + loadDistMetadataStub.resolves({ + libraries: { + "sap.ui.lib1": { + "npmPackageName": "@openui5/sap.ui.lib1", + "version": "1.116.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [], + "gav": "x:y:z" + } + } + }); + + t.context.installPackageStub + .callsFake(async ({pkgName, version}) => { + throw new Error(`Unknown install call: ${pkgName}@${version}`); + }) + .withArgs({ + pkgName: "@openui5/sap.ui.lib1-prebuilt", + groupId: "x", + artifactId: "y", + version: "1.116.0-SNAPSHOT", + classifier: "npm-dist", + extension: "zip", + }) + .resolves({pkgPath: "/foo/sap.ui.lib1"}); + + + const promises = await resolver.handleLibrary("sap.ui.lib1"); + + t.true(promises.metadata instanceof Promise, "Metadata promise should be returned"); + t.true(promises.install instanceof Promise, "Install promise should be returned"); + + const metadata = await promises.metadata; + t.deepEqual(metadata, { + "id": "@openui5/sap.ui.lib1-prebuilt", + "version": "1.116.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [] + }, "Expected library metadata should be returned"); + + t.deepEqual(await promises.install, {pkgPath: "/foo/sap.ui.lib1"}, "Install should resolve with expected object"); + t.is(loadDistMetadataStub.callCount, 1, "loadDistMetadata should be called once"); +}); + + +test.serial("Sapui5MavenSnapshotResolver: handleLibrary - legacy version", async (t) => { + const {Sapui5MavenSnapshotResolver} = t.context; + + const resolver = new Sapui5MavenSnapshotResolver({ + cwd: "/test-project/", + version: "1.75.0-SNAPSHOT" }); const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); @@ -140,7 +194,7 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary", async (t) => { libraries: { "sap.ui.lib1": { "npmPackageName": "@openui5/sap.ui.lib1", - "version": "1.75.0", + "version": "1.75.0-SNAPSHOT", "dependencies": [], "optionalDependencies": [], "gav": "x:y:z" @@ -156,7 +210,7 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary", async (t) => { pkgName: "@openui5/sap.ui.lib1-prebuilt", groupId: "x", artifactId: "y", - version: "1.75.0", + version: "1.75.0-SNAPSHOT", classifier: null, extension: "jar", }) @@ -171,7 +225,115 @@ test.serial("Sapui5MavenSnapshotResolver: handleLibrary", async (t) => { const metadata = await promises.metadata; t.deepEqual(metadata, { "id": "@openui5/sap.ui.lib1-prebuilt", - "version": "1.75.0", + "version": "1.75.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [] + }, "Expected library metadata should be returned"); + + t.deepEqual(await promises.install, {pkgPath: "/foo/sap.ui.lib1"}, "Install should resolve with expected object"); + t.is(loadDistMetadataStub.callCount, 1, "loadDistMetadata should be called once"); +}); + +test.serial("Sapui5MavenSnapshotResolver: handleLibrary - sources requested", async (t) => { + const {Sapui5MavenSnapshotResolver} = t.context; + + const resolver = new Sapui5MavenSnapshotResolver({ + cwd: "/test-project/", + version: "1.116.0-SNAPSHOT", + sources: true + }); + + const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); + loadDistMetadataStub.resolves({ + libraries: { + "sap.ui.lib1": { + "npmPackageName": "@openui5/sap.ui.lib1", + "version": "1.116.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [], + "gav": "x:y:z" + } + } + }); + + t.context.installPackageStub + .callsFake(async ({pkgName, version}) => { + throw new Error(`Unknown install call: ${pkgName}@${version}`); + }) + .withArgs({ + pkgName: "@openui5/sap.ui.lib1", + groupId: "x", + artifactId: "y", + version: "1.116.0-SNAPSHOT", + classifier: "npm-sources", + extension: "zip", + }) + .resolves({pkgPath: "/foo/sap.ui.lib1"}); + + + const promises = await resolver.handleLibrary("sap.ui.lib1"); + + t.true(promises.metadata instanceof Promise, "Metadata promise should be returned"); + t.true(promises.install instanceof Promise, "Install promise should be returned"); + + const metadata = await promises.metadata; + t.deepEqual(metadata, { + "id": "@openui5/sap.ui.lib1", + "version": "1.116.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [] + }, "Expected library metadata should be returned"); + + t.deepEqual(await promises.install, {pkgPath: "/foo/sap.ui.lib1"}, "Install should resolve with expected object"); + t.is(loadDistMetadataStub.callCount, 1, "loadDistMetadata should be called once"); +}); + +test.serial("Sapui5MavenSnapshotResolver: handleLibrary - sources requested with legacy version", async (t) => { + const {Sapui5MavenSnapshotResolver} = t.context; + + const resolver = new Sapui5MavenSnapshotResolver({ + cwd: "/test-project/", + version: "1.75.0-SNAPSHOT", + sources: true + }); + + const loadDistMetadataStub = sinon.stub(resolver, "loadDistMetadata"); + loadDistMetadataStub.resolves({ + libraries: { + "sap.ui.lib1": { + "npmPackageName": "@openui5/sap.ui.lib1", + "version": "1.75.0-SNAPSHOT", + "dependencies": [], + "optionalDependencies": [], + "gav": "x:y:z" + } + } + }); + + t.context.installPackageStub + .callsFake(async ({pkgName, version}) => { + throw new Error(`Unknown install call: ${pkgName}@${version}`); + }) + .withArgs({ + pkgName: "@openui5/sap.ui.lib1", + groupId: "x", + artifactId: "y", + version: "1.75.0-SNAPSHOT", + classifier: "npm-sources", + extension: "zip", + }) + .resolves({pkgPath: "/foo/sap.ui.lib1"}); + + + const promises = await resolver.handleLibrary("sap.ui.lib1"); + + t.true(promises.metadata instanceof Promise, "Metadata promise should be returned"); + t.true(promises.install instanceof Promise, "Install promise should be returned"); + + const metadata = await promises.metadata; + t.deepEqual(metadata, { + "id": "@openui5/sap.ui.lib1", + "version": "1.75.0-SNAPSHOT", "dependencies": [], "optionalDependencies": [] }, "Expected library metadata should be returned"); diff --git a/test/lib/ui5framework/maven/Installer.js b/test/lib/ui5framework/maven/Installer.js index 659e9b08c..d2bfd3adb 100644 --- a/test/lib/ui5framework/maven/Installer.js +++ b/test/lib/ui5framework/maven/Installer.js @@ -985,10 +985,12 @@ test.serial("_pathExists", async (t) => { snapshotEndpointUrlCb: () => {} }); - statStub.resolves("/target/path/"); - const pathExists = await installer._pathExists(); + statStub.resolves(); + const pathExists = await installer._pathExists("/target/path/"); - t.is(pathExists, true, "Resolves the target path"); + t.is(pathExists, true, "Target path exists"); + t.is(statStub.callCount, 1, "stat got called once"); + t.is(statStub.firstCall.firstArg, "/target/path/", "stat got called with expected argument"); }); test.serial("_pathExists file not found", async (t) => { @@ -1001,9 +1003,9 @@ test.serial("_pathExists file not found", async (t) => { }); statStub.throws({code: "ENOENT"}); - const pathExists = await installer._pathExists(); + const pathExists = await installer._pathExists("/target/path/"); - t.is(pathExists, false, "Target path is not resolved"); + t.is(pathExists, false, "Target path does not exist"); }); test.serial("_pathExists throws", async (t) => { @@ -1019,7 +1021,65 @@ test.serial("_pathExists throws", async (t) => { throw new Error("Error message"); }); - await t.throwsAsync(installer._pathExists(), { + await t.throwsAsync(installer._pathExists("/target/path/"), { message: "Error message", + }, "Threw with expected error message"); +}); + +test.serial("_projectExists", async (t) => { + const {Installer} = t.context; + + const installer = new Installer({ + cwd: "/cwd/", + ui5HomeDir: "/ui5Home/", + snapshotEndpointUrlCb: () => {} + }); + + const pathExistsStub = sinon.stub(installer, "_pathExists").resolves(true); + const projectExists = await installer._projectExists("/target/path/"); + + t.is(projectExists, true, "Resolves the target path"); + t.is(pathExistsStub.callCount, 1, "_pathExists got called once"); + t.is(pathExistsStub.firstCall.firstArg, path.join("/target/path/package.json"), + "_pathExists got called with expected argument"); +}); + +test.serial("_projectExists: Does not exist", async (t) => { + const {Installer} = t.context; + + const installer = new Installer({ + cwd: "/cwd/", + ui5HomeDir: "/ui5Home/", + snapshotEndpointUrlCb: () => {} + }); + + const pathExistsStub = sinon.stub(installer, "_pathExists").resolves(false); + const projectExists = await installer._projectExists("/target/path/"); + + t.is(projectExists, false, "Resolves the target path"); + t.is(pathExistsStub.callCount, 1, "_pathExists got called once"); + t.is(pathExistsStub.firstCall.firstArg, path.join("/target/path/package.json"), + "_pathExists got called with expected argument"); +}); + +test.serial("_projectExists: Throws", async (t) => { + const {Installer} = t.context; + + const installer = new Installer({ + cwd: "/cwd/", + ui5HomeDir: "/ui5Home/", + snapshotEndpointUrlCb: () => {} }); + + const pathExistsStub = sinon.stub(installer, "_pathExists").throws(() => { + throw new Error("Error message"); + }); + + await t.throwsAsync(installer._projectExists("/target/path/"), { + message: "Error message", + }, "Threw with expected error message"); + + t.is(pathExistsStub.callCount, 1, "_pathExists got called once"); + t.is(pathExistsStub.firstCall.firstArg, path.join("/target/path/package.json"), + "_pathExists got called with expected argument"); });