diff --git a/lib/types/application/ApplicationFormatter.js b/lib/types/application/ApplicationFormatter.js index 433462035..24795bb77 100644 --- a/lib/types/application/ApplicationFormatter.js +++ b/lib/types/application/ApplicationFormatter.js @@ -20,12 +20,26 @@ class ApplicationFormatter extends AbstractFormatter { }; return this.readManifest(project).then(function(manifest) { - if (!manifest["sap.app"] || !manifest["sap.app"].id) { + // check for a proper sap.app/id in manifest.json to determine namespace + const appId = manifest["sap.app"] && manifest["sap.app"].id; + if (!appId) { log.warn(`No "sap.app" ID configuration found in manifest of project ${project.metadata.name}`); return; } - project.metadata.namespace = manifest["sap.app"].id.replace(/\./g, "/"); - }).catch((err) => { + // check app id for placeholder and try to guess namespace from component controller + const parts = appId.match(/^\$\{(.*)\}$/); + if (parts) { + log.warn(`No valid "sap.app" ID found in manifest of project ${project.metadata.name}. Guessing from Component.js...`); + return this.readComponentController(project).then((code) => { + // use regex to determine namespace from component controller + const namespaceParts = code.match(/\.(?:\s+)?extend(?:\s+)?\((?:\s+)?["'](.*)\.Component["'](?:\s+)?,/); + if (namespaceParts) { + project.metadata.namespace = namespaceParts[1].replace(/\./g, "/"); + } + }); + } + project.metadata.namespace = appId.replace(/\./g, "/"); + }.bind(this)).catch((err) => { log.verbose(`No manifest found for project ${project.metadata.name}.`); }); }); @@ -33,20 +47,33 @@ class ApplicationFormatter extends AbstractFormatter { /** * Reads the manifest - * + * * @param {Object} project * @returns {Promise} resolves with the json object */ readManifest(project) { - return readFile(path.join(project.path, project.resources.pathMappings["/"], "manifest.json")) + return readFile(path.join(project.path, project.resources.pathMappings["/"], "manifest.json"), "utf-8") .then((file) => { return JSON.parse(file); }); } + /** + * Reads the Component Controller + * + * @param {Object} project + * @returns {Promise} resolves with the code of the Component Controller + */ + readComponentController(project) { + return readFile(path.join(project.path, project.resources.pathMappings["/"], "Component.js"), "utf-8") + .then((file) => { + return file; + }); + } + /** * Validates a project - * + * * @param {Object} project * @returns {Promise} resolves if successfully validated * @throws {Error} if validation fails diff --git a/test/expected/build/application.h/dest/manifest.json b/test/expected/build/application.h/dest/manifest.json index 0c7cbf74c..7de6072ce 100644 --- a/test/expected/build/application.h/dest/manifest.json +++ b/test/expected/build/application.h/dest/manifest.json @@ -2,7 +2,7 @@ "_version": "1.1.0", "sap.app": { "_version": "1.1.0", - "id": "application.g", + "id": "${project.artifactId}", "type": "application", "applicationVersion": { "version": "1.2.2" diff --git a/test/fixtures/application.h/webapp/manifest.json b/test/fixtures/application.h/webapp/manifest.json index 0c7cbf74c..7de6072ce 100644 --- a/test/fixtures/application.h/webapp/manifest.json +++ b/test/fixtures/application.h/webapp/manifest.json @@ -2,7 +2,7 @@ "_version": "1.1.0", "sap.app": { "_version": "1.1.0", - "id": "application.g", + "id": "${project.artifactId}", "type": "application", "applicationVersion": { "version": "1.2.2" diff --git a/test/lib/types/application/ApplicationFormatter.js b/test/lib/types/application/ApplicationFormatter.js index c9520b54d..1e07c2905 100644 --- a/test/lib/types/application/ApplicationFormatter.js +++ b/test/lib/types/application/ApplicationFormatter.js @@ -155,3 +155,35 @@ test("format: set namespace to id", async (t) => { t.deepEqual(project.metadata.namespace, "my/id", "namespace was successfully set since readManifest provides the correct object structure"); }); + +const applicationHPath = path.join(__dirname, "..", "..", "..", "fixtures", "application.h"); +const applicationHTree = { + id: "application.h", + version: "1.0.0", + path: applicationHPath, + dependencies: [], + _level: 0, + specVersion: "0.1", + type: "application", + metadata: { + name: "application.h" + }, + resources: { + configuration: { + paths: { + webapp: "webapp" + } + } + } +}; + + +test("namespace: detect namespace from component", async (t) => { + const myProject = clone(applicationHTree); + const applicationFormatter = new ApplicationFormatter(); + + await applicationFormatter.format(myProject); + t.deepEqual(myProject.metadata.namespace, "application/h", + "namespace was successfully set since readManifest provides the correct object structure"); +}); +