diff --git a/index.js b/index.js index 21360bc5..e9bfca0a 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,11 @@ module.exports = { /** * @type {typeof import('./lib/adapters/Memory')} */ - Memory: "./lib/adapters/Memory" + Memory: "./lib/adapters/Memory", + /** + * @type {typeof import('./lib/adapters/ZipArchive')} + */ + ZipArchive: "./lib/adapters/ZipArchive" }, /** * @public @@ -65,6 +69,10 @@ module.exports = { * @type {typeof import('./lib/Resource')} */ Resource: "./lib/Resource", + /** + * @type {typeof import('./lib/ProjectResource')} + */ + ProjectResource: "./lib/ProjectResource", /** * @type {typeof import('./lib/ResourceTagCollection')} */ diff --git a/lib/AbstractReader.js b/lib/AbstractReader.js index c4bab6b3..63958b4f 100644 --- a/lib/AbstractReader.js +++ b/lib/AbstractReader.js @@ -102,6 +102,19 @@ class AbstractReader { callback }); } + /** + * Create a [Link-Reader]{@link module:@ui5/fs.readers.Link} from the current reader + * + * @public + * @returns {module:@ui5/fs.reader.Link} Link instance + */ + link(pathMapping) { + const Link = require("./readers/Link"); + return new Link({ + reader: this, + pathMapping + }); + } /** * Locates resources by one or more glob patterns. diff --git a/lib/ProjectResource.js b/lib/ProjectResource.js new file mode 100644 index 00000000..f7b1e8c4 --- /dev/null +++ b/lib/ProjectResource.js @@ -0,0 +1,64 @@ +const Resource = require("./Resource"); + +/** + * Project Resource + * + * @public + * @memberof module:@ui5/fs + */ +class ProjectResource extends Resource { + /** + * Function for dynamic creation of content streams + * + * @public + * @callback module:@ui5/fs.Resource~createStream + * @returns {stream.Readable} A readable stream of a resources content + */ + + /** + * The constructor. + * + * @public + * @param {object} parameters Parameters + * @param {string} parameters.path Virtual path + * @param {fs.Stats|object} [parameters.statInfo] File information. Instance of + * [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} or similar object + * @param {Buffer} [parameters.buffer] Content of this resources as a Buffer instance + * (cannot be used in conjunction with parameters string, stream or createStream) + * @param {string} [parameters.string] Content of this resources as a string + * (cannot be used in conjunction with parameters buffer, stream or createStream) + * @param {Stream} [parameters.stream] Readable stream of the content of this resource + * (cannot be used in conjunction with parameters buffer, string or createStream) + * @param {module:@ui5/fs.Resource~createStream} [parameters.createStream] Function callback that returns a readable + * stream of the content of this resource (cannot be used in conjunction with parameters buffer, + * string or stream). + * In some cases this is the most memory-efficient way to supply resource content + * @param {module:@ui5/project.specifications.Project} parameters.project Project this resource belongs to + * @param {object} parameters.source TODO + */ + constructor({path, statInfo, buffer, string, createStream, stream, project, source}) { + super({path, statInfo, buffer, string, createStream, stream, source}); + if (!project) { + throw new Error("Cannot create ProjectResource: 'project' parameters missing"); + } + if (!source) { + throw new Error("Cannot create ProjectResource: 'source' parameters missing"); + } + this.__project = project; // Two underscores since "_project" was widely used in UI5 Tooling 2.0 + } + + getProject() { + return this.__project; + } + + getSource() { + return this._source; + } + + _createClone(options) { + options.project = this.__project; + return new ProjectResource(options); + } +} + +module.exports = ProjectResource; diff --git a/lib/Resource.js b/lib/Resource.js index 5f2a8094..ff3bd0cc 100644 --- a/lib/Resource.js +++ b/lib/Resource.js @@ -39,9 +39,8 @@ class Resource { * string or stream). * In some cases this is the most memory-efficient way to supply resource content * @param {object} [parameters.source] Experimental, internal parameter. Do not use - * @param {object} [parameters.project] Experimental, internal parameter. Do not use */ - constructor({path, statInfo, buffer, string, createStream, stream, source, project}) { + constructor({path, statInfo, buffer, string, createStream, stream, source}) { if (!path) { throw new Error("Cannot create Resource: path parameter missing"); } @@ -50,11 +49,11 @@ class Resource { throw new Error("Cannot create Resource: Please set only one content parameter. " + "Buffer, string, stream or createStream"); } - + // TODO: Namespace? this._path = path; this._name = this._getNameFromPath(path); - this._project = project; // Experimental, internal parameter - this._source = source; // Experimental, internal parameter + + this._source = source; // Experimental, internal parameter // TODO Move to ProjectResource? if (this._source) { // Indicator for adapters like FileSystem to detect whether a resource has been changed this._source.modified = false; @@ -292,10 +291,11 @@ class Resource { * @public * @returns {Promise} Promise resolving with the clone */ - clone() { + async clone() { const options = { path: this._path, - statInfo: clone(this._statInfo) + statInfo: clone(this._statInfo), + source: this._source }; const addContentOption = () => { @@ -314,10 +314,14 @@ class Resource { }; return addContentOption().then(() => { - return new Resource(options); + return this._createClone(options); }); } + _createClone(options) { + return new Resource(options); + } + /** * Tracing: Get tree for printing out trace * diff --git a/lib/ResourceFacade.js b/lib/ResourceFacade.js new file mode 100644 index 00000000..d7d5a7c4 --- /dev/null +++ b/lib/ResourceFacade.js @@ -0,0 +1,179 @@ +/** + * A [Resource]{module:@ui5/project.Resource} with a different path than it's original + * + * @public + * @memberof module:@ui5/fs + */ +class ResourceFacade { + /** + * The constructor. + * + * @public + * @param {object} parameters Parameters + * @param {string} parameters.path Virtual path + * @param {module:@ui5/fs.Resource} parameters.resource Resource to cover + */ + constructor({path, resource}) { + if (!path) { + throw new Error("Cannot create ResourceFacade: path parameter missing"); + } + if (!resource) { + throw new Error("Cannot create ResourceFacade: resource parameter missing"); + } + this._path = path; + this._resource = resource; + } + + /** + * Gets the resources path + * + * @public + * @returns {string} (Virtual) path of the resource + */ + getPath() { + return this._path; + } + + /** + * Sets the resources path + * + * @public + * @param {string} path (Virtual) path of the resource + */ + setPath(path) { + throw new Error(`The path of a ResourceFacade can't be changed at the moment`); + } + + /** + * Returns a clone of the resource. The clones content is independent from that of the original resource. + * A ResourceFacade becomes a Resource + * + * @public + * @returns {Promise} Promise resolving with the clone + */ + async clone() { + // Cloning resolves the facade + const resourceClone = await this._resource.clone(); + resourceClone.setPath(this.getPath()); + return resourceClone; + } + + /** + * ====================================================================== + * Call through functions to original resource + * ====================================================================== + */ + /** + * Gets a buffer with the resource content. + * + * @public + * @returns {Promise} Promise resolving with a buffer of the resource content. + */ + async getBuffer() { + return this._resource.getBuffer(); + } + + /** + * Sets a Buffer as content. + * + * @public + * @param {Buffer} buffer Buffer instance + */ + setBuffer(buffer) { + return this._resource.setBuffer(buffer); + } + + /** + * Gets a string with the resource content. + * + * @public + * @returns {Promise} Promise resolving with the resource content. + */ + getString() { + return this._resource.getString(); + } + + /** + * Sets a String as content + * + * @public + * @param {string} string Resource content + */ + setString(string) { + return this._resource.setString(string); + } + + /** + * Gets a readable stream for the resource content. + * + * Repetitive calls of this function are only possible if new content has been set in the meantime (through + * [setStream]{@link module:@ui5/fs.Resource#setStream}, [setBuffer]{@link module:@ui5/fs.Resource#setBuffer} + * or [setString]{@link module:@ui5/fs.Resource#setString}). This + * is to prevent consumers from accessing drained streams. + * + * @public + * @returns {stream.Readable} Readable stream for the resource content. + */ + getStream() { + return this._resource.getStream(); + } + + /** + * Sets a readable stream as content. + * + * @public + * @param {stream.Readable|module:@ui5/fs.Resource~createStream} stream Readable stream of the resource content or + callback for dynamic creation of a readable stream + */ + setStream(stream) { + return this._resource.setStream(stream); + } + + /** + * Gets the resources stat info. + * Note that a resources stat information is not updated when the resource is being modified. + * Also, depending on the used adapter, some fields might be missing which would be present for a + * [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} instance. + * + * @public + * @returns {fs.Stats|object} Instance of [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} + * or similar object + */ + getStatInfo() { + return this._resource.getStatInfo(); + } + + /** + * Size in bytes allocated by the underlying buffer. + * + * @see {TypedArray#byteLength} + * @returns {Promise} size in bytes, 0 if there is no content yet + */ + async getSize() { + return this._resource.getSize(); + } + + /** + * Adds a resource collection name that was involved in locating this resource. + * + * @param {string} name Resource collection name + */ + pushCollection(name) { + return this._resource.pushCollection(name); + } + + /** + * Tracing: Get tree for printing out trace + * + * @returns {object} Trace tree + */ + getPathTree() { + return this._resource.getPathTree(); + } + + getSource() { + return this._resource.getSource(); + } +} + +module.exports = ResourceFacade; diff --git a/lib/adapters/AbstractAdapter.js b/lib/adapters/AbstractAdapter.js index c21c61e8..cc4c534b 100644 --- a/lib/adapters/AbstractAdapter.js +++ b/lib/adapters/AbstractAdapter.js @@ -27,6 +27,9 @@ class AbstractAdapter extends AbstractReaderWriter { throw new TypeError("Class 'AbstractAdapter' is abstract"); } super(); + if (!virBasePath.endsWith("/")) { + throw new Error(`Virtual base path must end with a slash: ${virBasePath}`); + } this._virBasePath = virBasePath; this._virBaseDir = virBasePath.slice(0, -1); this._excludes = excludes; @@ -68,12 +71,15 @@ class AbstractAdapter extends AbstractReaderWriter { const subPath = patterns[i]; return Promise.resolve([ new Resource({ - project: this.project, + project: this._project, statInfo: { // TODO: make closer to fs stat info isDirectory: function() { return true; } }, + source: { + adapter: "Abstract" + }, path: subPath }) ]); @@ -170,6 +176,17 @@ class AbstractAdapter extends AbstractReaderWriter { return resultGlobs; }); } + + _createResource(parameters) { + let Resource; + if (this._project) { + Resource = require("../ProjectResource"); + parameters.project = this._project; + } else { + Resource = require("../Resource"); + } + return new Resource(parameters); + } } module.exports = AbstractAdapter; diff --git a/lib/adapters/FileSystem.js b/lib/adapters/FileSystem.js index de61ccb0..138c8ecd 100644 --- a/lib/adapters/FileSystem.js +++ b/lib/adapters/FileSystem.js @@ -7,7 +7,6 @@ const chmod = promisify(fs.chmod); const globby = require("globby"); const makeDir = require("make-dir"); const {PassThrough} = require("stream"); -const Resource = require("../Resource"); const AbstractAdapter = require("./AbstractAdapter"); const READ_ONLY_MODE = 0o444; @@ -60,7 +59,7 @@ class FileSystem extends AbstractAdapter { if (err) { reject(err); } else { - resolve(new Resource({ + resolve(this._createResource({ project: this._project, statInfo: stat, path: this._virBaseDir, @@ -96,7 +95,7 @@ class FileSystem extends AbstractAdapter { if (err) { reject(err); } else { - resolve(new Resource({ + resolve(this._createResource({ project: this._project, statInfo: stat, path: virPath, @@ -137,7 +136,7 @@ class FileSystem extends AbstractAdapter { if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) { // Neither starts with basePath, nor equals baseDirectory if (!options.nodir && this._virBasePath.startsWith(virPath)) { - resolve(new Resource({ + resolve(this._createResource({ project: this._project, statInfo: { // TODO: make closer to fs stat info isDirectory: function() { @@ -183,7 +182,7 @@ class FileSystem extends AbstractAdapter { }; } - resolve(new Resource(options)); + resolve(this._createResource(options)); } }); }); diff --git a/lib/adapters/Memory.js b/lib/adapters/Memory.js index 2f78b2d0..bce2fec6 100644 --- a/lib/adapters/Memory.js +++ b/lib/adapters/Memory.js @@ -1,6 +1,5 @@ const log = require("@ui5/logger").getLogger("resources:adapters:Memory"); const micromatch = require("micromatch"); -const Resource = require("../Resource"); const AbstractAdapter = require("./AbstractAdapter"); /** @@ -39,13 +38,16 @@ class Memory extends AbstractAdapter { async _runGlob(patterns, options = {nodir: true}, trace) { if (patterns[0] === "" && !options.nodir) { // Match virtual root directory return [ - new Resource({ - project: this.project, + this._createResource({ + project: this._project, statInfo: { // TODO: make closer to fs stat info isDirectory: function() { return true; } }, + source: { + adapter: "Memory" + }, path: this._virBasePath.slice(0, -1) }) ]; @@ -133,8 +135,11 @@ class Memory extends AbstractAdapter { for (let i = pathSegments.length - 1; i >= 0; i--) { const segment = pathSegments[i]; if (!this._virDirs[segment]) { - this._virDirs[segment] = new Resource({ - project: this.project, + this._virDirs[segment] = this._createResource({ + project: this._project, + source: { + adapter: "Memory" + }, statInfo: { // TODO: make closer to fs stat info isDirectory: function() { return true; diff --git a/lib/adapters/ZipArchive.js b/lib/adapters/ZipArchive.js new file mode 100644 index 00000000..c3ca9472 --- /dev/null +++ b/lib/adapters/ZipArchive.js @@ -0,0 +1,236 @@ +const log = require("@ui5/logger").getLogger("resources:adapters:ZIPArchive"); +const micromatch = require("micromatch"); +const Resource = require("../Resource"); +const AbstractAdapter = require("./AbstractAdapter"); +const StreamZip = require("node-stream-zip"); + +/** + * Virtual resource Adapter + * + * @public + * @memberof module:@ui5/fs.adapters + * @augments module:@ui5/fs.adapters.AbstractAdapter + */ +class ZIPArchive extends AbstractAdapter { + /** + * The constructor. + * + * @public + * @param {object} parameters Parameters + * @param {string} parameters.virBasePath Virtual base path + * @param parameters.project + * @param parameters.fsArchive + * @param parameters.archivePath + * @param {string[]} [parameters.excludes] List of glob patterns to exclude + */ + constructor({virBasePath, project, fsArchive, archivePath = "/", excludes}) { + super({virBasePath, project, excludes}); + this._zipPath = fsArchive; + this._archivePath = archivePath; + this._zipLoaded = null; + this._virFiles = {}; // map full of files + this._virDirs = {}; // map full of directories + } + + async _prepare() { + if ( this._zipLoaded == null ) { + this._zipLoaded = new Promise((resolve, reject) => { + const zip = this._zip = new StreamZip({ + file: this._zipPath, + storeEntries: true + }); + + // Handle errors + zip.on("error", (err) => { + console.error(err); + reject(err); + }); + zip.on("ready", () => { + console.log("Entries read: " + zip.entriesCount); + for (const entry of Object.values(zip.entries())) { + const desc = entry.isDirectory ? "directory" : `${entry.size} bytes`; + if ( entry.name.startsWith("META-INF/resources/") || + entry.name.startsWith("META-INF/test-resources/") ) { + const virPath = entry.name.slice("META-INF".length); + if ( entry.isDirectory ) { + this._virDirs[virPath] = new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return true; + } + }, + path: virPath + }); + } else { + this._virFiles[virPath] = new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return false; + } + }, + path: virPath, + createStream: async () => { + return new Promise((resolve, reject) => { + zip.stream("META-INF" + virPath, (err, stm) => { + if ( err ) { + reject(err); + } else { + resolve(stm); + } + }); + }); + } + }); + } + console.log(`Entry ${virPath}: ${desc}`); + } else { + console.log(`Entry ignored: ${entry.name}`); + let virPath = "/" + entry.name; + if (virPath.startsWith(this._archivePath)) { + console.log("orig path: " + virPath); + console.log("archive path: " + this._archivePath); + virPath = virPath.replace(this._archivePath, ""); + console.log("new path: " + virPath); + if ( entry.isDirectory ) { + this._virDirs[virPath] = true; + } else { + this._virFiles[virPath] = true; + } + } + } + } + resolve(); + }); + }); + } + + return this._zipLoaded; + } + + /** + * Locate resources by glob. + * + * @private + * @param {Array} patterns array of glob patterns + * @param {object} [options={}] glob options + * @param {boolean} [options.nodir=true] Do not match directories + * @param {module:@ui5/fs.tracing.Trace} trace Trace instance + * @returns {Promise} Promise resolving to list of resources + */ + async _runGlob(patterns, options = {nodir: true}, trace) { + if (patterns[0] === "" && !options.nodir) { // Match virtual root directory + return [ + new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return true; + } + }, + path: this._virBasePath.slice(0, -1) + }) + ]; + } + + await this._prepare(); + + const filePaths = Object.keys(this._virFiles); + const matchedFilePaths = micromatch(filePaths, patterns, { + dot: true + }); + // console.log(matchedFilePaths); + let matchedResources = await Promise.all(matchedFilePaths.map(async (virPath) => { + const stream = await new Promise((resolve, reject) => { + this._zip.stream(this._archivePath.substring(1) + virPath, (err, stm) => { + if ( err ) { + reject(err); + } else { + resolve(stm); + } + }); + }); + return new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return false; + } + }, + path: this._virBasePath + virPath, + stream: stream + }); + })); + + if (!options.nodir) { + const dirPaths = Object.keys(this._virDirs); + const matchedDirs = micromatch(dirPaths, patterns, { + dot: true + }); + matchedResources = matchedResources.concat(matchedDirs.map((virPath) => { + return new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return true; + } + }, + path: this._virBasePath + virPath + }); + })); + } + + // console.log(matchedResources); + + return matchedResources; + } + + /** + * Locates resources by path. + * + * @private + * @param {string} virPath Virtual path + * @param {object} options Options + * @param {module:@ui5/fs.tracing.Trace} trace Trace instance + * @returns {Promise} Promise resolving to a single resource + */ + async _byPath(virPath, options, trace) { + if (this.isPathExcluded(virPath)) { + return null; + } + if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) { + // Neither starts with basePath, nor equals baseDirectory + return null; + } + + await this._prepare(); + + const relPath = virPath.substr(this._virBasePath.length); + trace.pathCall(); + if (!this._virFiles[relPath]) { + return null; + } + const stream = await new Promise((resolve, reject) => { + this._zip.stream(this._archivePath.substring(1) + relPath, (err, stm) => { + if ( err ) { + reject(err); + } else { + resolve(stm); + } + }); + }); + return new Resource({ + project: this.project, + statInfo: { // TODO: make closer to fs stat info + isDirectory: function() { + return false; + } + }, + path: virPath, + stream: stream + }); + } +} + +module.exports = ZIPArchive; diff --git a/lib/readers/Link.js b/lib/readers/Link.js new file mode 100644 index 00000000..8efcfd5e --- /dev/null +++ b/lib/readers/Link.js @@ -0,0 +1,122 @@ +const AbstractReader = require("../AbstractReader"); +const ResourceFacade = require("../ResourceFacade"); +const resourceFactory = require("../resourceFactory"); +const log = require("@ui5/logger").getLogger("resources:readers:Link"); + +/** + * A reader that allows modification of all resources passed through it. + * + * @public + * @memberof module:@ui5/fs.readers + * @augments module:@ui5/fs.AbstractReader + */ +class Link extends AbstractReader { + /** + * Callback to retrieve a resource for modification. This will create a clone of the original + * resource which then takes its place in the result set of the reader + * + * @public + * @callback module:@ui5/fs.readers.Link~getResource + * @returns {Promise} Promise resolving to the resource + */ + + /** + * Constructor + * + * @param {object} parameters Parameters + * @param {module:@ui5/fs.AbstractReader} parameters.reader The resource reader to wrap + * @param {string} parameters.pathMapping + */ + constructor({reader, pathMapping}) { + super(); + if (!reader) { + throw new Error(`Missing parameter "reader"`); + } + if (!pathMapping) { + throw new Error(`Missing parameter "pathMapping"`); + } + this._reader = reader; + this._pathMapping = pathMapping; + this._validatePathMapping(pathMapping); + } + + /** + * Locates resources by glob. + * + * @private + * @param {string|string[]} patterns glob pattern as string or an array of + * glob patterns for virtual directory structure + * @param {object} options glob options + * @param {module:@ui5/fs.tracing.Trace} trace Trace instance + * @returns {Promise} Promise resolving to list of resources + */ + async _byGlob(patterns, options, trace) { + if (!(patterns instanceof Array)) { + patterns = [patterns]; + } + patterns = patterns.map((pattern) => { + if (pattern.startsWith(this._pathMapping.linkPath)) { + pattern = pattern.substr(this._pathMapping.linkPath.length); + } + return resourceFactory._prefixGlobPattern(pattern, this._pathMapping.targetPath); + }); + + // Flatten prefixed patterns + patterns = Array.prototype.concat.apply([], patterns); + + // Keep resource's internal path unchanged for now + const resources = await this._reader._byGlob(patterns, options, trace); + return resources.map((resource) => { + const resourcePath = resource.getPath(); + if (resourcePath.startsWith(this._pathMapping.targetPath)) { + return new ResourceFacade({ + resource, + path: this._pathMapping.linkPath + resourcePath.substr(this._pathMapping.targetPath.length) + }); + } + }); + } + + /** + * Locates resources by path. + * + * @private + * @param {string} virPath Virtual path + * @param {object} options Options + * @param {module:@ui5/fs.tracing.Trace} trace Trace instance + * @returns {Promise} Promise resolving to a single resource + */ + async _byPath(virPath, options, trace) { + if (!virPath.startsWith(this._pathMapping.linkPath)) { + return null; + } + const targetPath = this._pathMapping.targetPath + virPath.substr(this._pathMapping.linkPath.length); + log.verbose(`byPath: Rewriting virtual path ${virPath} to ${targetPath}`); + + const resource = await this._reader._byPath(targetPath, options, trace); + if (resource) { + return new ResourceFacade({ + resource, + path: this._pathMapping.linkPath + resource.getPath().substr(this._pathMapping.targetPath.length) + }); + } + return null; + } + + _validatePathMapping({linkPath, targetPath}) { + if (!linkPath) { + throw new Error(`Path mapping is missing attribute "linkPath"`); + } + if (!targetPath) { + throw new Error(`Path mapping is missing attribute "targetPath"`); + } + if (!linkPath.endsWith("/")) { + throw new Error(`Link path must end with a slash: ${linkPath}`); + } + if (!targetPath.endsWith("/")) { + throw new Error(`Target path must end with a slash: ${targetPath}`); + } + } +} + +module.exports = Link; diff --git a/lib/resourceFactory.js b/lib/resourceFactory.js index 66767add..6b2dc825 100644 --- a/lib/resourceFactory.js +++ b/lib/resourceFactory.js @@ -2,6 +2,7 @@ const log = require("@ui5/logger").getLogger("resources:resourceFactory"); const path = require("path"); const FsAdapter = require("./adapters/FileSystem"); const MemAdapter = require("./adapters/Memory"); +const ZipAdapter = require("./adapters/ZipArchive"); const ReaderCollection = require("./ReaderCollection"); const ReaderCollectionPrioritized = require("./ReaderCollectionPrioritized"); const DuplexCollection = require("./DuplexCollection"); @@ -199,7 +200,7 @@ const resourceFactory = { * * @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 + * @returns {string[]} A list of normalized glob patterns */ _prefixGlobPattern(virPattern, virBaseDir) { const minimatch = require("minimatch"); @@ -233,12 +234,60 @@ const resourceFactory = { */ createAdapter({fsBasePath, virBasePath, project, excludes}) { if (fsBasePath) { + let dotZipIdx = fsBasePath.indexOf(".zip/"); + if (dotZipIdx === -1) { + // Also support jar files + dotZipIdx = fsBasePath.indexOf(".jar/"); + } + if (dotZipIdx !== -1) { + console.log(virBasePath); + console.log(fsBasePath); + console.log(fsBasePath.substring(0, dotZipIdx + 4)); + console.log(fsBasePath.substring(dotZipIdx + 4)); + console.log(""); + return new ZipAdapter({ + virBasePath, + fsArchive: fsBasePath.substring(0, dotZipIdx + 4), + archivePath: fsBasePath.substring(dotZipIdx + 4) + "/" + }); + } return new FsAdapter({fsBasePath, virBasePath, project, excludes}); } else { return new MemAdapter({virBasePath, project, excludes}); } }, + /** + * Creates an adapter and wraps it in a ReaderCollection + * + * @public + * @param {object} parameters Parameters + * @param {string} parameters.virBasePath Virtual base path + * @param {string} [parameters.fsBasePath] File system base path + * @param {object} [parameters.project] Experimental, internal parameter. Do not use + * @param {string} [parameters.name] Name for the reader collection + * @returns {module:@ui5/fs.ReaderCollection} Reader collection wrapping an adapter + */ + createReader({fsBasePath, virBasePath, project, name}) { + return new ReaderCollection({ + name, + readers: [resourceFactory.createAdapter({fsBasePath, virBasePath, project})] + }); + }, + + createReaderCollection({name, readers}) { + return new ReaderCollection({ + name, + readers + }); + }, + + createReaderCollectionPrioritized({name, readers}) { + return new ReaderCollectionPrioritized({ + name, + readers + }); + }, /** * Creates a Resource. Accepts the same parameters as the Resource constructor. diff --git a/package-lock.json b/package-lock.json index be6b608d..b3601694 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "make-dir": "^3.1.0", "micromatch": "^4.0.4", "minimatch": "^3.0.3", + "node-stream-zip": "^1.15.0", "pretty-hrtime": "^1.0.3", "random-int": "^2.0.1" }, @@ -4604,6 +4605,18 @@ "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", "dev": true }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -10559,6 +10572,11 @@ "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", "dev": true }, + "node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", diff --git a/package.json b/package.json index 8407795c..99ad939f 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "make-dir": "^3.1.0", "micromatch": "^4.0.4", "minimatch": "^3.0.3", + "node-stream-zip": "^1.15.0", "pretty-hrtime": "^1.0.3", "random-int": "^2.0.1" },