diff --git a/lib/resourceFactory.js b/lib/resourceFactory.js index dde39407..cddd3247 100644 --- a/lib/resourceFactory.js +++ b/lib/resourceFactory.js @@ -1,13 +1,3 @@ -const log = require("@ui5/logger").getLogger("resources:resourceFactory"); -const path = require("path"); -const FsAdapter = require("./adapters/FileSystem"); -const MemAdapter = require("./adapters/Memory"); -const ReaderCollection = require("./ReaderCollection"); -const ReaderCollectionPrioritized = require("./ReaderCollectionPrioritized"); -const DuplexCollection = require("./DuplexCollection"); -const Resource = require("./Resource"); -const hasOwnProperty = Object.prototype.hasOwnProperty; - /** * Resource Factory * @@ -16,207 +6,6 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; * @alias module:@ui5/fs.resourceFactory */ const resourceFactory = { - /** - * Callback to retrieve excludes for a given project - * - * @public - * @callback module:@ui5/fs.resourceFactory~getProjectExcludes - * @param {object} Project - * @returns {string[]} List of glob patterns to exclude - */ - - /** - * Callback to retrieve a prefix to use for a given virtual base path of a project - * - * @public - * @callback module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix - * @param {object} parameters Parameters - * @param {object} parameters.project Project - * @param {object} parameters.virBasePath virtual base path to prefix - * @returns {string} Prefix for the virtual base path - */ - - /** - * Creates resource reader collections for a (sub-)tree. Returns an object of resource readers: - *
-	 * {
-	 *  source: Resource reader for source resources
-	 *  dependencies: Resource readers for dependency resources
-	 * }
-	 * 
- * - * @public - * @param {object} tree A (sub-)tree - * @param {object} [parameters] Parameters - * @param {module:@ui5/fs.resourceFactory~getProjectExcludes} [parameters.getProjectExcludes] - * Callback to retrieve the exclude globs of a project - * @param {module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix} [parameters.getVirtualBasePathPrefix] - * Callback to retrieve a prefix for a given virtual base path of a project if required - * @param {object} [parameters.virtualReaders] Experimental, internal parameter. Do not use - * @returns {object} Object containing source and dependencies resource readers - */ - createCollectionsForTree(tree, { - getProjectExcludes, getVirtualBasePathPrefix, virtualReaders={} - } = {}) { - // TODO 3.0: virtualReaders is private API. The virtual reader of a project should be stored on the - // project itself. This requires projects to become objects independent from the dependency tree. - // Also see: https://github.com/SAP/ui5-project/issues/122 - - const dependencyCollection = []; - const dependencyPathIndex = {}; - const virtualReaderIndex = {}; - const sourceResourceLocators = []; - - function processDependencies(project) { - if (project.resources && project.resources.pathMappings) { - const fsAdapters = []; - for (const virBasePath in project.resources.pathMappings) { - if (hasOwnProperty.call(project.resources.pathMappings, virBasePath)) { - // Prevent duplicate dependency resource locators - const fsPath = project.resources.pathMappings[virBasePath]; - const fsBasePath = path.join(project.path, fsPath); - const key = virBasePath + fsBasePath; - if (dependencyPathIndex[key]) { - continue; - } - dependencyPathIndex[key] = true; - - // Create an fs adapter for every path mapping - const fsAdapter = resourceFactory._createFsAdapterForVirtualBasePath({ - project, - virBasePath, - getProjectExcludes, - getVirtualBasePathPrefix - }); - fsAdapters.push(fsAdapter); - } - } - - if (!virtualReaderIndex[project.metadata.name] && virtualReaders[project.metadata.name]) { - // Mix-in virtual reader of dependency if available and not already added - virtualReaderIndex[project.metadata.name] = true; - const virtualReader = virtualReaders[project.metadata.name]; - const readerCollection = new ReaderCollectionPrioritized({ - name: `fs & vir reader collection for project ${project.metadata.name}`, - readers: [virtualReader, ...fsAdapters] - }); - dependencyCollection.push(readerCollection); - } else { - dependencyCollection.push(...fsAdapters); - } - } - - project.dependencies.forEach(function(depProject) { - processDependencies(depProject); - }); - } - - if (tree.resources && tree.resources.pathMappings) { - for (const virBasePath in tree.resources.pathMappings) { - if (hasOwnProperty.call(tree.resources.pathMappings, virBasePath)) { - // Create an fs adapter for every path mapping - const fsAdapter = resourceFactory._createFsAdapterForVirtualBasePath({ - project: tree, - virBasePath, - getProjectExcludes, - getVirtualBasePathPrefix - }); - sourceResourceLocators.push(fsAdapter); - } - } - } - - tree.dependencies.forEach(function(project) { - processDependencies(project); - }); - - const source = new ReaderCollection({ - name: "source of " + tree.metadata.name, - readers: sourceResourceLocators - }); - const dependencies = new ReaderCollection({ - name: "dependencies of " + tree.metadata.name, - readers: dependencyCollection - }); - return { - source, - dependencies - }; - }, - - /** - * Creates a FileSystem adapter mapping to the given virtual base path based on the given projects - * configuration. - * - * @param {object} parameters Parameters - * @param {Project} parameters.project A project - * @param {string} parameters.virBasePath Virtual base path to create the adapter for - * @param {module:@ui5/fs.resourceFactory~getProjectExcludes} [parameters.getProjectExcludes] - * Callback to retrieve the exclude glob of a project - * @param {module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix} [parameters.getVirtualBasePathPrefix] - * Callback to retrieve the exclude glob of a project - * @returns {Promise} Promise resolving to list of normalized glob patterns - */ - _createFsAdapterForVirtualBasePath({ - project, virBasePath, getProjectExcludes, getVirtualBasePathPrefix - }) { - const fsPath = project.resources.pathMappings[virBasePath]; - const fsBasePath = path.join(project.path, fsPath); - - let pathExcludes; - if (getProjectExcludes) { - pathExcludes = getProjectExcludes(project); - } - - if (getVirtualBasePathPrefix) { - const virBasePathPrefix = getVirtualBasePathPrefix({project, virBasePath}); - if (virBasePathPrefix) { - log.verbose(`Prefixing virtual base path ${virBasePath} of project ${project.metadata.name} ` + - `${virBasePathPrefix}...`); - virBasePath = virBasePathPrefix + virBasePath; - log.verbose(`New virtual base path: ${virBasePath}`); - - if (pathExcludes) { - const normalizedPatterns = pathExcludes.map((pattern) => { - return resourceFactory._prefixGlobPattern(pattern, virBasePathPrefix); - }); - 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 - */ - _prefixGlobPattern(virPattern, virBaseDir) { - const minimatch = require("minimatch"); - 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. * @@ -233,8 +22,10 @@ const resourceFactory = { */ createAdapter({fsBasePath, virBasePath, project, excludes}) { if (fsBasePath) { + const FsAdapter = require("./adapters/FileSystem"); return new FsAdapter({fsBasePath, virBasePath, project, excludes}); } else { + const MemAdapter = require("./adapters/Memory"); return new MemAdapter({virBasePath, project, excludes}); } }, @@ -299,6 +90,7 @@ const resourceFactory = { * @returns {module:@ui5/fs.Resource} Resource */ createResource(parameters) { + const Resource = require("./Resource"); return new Resource(parameters); }, @@ -319,7 +111,10 @@ const resourceFactory = { * @returns {module:@ui5/fs.DuplexCollection} DuplexCollection which wraps the provided resource locators */ createWorkspace({reader, writer, virBasePath = "/", name = "vir & fs source"}) { + const DuplexCollection = require("./DuplexCollection"); + if (!writer) { + const MemAdapter = require("./adapters/Memory"); writer = new MemAdapter({ virBasePath }); diff --git a/test/lib/resources.js b/test/lib/resources.js index 8ccb50e6..ec8b5442 100644 --- a/test/lib/resources.js +++ b/test/lib/resources.js @@ -47,267 +47,7 @@ test("Get resource from application.a (/index.html) and write it to /dest/ using })); }); -test("createCollectionsForTree: high level test", (t) => { - // Creates resource reader collections for a given tree - const resourceReaders = ui5Fs.resourceFactory.createCollectionsForTree(applicationBTree); - - // Check whether resulting object contains both, - // resource readers for the application source itself and for its dependencies. - t.true(hasOwnProperty.call(resourceReaders, "source"), - "Contains readers for the application code"); - t.true(hasOwnProperty.call(resourceReaders, "dependencies"), - "Contains readers for the application's dependencies"); - - t.true(resourceReaders.source._readers.length === 1, "One reader for the application code"); - t.true(resourceReaders.dependencies._readers.length === 8, "Eight readers for the application's dependencies"); -}); - -test.serial("createCollectionsForTree", (t) => { - const createFsAdapterForVirtualBasePathSpy = sinon.spy(ui5Fs.resourceFactory, "_createFsAdapterForVirtualBasePath"); - - const getVirtualBasePathPrefixCallback = function() {}; - const getProjectExcludesCallback = function() {}; - - const libraryDMemoryAdapter = ui5Fs.resourceFactory.createAdapter({ - virBasePath: "/" - }); - // Creates resource reader collections for a given tree - const resourceReaders = ui5Fs.resourceFactory.createCollectionsForTree(applicationBTree, { - getVirtualBasePathPrefix: getVirtualBasePathPrefixCallback, - getProjectExcludes: getProjectExcludesCallback, - virtualReaders: { - "library.d": libraryDMemoryAdapter - } - }); - - t.deepEqual(createFsAdapterForVirtualBasePathSpy.callCount, 9, - "createFsAdapterForVirtualBasePath got called nine times"); - - t.deepEqual(resourceReaders.source._readers.length, 1, "One reader for the application code"); - t.deepEqual(resourceReaders.dependencies._readers.length, 7, - "Seven readers for the application's dependencies on top level"); - t.deepEqual(resourceReaders.dependencies._readers[0]._readers.length, 3, - "First dependency reader is a (prioritized) collection of three readers"); - t.is(resourceReaders.dependencies._readers[0]._readers[0], libraryDMemoryAdapter, - "First reader of that collection is the supplied memory reader"); - - const firstCall = createFsAdapterForVirtualBasePathSpy.getCall(0).args[0]; - t.is(firstCall.project, applicationBTree, - "First createAdapter call: Correct project supplied"); - t.deepEqual(firstCall.virBasePath, "/", - "First createAdapter call: Correct virBasePath supplied"); - t.is(firstCall.getProjectExcludes, getProjectExcludesCallback, - "First createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(firstCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "First createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const secondCall = createFsAdapterForVirtualBasePathSpy.getCall(1).args[0]; - t.is(secondCall.project, applicationBTree.dependencies[0], - "second createAdapter call: Correct project supplied"); - t.deepEqual(secondCall.virBasePath, "/resources/", - "second createAdapter call: Correct virBasePath supplied"); - t.is(secondCall.getProjectExcludes, getProjectExcludesCallback, - "second createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(secondCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "second createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const thirdCall = createFsAdapterForVirtualBasePathSpy.getCall(2).args[0]; - t.is(thirdCall.project, applicationBTree.dependencies[0], - "third createAdapter call: Correct project supplied"); - t.deepEqual(thirdCall.virBasePath, "/test-resources/", - "third createAdapter call: Correct virBasePath supplied"); - t.is(thirdCall.getProjectExcludes, getProjectExcludesCallback, - "third createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(thirdCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "third createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const fourthCall = createFsAdapterForVirtualBasePathSpy.getCall(3).args[0]; - t.is(fourthCall.project, applicationBTree.dependencies[1], - "fourth createAdapter call: Correct project supplied"); - t.deepEqual(fourthCall.virBasePath, "/resources/", - "fourth createAdapter call: Correct virBasePath supplied"); - t.is(fourthCall.getProjectExcludes, getProjectExcludesCallback, - "fourth createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(fourthCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "fourth createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const fifthCall = createFsAdapterForVirtualBasePathSpy.getCall(4).args[0]; - t.is(fifthCall.project, applicationBTree.dependencies[1], - "fifth createAdapter call: Correct project supplied"); - t.deepEqual(fifthCall.virBasePath, "/test-resources/", - "fifth createAdapter call: Correct virBasePath supplied"); - t.is(fifthCall.getProjectExcludes, getProjectExcludesCallback, - "fifth createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(fifthCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "fifth createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const sixthCall = createFsAdapterForVirtualBasePathSpy.getCall(5).args[0]; - t.is(sixthCall.project, applicationBTree.dependencies[2], - "sixth createAdapter call: Correct project supplied"); - t.deepEqual(sixthCall.virBasePath, "/resources/", - "sixth createAdapter call: Correct virBasePath supplied"); - t.is(sixthCall.getProjectExcludes, getProjectExcludesCallback, - "sixth createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(sixthCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "sixth createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const seventhCall = createFsAdapterForVirtualBasePathSpy.getCall(6).args[0]; - t.is(seventhCall.project, applicationBTree.dependencies[2], - "seventh createAdapter call: Correct project supplied"); - t.deepEqual(seventhCall.virBasePath, "/test-resources/", - "seventh createAdapter call: Correct virBasePath supplied"); - t.is(seventhCall.getProjectExcludes, getProjectExcludesCallback, - "seventh createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(seventhCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "seventh createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const eightCall = createFsAdapterForVirtualBasePathSpy.getCall(7).args[0]; - t.is(eightCall.project, applicationBTree.dependencies[3], - "eight createAdapter call: Correct project supplied"); - t.deepEqual(eightCall.virBasePath, "/resources/", - "eight createAdapter call: Correct virBasePath supplied"); - t.is(eightCall.getProjectExcludes, getProjectExcludesCallback, - "eight createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(eightCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "eight createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); - - const ninthCall = createFsAdapterForVirtualBasePathSpy.getCall(8).args[0]; - t.is(ninthCall.project, applicationBTree.dependencies[3], - "ninth createAdapter call: Correct project supplied"); - t.deepEqual(ninthCall.virBasePath, "/test-resources/", - "ninth createAdapter call: Correct virBasePath supplied"); - t.is(ninthCall.getProjectExcludes, getProjectExcludesCallback, - "ninth createAdapter call: Correct getProjectExcludes parameter supplied"); - t.is(ninthCall.getVirtualBasePathPrefix, getVirtualBasePathPrefixCallback, - "ninth createAdapter call: Correct getVirtualBasePathPrefix parameter supplied"); -}); - -test.serial("createCollectionsForTree/createFsAdapterForVirtualBasePath with excludes", (t) => { - const createAdapterSpy = sinon.spy(ui5Fs.resourceFactory, "createAdapter"); - ui5Fs.resourceFactory.createCollectionsForTree(applicationBTreeWithExcludes, { - getProjectExcludes: (proj) => { - return proj.pony.excludes; - } - }); - - t.deepEqual(createAdapterSpy.callCount, 5, "createAdapter got called three times"); - - 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, ["/pony-path/*"], - "First createAdapter call: Correct exclude patterns supplied"); - - const secondCall = createAdapterSpy.getCall(1).args[0]; - t.deepEqual(secondCall.fsBasePath, - path.join(applicationBPath, "..", "library.d", "main", "src"), - "Second createAdapter call: Correct base path supplied"); - t.deepEqual(secondCall.excludes, [ - "/unicorn-path/*", - "/duck-path/*" - ], - "Second createAdapter call: Correct exclude patterns supplied"); - - const thirdCall = createAdapterSpy.getCall(2).args[0]; - t.deepEqual(thirdCall.fsBasePath, - path.join(applicationBPath, "..", "library.d", "main", "test"), - "Third createAdapter call: Correct base path supplied"); - t.deepEqual(thirdCall.excludes, [ - "/unicorn-path/*", - "/duck-path/*" - ], - "Third createAdapter call: Correct exclude patterns supplied"); - - const fourthCall = createAdapterSpy.getCall(3).args[0]; - t.deepEqual(fourthCall.fsBasePath, - path.join(applicationBPath, "..", "collection", "library.a", "src"), - "Fourth createAdapter call: Correct base path supplied"); - t.deepEqual(fourthCall.excludes, ["/duck-path/*"], - "Fourth createAdapter call: Correct exclude patterns supplied"); - - const fifthCall = createAdapterSpy.getCall(4).args[0]; - t.deepEqual(fifthCall.fsBasePath, - path.join(applicationBPath, "..", "collection", "library.a", "test"), - "Fifth createAdapter call: Correct base path supplied"); - t.deepEqual(fifthCall.excludes, ["/duck-path/*"], - "Fifth createAdapter call: Correct exclude patterns supplied"); -}); - -test.serial("_createFsAdapterForVirtualBasePath: application with virtual base path prefixing", (t) => { - const getVirtualBasePathPrefixStub = sinon.stub().returns("/pony/path"); - const createAdapterSpy = sinon.spy(ui5Fs.resourceFactory, "createAdapter"); - - const fsAdapter = ui5Fs.resourceFactory._createFsAdapterForVirtualBasePath({ - project: applicationBTreeWithExcludes, - virBasePath: "/", - getVirtualBasePathPrefix: getVirtualBasePathPrefixStub, - getProjectExcludes: () => { - return [ - "{/sub-directory-1/,/sub-directory-2/}**", - "/pony-path/**", - "!/duck*path/**", - "!**.json" - ]; - } - }); - - t.deepEqual(getVirtualBasePathPrefixStub.callCount, 1, - "getVirtualBasePathPrefix callback called once"); - t.deepEqual(getVirtualBasePathPrefixStub.getCall(0).args[0].project.id, "application.b", - "getVirtualBasePathPrefix callback called with correct project"); - t.deepEqual(getVirtualBasePathPrefixStub.getCall(0).args[0].virBasePath, "/", - "getVirtualBasePathPrefix callback called with correct virtual base path"); - - 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, [ - "/pony/path/sub-directory-1/**", - "/pony/path/sub-directory-2/**", - "/pony/path/pony-path/**", - "!/pony/path/duck*path/**", - "!/pony/path/**.json" - ], - "First createAdapter call: Correct exclude patterns supplied"); - - t.deepEqual(fsAdapter._fsBasePath, path.join(applicationBPath, "webapp"), "Returned an FS adapter"); -}); - -test.serial("_createFsAdapterForVirtualBasePath: library", (t) => { - const createAdapterSpy = sinon.spy(ui5Fs.resourceFactory, "createAdapter"); - - const fsAdapter = ui5Fs.resourceFactory._createFsAdapterForVirtualBasePath({ - project: libraryDTree, - virBasePath: "/resources/", - getProjectExcludes: () => { - return [ - "/resources/library/d/{sub-directory-1/,sub-directory-2/}**", - "/resources/library/d/pony-path/**", - "!/resources/library/d/duck*path/**", - "!/resources/library/d/**.json" - ]; - } - }); - - t.deepEqual(createAdapterSpy.callCount, 1, "createAdapter got called one time"); - const firstCall = createAdapterSpy.getCall(0).args[0]; - t.deepEqual(firstCall.fsBasePath, path.join(libraryDTree.path, "main/src"), - "First createAdapter call: Correct base path supplied"); - t.deepEqual(firstCall.excludes, [ - // Since no virtual base path prefixing was done, no special processing - // of exclude patterns was necessary - "/resources/library/d/{sub-directory-1/,sub-directory-2/}**", - "/resources/library/d/pony-path/**", - "!/resources/library/d/duck*path/**", - "!/resources/library/d/**.json" - ], - "First createAdapter call: Correct exclude patterns supplied"); - - t.deepEqual(fsAdapter._fsBasePath, path.join(libraryDTree.path, "main/src"), "Returned an FS adapter"); -}); - -test("_prefixGlobPattern", (t) => { +test("prefixGlobPattern", (t) => { t.deepEqual( ui5Fs.resourceFactory.prefixGlobPattern("{/sub-directory-1/,/sub-directory-2/}**", "/pony/path/a"), [