fs/lib/resourceFactory.js

  1. const log = require("@ui5/logger").getLogger("resources:resourceFactory");
  2. const path = require("path");
  3. const FsAdapter = require("./adapters/FileSystem");
  4. const MemAdapter = require("./adapters/Memory");
  5. const ReaderCollection = require("./ReaderCollection");
  6. const ReaderCollectionPrioritized = require("./ReaderCollectionPrioritized");
  7. const DuplexCollection = require("./DuplexCollection");
  8. const Resource = require("./Resource");
  9. const hasOwnProperty = Object.prototype.hasOwnProperty;
  10. /**
  11. * Resource Factory
  12. *
  13. * @public
  14. * @namespace
  15. * @alias module:@ui5/fs.resourceFactory
  16. */
  17. const resourceFactory = {
  18. /**
  19. * Callback to retrieve excludes for a given project
  20. *
  21. * @public
  22. * @callback module:@ui5/fs.resourceFactory~getProjectExcludes
  23. * @param {object} Project
  24. * @returns {string[]} List of glob patterns to exclude
  25. */
  26. /**
  27. * Callback to retrieve a prefix to use for a given virtual base path of a project
  28. *
  29. * @public
  30. * @callback module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix
  31. * @param {object} parameters Parameters
  32. * @param {object} parameters.project Project
  33. * @param {object} parameters.virBasePath virtual base path to prefix
  34. * @returns {string} Prefix for the virtual base path
  35. */
  36. /**
  37. * Creates resource reader collections for a (sub-)tree. Returns an object of resource readers:
  38. * <pre>
  39. * {
  40. * source: Resource reader for source resources
  41. * dependencies: Resource readers for dependency resources
  42. * }
  43. * </pre>
  44. *
  45. * @public
  46. * @param {object} tree A (sub-)tree
  47. * @param {object} [parameters] Parameters
  48. * @param {module:@ui5/fs.resourceFactory~getProjectExcludes} [parameters.getProjectExcludes]
  49. * Callback to retrieve the exclude globs of a project
  50. * @param {module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix} [parameters.getVirtualBasePathPrefix]
  51. * Callback to retrieve a prefix for a given virtual base path of a project if required
  52. * @param {object} [parameters.virtualReaders] Experimental, internal parameter. Do not use
  53. * @returns {object} Object containing <code>source</code> and <code>dependencies</code> resource readers
  54. */
  55. createCollectionsForTree(tree, {
  56. getProjectExcludes, getVirtualBasePathPrefix, virtualReaders={}
  57. } = {}) {
  58. // TODO 3.0: virtualReaders is private API. The virtual reader of a project should be stored on the
  59. // project itself. This requires projects to become objects independent from the dependency tree.
  60. // Also see: https://github.com/SAP/ui5-project/issues/122
  61. const dependencyCollection = [];
  62. const dependencyPathIndex = {};
  63. const virtualReaderIndex = {};
  64. const sourceResourceLocators = [];
  65. function processDependencies(project) {
  66. if (project.resources && project.resources.pathMappings) {
  67. const fsAdapters = [];
  68. for (const virBasePath in project.resources.pathMappings) {
  69. if (hasOwnProperty.call(project.resources.pathMappings, virBasePath)) {
  70. // Prevent duplicate dependency resource locators
  71. const fsPath = project.resources.pathMappings[virBasePath];
  72. const fsBasePath = path.join(project.path, fsPath);
  73. const key = virBasePath + fsBasePath;
  74. if (dependencyPathIndex[key]) {
  75. continue;
  76. }
  77. dependencyPathIndex[key] = true;
  78. // Create an fs adapter for every path mapping
  79. const fsAdapter = resourceFactory._createFsAdapterForVirtualBasePath({
  80. project,
  81. virBasePath,
  82. getProjectExcludes,
  83. getVirtualBasePathPrefix
  84. });
  85. fsAdapters.push(fsAdapter);
  86. }
  87. }
  88. if (!virtualReaderIndex[project.metadata.name] && virtualReaders[project.metadata.name]) {
  89. // Mix-in virtual reader of dependency if available and not already added
  90. virtualReaderIndex[project.metadata.name] = true;
  91. const virtualReader = virtualReaders[project.metadata.name];
  92. const readerCollection = new ReaderCollectionPrioritized({
  93. name: `fs & vir reader collection for project ${project.metadata.name}`,
  94. readers: [virtualReader, ...fsAdapters]
  95. });
  96. dependencyCollection.push(readerCollection);
  97. } else {
  98. dependencyCollection.push(...fsAdapters);
  99. }
  100. }
  101. project.dependencies.forEach(function(depProject) {
  102. processDependencies(depProject);
  103. });
  104. }
  105. if (tree.resources && tree.resources.pathMappings) {
  106. for (const virBasePath in tree.resources.pathMappings) {
  107. if (hasOwnProperty.call(tree.resources.pathMappings, virBasePath)) {
  108. // Create an fs adapter for every path mapping
  109. const fsAdapter = resourceFactory._createFsAdapterForVirtualBasePath({
  110. project: tree,
  111. virBasePath,
  112. getProjectExcludes,
  113. getVirtualBasePathPrefix
  114. });
  115. sourceResourceLocators.push(fsAdapter);
  116. }
  117. }
  118. }
  119. tree.dependencies.forEach(function(project) {
  120. processDependencies(project);
  121. });
  122. const source = new ReaderCollection({
  123. name: "source of " + tree.metadata.name,
  124. readers: sourceResourceLocators
  125. });
  126. const dependencies = new ReaderCollection({
  127. name: "dependencies of " + tree.metadata.name,
  128. readers: dependencyCollection
  129. });
  130. return {
  131. source,
  132. dependencies
  133. };
  134. },
  135. /**
  136. * Creates a FileSystem adapter mapping to the given virtual base path based on the given projects
  137. * configuration.
  138. *
  139. * @param {object} parameters Parameters
  140. * @param {Project} parameters.project A project
  141. * @param {string} parameters.virBasePath Virtual base path to create the adapter for
  142. * @param {module:@ui5/fs.resourceFactory~getProjectExcludes} [parameters.getProjectExcludes]
  143. * Callback to retrieve the exclude glob of a project
  144. * @param {module:@ui5/fs.resourceFactory~getVirtualBasePathPrefix} [parameters.getVirtualBasePathPrefix]
  145. * Callback to retrieve the exclude glob of a project
  146. * @returns {Promise<string[]>} Promise resolving to list of normalized glob patterns
  147. */
  148. _createFsAdapterForVirtualBasePath({
  149. project, virBasePath, getProjectExcludes, getVirtualBasePathPrefix
  150. }) {
  151. const fsPath = project.resources.pathMappings[virBasePath];
  152. const fsBasePath = path.join(project.path, fsPath);
  153. let pathExcludes;
  154. if (getProjectExcludes) {
  155. pathExcludes = getProjectExcludes(project);
  156. }
  157. if (getVirtualBasePathPrefix) {
  158. const virBasePathPrefix = getVirtualBasePathPrefix({project, virBasePath});
  159. if (virBasePathPrefix) {
  160. log.verbose(`Prefixing virtual base path ${virBasePath} of project ${project.metadata.name} ` +
  161. `${virBasePathPrefix}...`);
  162. virBasePath = virBasePathPrefix + virBasePath;
  163. log.verbose(`New virtual base path: ${virBasePath}`);
  164. if (pathExcludes) {
  165. const normalizedPatterns = pathExcludes.map((pattern) => {
  166. return resourceFactory._prefixGlobPattern(pattern, virBasePathPrefix);
  167. });
  168. pathExcludes = Array.prototype.concat.apply([], normalizedPatterns);
  169. }
  170. }
  171. }
  172. return resourceFactory.createAdapter({
  173. fsBasePath,
  174. virBasePath,
  175. excludes: pathExcludes,
  176. project
  177. });
  178. },
  179. /**
  180. * Normalizes virtual glob patterns by prefixing them with
  181. * a given virtual base directory path
  182. *
  183. * @param {string} virPattern glob pattern for virtual directory structure
  184. * @param {string} virBaseDir virtual base directory path to prefix the given patterns with
  185. * @returns {Promise<string[]>} Promise resolving to list of normalized glob patterns
  186. */
  187. _prefixGlobPattern(virPattern, virBaseDir) {
  188. const minimatch = require("minimatch");
  189. const mm = new minimatch.Minimatch(virPattern);
  190. const resultGlobs = [];
  191. for (let i = 0; i < mm.globSet.length; i++) {
  192. let resultPattern = path.posix.join(virBaseDir, mm.globSet[i]);
  193. if (mm.negate) {
  194. resultPattern = "!" + resultPattern;
  195. }
  196. resultGlobs.push(resultPattern);
  197. }
  198. return resultGlobs;
  199. },
  200. /**
  201. * Creates a resource <code>ReaderWriter</code>.
  202. *
  203. * If a file system base path is given, file system resource <code>ReaderWriter</code> is returned.
  204. * In any other case a virtual one.
  205. *
  206. * @public
  207. * @param {object} parameters Parameters
  208. * @param {string} parameters.virBasePath Virtual base path
  209. * @param {string} [parameters.fsBasePath] File system base path
  210. * @param {string[]} [parameters.excludes] List of glob patterns to exclude
  211. * @param {object} [parameters.project] Experimental, internal parameter. Do not use
  212. * @returns {module:@ui5/fs.adapters.FileSystem|module:@ui5/fs.adapters.Memory} File System- or Virtual Adapter
  213. */
  214. createAdapter({fsBasePath, virBasePath, project, excludes}) {
  215. if (fsBasePath) {
  216. return new FsAdapter({fsBasePath, virBasePath, project, excludes});
  217. } else {
  218. return new MemAdapter({virBasePath, project, excludes});
  219. }
  220. },
  221. /**
  222. * Creates a <code>Resource</code>. Accepts the same parameters as the Resource constructor.
  223. *
  224. * @public
  225. * @param {object} parameters Parameters to be passed to the resource constructor
  226. * @returns {module:@ui5/fs.Resource} Resource
  227. */
  228. createResource(parameters) {
  229. return new Resource(parameters);
  230. },
  231. /**
  232. * Creates a Workspace
  233. *
  234. * A workspace is a DuplexCollection which reads from the project sources. It is used during the build process
  235. * to write modified files into a separate writer, this is usually a Memory adapter. If a file already exists it is
  236. * fetched from the memory to work on it in further build steps.
  237. *
  238. * @public
  239. * @param {object} parameters
  240. * @param {module:@ui5/fs.AbstractReader} parameters.reader Single reader or collection of readers
  241. * @param {module:@ui5/fs.AbstractReaderWriter} [parameters.writer] A ReaderWriter instance which is
  242. * only used for writing files. If not supplied, a Memory adapter will be created.
  243. * @param {string} [parameters.name="vir & fs source"] Name of the collection
  244. * @param {string} [parameters.virBasePath="/"] Virtual base path
  245. * @returns {module:@ui5/fs.DuplexCollection} DuplexCollection which wraps the provided resource locators
  246. */
  247. createWorkspace({reader, writer, virBasePath = "/", name = "vir & fs source"}) {
  248. if (!writer) {
  249. writer = new MemAdapter({
  250. virBasePath
  251. });
  252. }
  253. return new DuplexCollection({
  254. reader,
  255. writer,
  256. name
  257. });
  258. }
  259. };
  260. module.exports = resourceFactory;