UNPKG

@ui5/fs

Version:

UI5 CLI - File System Abstraction

281 lines (265 loc) 10.5 kB
import path from "node:path"; import {minimatch} from "minimatch"; import DuplexCollection from "./DuplexCollection.js"; import FsAdapter from "./adapters/FileSystem.js"; import MemAdapter from "./adapters/Memory.js"; import ReaderCollection from "./ReaderCollection.js"; import ReaderCollectionPrioritized from "./ReaderCollectionPrioritized.js"; import Resource from "./Resource.js"; import WriterCollection from "./WriterCollection.js"; import Filter from "./readers/Filter.js"; import Link from "./readers/Link.js"; import {getLogger} from "@ui5/logger"; const log = getLogger("resources:resourceFactory"); /** * @module @ui5/fs/resourceFactory * @description A collection of resource related APIs * @public */ /** * Creates a resource <code>ReaderWriter</code>. * * If a file system base path is given, file system resource <code>ReaderWriter</code> is returned. * In any other case a virtual one. * * @public * @param {object} parameters Parameters * @param {string} parameters.virBasePath Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string} [parameters.fsBasePath] * File System base path. * If this parameter is supplied, a File System adapter will be created instead of a Memory adapter. * The provided path must be absolute and must use platform-specific path segment separators. * @param {string[]} [parameters.excludes] List of glob patterns to exclude * @param {object} [parameters.useGitignore=false] * Whether to apply any excludes defined in an optional .gitignore in the base directory. * This parameter only takes effect in conjunction with the <code>fsBasePath</code> parameter. * @param {@ui5/project/specifications/Project} [parameters.project] Project this adapter belongs to (if any) * @returns {@ui5/fs/adapters/FileSystem|@ui5/fs/adapters/Memory} File System- or Virtual Adapter */ export function createAdapter({fsBasePath, virBasePath, project, excludes, useGitignore}) { if (fsBasePath) { return new FsAdapter({fsBasePath, virBasePath, project, excludes, useGitignore}); } else { return new MemAdapter({virBasePath, project, excludes}); } } /** * Creates a File System adapter and wraps it in a ReaderCollection * * @public * @param {object} parameters Parameters * @param {string} parameters.virBasePath Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string} parameters.fsBasePath * File System base path. Must be absolute and must use platform-specific path segment separators * @param {object} [parameters.project] Experimental, internal parameter. Do not use * @param {string[]} [parameters.excludes] List of glob patterns to exclude * @param {string} [parameters.name] Name for the reader collection * @returns {@ui5/fs/ReaderCollection} Reader collection wrapping an adapter */ export function createReader({fsBasePath, virBasePath, project, excludes = [], name}) { if (!fsBasePath) { // Creating a reader with a memory adapter seems pointless right now // since there would be no way to fill the adapter with resources throw new Error(`Unable to create reader: Missing parameter "fsBasePath"`); } let normalizedExcludes = excludes; // If a project is supplied, and that project is of type application, // Prefix all exclude patterns with the virtual base path (unless it already starts with that) // TODO 4.0: // TODO specVersion 4.0: Disallow excludes without namespaced prefix in configuration // Specifying an exclude for "/test" is disambigous as it neither reflects the source path nor the // ui5 runtime path of the excluded resources. Therefore, only allow paths like /resources/<namespace>/test // starting with specVersion 4.0 if (excludes.length && project && project.getType() === "application") { normalizedExcludes = excludes.map((pattern) => { if (pattern.startsWith(virBasePath) || pattern.startsWith("!" + virBasePath)) { return pattern; } log.verbose( `Prefixing exclude pattern defined in application project ${project.getName()}: ${pattern}`); return prefixGlobPattern(pattern, virBasePath); }); // Flatten list of patterns normalizedExcludes = Array.prototype.concat.apply([], normalizedExcludes); log.verbose(`Effective exclude patterns for application project ${project.getName()}:\n` + normalizedExcludes.join(", ")); } return new ReaderCollection({ name, readers: [createAdapter({ fsBasePath, virBasePath, project, excludes: normalizedExcludes })] }); } /** * Creates a ReaderCollection * * @public * @param {object} parameters Parameters * @param {string} parameters.name The collection name * @param {@ui5/fs/AbstractReader[]} parameters.readers List of resource readers (all tried in parallel) * @returns {@ui5/fs/ReaderCollection} Reader collection wrapping provided readers */ export function createReaderCollection({name, readers}) { return new ReaderCollection({ name, readers }); } /** * Creates a ReaderCollectionPrioritized * * @public * @param {object} parameters * @param {string} parameters.name The collection name * @param {@ui5/fs/AbstractReader[]} parameters.readers Prioritized list of resource readers * (first is tried first) * @returns {@ui5/fs/ReaderCollectionPrioritized} Reader collection wrapping provided readers */ export function createReaderCollectionPrioritized({name, readers}) { return new ReaderCollectionPrioritized({ name, readers }); } /** * Creates a WriterCollection * * @public * @param {object} parameters * @param {string} parameters.name The collection name * @param {object.<string, @ui5/fs/AbstractReaderWriter>} parameters.writerMapping Mapping of virtual base * paths to writers. Path are matched greedy * @returns {@ui5/fs/WriterCollection} Writer collection wrapping provided writers */ export function createWriterCollection({name, writerMapping}) { return new WriterCollection({ name, writerMapping }); } /** * Creates a [Resource]{@link @ui5/fs/Resource}. * Accepts the same parameters as the [Resource]{@link @ui5/fs/Resource} constructor. * * @public * @param {object} parameters Parameters to be passed to the resource constructor * @returns {@ui5/fs/Resource} Resource */ export function createResource(parameters) { return new Resource(parameters); } /** * Creates a Workspace * * A workspace is a DuplexCollection which reads from the project sources. It is used during the build process * to write modified files into a separate writer, this is usually a Memory adapter. If a file already exists it is * fetched from the memory to work on it in further build steps. * * @public * @param {object} parameters * @param {@ui5/fs/AbstractReader} parameters.reader Single reader or collection of readers * @param {@ui5/fs/AbstractReaderWriter} [parameters.writer] A ReaderWriter instance which is * only used for writing files. If not supplied, a Memory adapter will be created. * @param {string} [parameters.name="workspace"] Name of the collection * @param {string} [parameters.virBasePath="/"] Virtual base path * @returns {@ui5/fs/DuplexCollection} DuplexCollection which wraps the provided resource locators */ export function createWorkspace({reader, writer, virBasePath = "/", name = "workspace"}) { if (!writer) { writer = new MemAdapter({ virBasePath }); } return new DuplexCollection({ reader, writer, name }); } /** * Create a [Filter-Reader]{@link @ui5/fs/readers/Filter} with the given reader. * The provided callback is called for every resource that is retrieved through the * reader and decides whether the resource shall be passed on or dropped. * * @public * @param {object} parameters * @param {@ui5/fs/AbstractReader} parameters.reader Single reader or collection of readers * @param {@ui5/fs/readers/Filter~callback} parameters.callback * Filter function. Will be called for every resource passed through this reader. * @returns {@ui5/fs/readers/Filter} Reader instance */ export function createFilterReader(parameters) { return new Filter(parameters); } /** * Create a [Link-Reader]{@link @ui5/fs/readers/Filter} with the given reader. * The provided path mapping allows for rewriting paths segments of all resources passed through it. * * @example * import {createLinkReader} from "@ui5/fs/resourceFactory"; * const linkedReader = createLinkReader({ * reader: sourceReader, * pathMapping: { * linkPath: `/app`, * targetPath: `/resources/my-app-name/` * } * }); * * // The following resolves with a @ui5/fs/ResourceFacade of the resource * // located at "/resources/my-app-name/Component.js" in the sourceReader * const resource = await linkedReader.byPath("/app/Component.js"); * * @public * @param {object} parameters * @param {@ui5/fs/AbstractReader} parameters.reader Single reader or collection of readers * @param {@ui5/fs/readers/Link/PathMapping} parameters.pathMapping * @returns {@ui5/fs/readers/Link} Reader instance */ export function createLinkReader(parameters) { return new Link(parameters); } /** * Create a [Link-Reader]{@link @ui5/fs/readers/Link} where all requests are prefixed with * <code>/resources/<namespace></code>. * * This simulates "flat" resource access, which is for example common for projects of type * "application". * * @public * @param {object} parameters * @param {@ui5/fs/AbstractReader} parameters.reader Single reader or collection of readers * @param {string} parameters.namespace Project namespace * @returns {@ui5/fs/readers/Link} Reader instance */ export function createFlatReader({reader, namespace}) { return new Link({ reader: reader, pathMapping: { linkPath: `/`, targetPath: `/resources/${namespace}/` } }); } /** * 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 {string[]} A list of normalized glob patterns */ export function prefixGlobPattern(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; }