diff --git a/lib/resourceFactory.js b/lib/resourceFactory.js index 57638775..73541f5d 100644 --- a/lib/resourceFactory.js +++ b/lib/resourceFactory.js @@ -1,4 +1,5 @@ const path = require("path"); +const minimatch = require("minimatch"); const FsAdapter = require("./adapters/FileSystem"); const MemAdapter = require("./adapters/Memory"); const ReaderCollection = require("./ReaderCollection"); @@ -52,31 +53,26 @@ const resourceFactory = { function processDependencies(project) { if (project.resources && project.resources.pathMappings) { - const fsReaders = []; - for (let virBasePath in project.resources.pathMappings) { - // Create an fs reader for every path mapping + const fsAdapters = []; + for (const virBasePath in project.resources.pathMappings) { if (project.resources.pathMappings.hasOwnProperty(virBasePath)) { + // Prevent duplicate dependency resource locators const fsPath = project.resources.pathMappings[virBasePath]; const fsBasePath = path.join(project.path, fsPath); - - if (useNamespaces && project.metadata.namespace) { // Prefix resource paths with namespace - virBasePath = "/resources/" + project.metadata.namespace + virBasePath; - } - - // Prevent duplicate dependency resource locators const key = virBasePath + fsBasePath; if (dependencyPathIndex[key]) { continue; } dependencyPathIndex[key] = true; - const fsReader = resourceFactory.createAdapter({ - fsBasePath, + // Create an fs adapter for every path mapping + const fsAdapter = resourceFactory.createFsAdapterForVirtualBasePath({ + project, virBasePath, - excludes: getProjectExcludes ? getProjectExcludes(project) : [], - project + useNamespace: useNamespaces, + getProjectExcludes }); - fsReaders.push(fsReader); + fsAdapters.push(fsAdapter); } } @@ -86,11 +82,11 @@ const resourceFactory = { const virtualReader = virtualReaders[project.metadata.name]; const readerCollection = new ReaderCollectionPrioritized({ name: `fs & vir reader collection for project ${project.metadata.name}`, - readers: [virtualReader, ...fsReaders] + readers: [virtualReader, ...fsAdapters] }); dependencyCollection.push(readerCollection); } else { - dependencyCollection.push(...fsReaders); + dependencyCollection.push(...fsAdapters); } } @@ -100,19 +96,16 @@ const resourceFactory = { } if (tree.resources && tree.resources.pathMappings) { - for (let virBasePath in tree.resources.pathMappings) { + for (const virBasePath in tree.resources.pathMappings) { if (tree.resources.pathMappings.hasOwnProperty(virBasePath)) { - const fsBasePath = path.join(tree.path, tree.resources.pathMappings[virBasePath]); - - if (useNamespaces && tree.metadata.namespace) { // Prefix resource paths with namespace - virBasePath = "/resources/" + tree.metadata.namespace + virBasePath; - } - sourceResourceLocators.push(resourceFactory.createAdapter({ - fsBasePath, + // Create an fs adapter for every path mapping + const fsAdapter = resourceFactory.createFsAdapterForVirtualBasePath({ + project: tree, virBasePath, - excludes: getProjectExcludes ? getProjectExcludes(tree) : [], - project: tree - })); + useNamespace: useNamespaces, + getProjectExcludes + }); + sourceResourceLocators.push(fsAdapter); } } } @@ -135,6 +128,57 @@ const resourceFactory = { }; }, + createFsAdapterForVirtualBasePath({project, virBasePath, useNamespace, getProjectExcludes}) { + const fsPath = project.resources.pathMappings[virBasePath]; + const fsBasePath = path.join(project.path, fsPath); + + let pathExcludes; + if (getProjectExcludes) { + pathExcludes = getProjectExcludes(project); + } + if (useNamespace && project.metadata.namespace) { // Prefix resource paths with namespace + const namespacedBasePath = "/resources/" + project.metadata.namespace; + virBasePath = namespacedBasePath + virBasePath; + + if (pathExcludes) { + const normalizedPatterns = pathExcludes.map((pattern) => { + return resourceFactory._normalizePattern(pattern, namespacedBasePath); + }); + pathExcludes = Array.prototype.concat.apply([], normalizedPatterns); + } + } + + return resourceFactory.createAdapter({ + fsBasePath, + virBasePath, + excludes: pathExcludes, + project + }); + }, + + /** + * Normalizes virtual glob patterns by prefixing them with + * a given virtual base directory path + * + * @param {string} virPattern glob pattern for virtual directory structure + * @param {string} virBaseDir virtual base directory path to prefix the given patterns with + * @returns {Promise} Promise resolving to list of normalized glob patterns + */ + _normalizePattern(virPattern, virBaseDir) { + const mm = new minimatch.Minimatch(virPattern); + + const resultGlobs = []; + for (let i = 0; i < mm.globSet.length; i++) { + let resultPattern = path.posix.join(virBaseDir, mm.globSet[i]); + + if (mm.negate) { + resultPattern = "!" + resultPattern; + } + resultGlobs.push(resultPattern); + } + return resultGlobs; + }, + /** * Creates a resource ReaderWriter. * diff --git a/test/lib/resources.js b/test/lib/resources.js index ac6904b1..59fea462 100644 --- a/test/lib/resources.js +++ b/test/lib/resources.js @@ -46,7 +46,7 @@ test("Get resource from application.a (/index.html) and write it to /dest/ using })); }); -test("createCollectionsForTree", (t) => { +test("createCollectionsForTree: high level test", (t) => { // Creates resource reader collections for a given tree const resourceReaders = ui5Fs.resourceFactory.createCollectionsForTree(applicationBTree); @@ -114,6 +114,40 @@ test.serial("createCollectionsForTree with excludes", (t) => { "Fifth createAdapter call: Correct exclude patterns supplied"); }); +test.serial("createFsAdapterForVirtualBasePath", (t) => { + const createAdapterSpy = sinon.spy(ui5Fs.resourceFactory, "createAdapter"); + + const fsAdapter = ui5Fs.resourceFactory.createFsAdapterForVirtualBasePath({ + project: applicationBTreeWithExcludes, + virBasePath: "/", + useNamespace: true, + getProjectExcludes: () => { + return [ + "{/sub-directory-1/,/sub-directory-2/}**", + "/pony-path/**", + "!/duck*path/**", + "!**.json" + ]; + } + }); + + t.deepEqual(createAdapterSpy.callCount, 1, "createAdapter got called one time"); + const firstCall = createAdapterSpy.getCall(0).args[0]; + t.deepEqual(firstCall.fsBasePath, path.join(applicationBPath, "webapp"), + "First createAdapter call: Correct base path supplied"); + t.deepEqual(firstCall.excludes, [ + "/resources/id1/sub-directory-1/**", + "/resources/id1/sub-directory-2/**", + "/resources/id1/pony-path/**", + "!/resources/id1/duck*path/**", + "!/resources/id1/**.json" + ], + "First createAdapter call: Correct exclude patterns supplied"); + + t.deepEqual(fsAdapter._fsBasePath, path.join(applicationBPath, "webapp"), "Returned an FS adapter"); + // t.deepEqual(fsAdapter); +}); + /* Test data */ const applicationBTree = { "id": "application.b",