// // used by archiver // Source: https://github.com/Yqnn/node-readdir-glob // module.exports = readdirGlob; const fs = require('fs'); const { EventEmitter } = require('events'); const { Minimatch } = require('../archiverUtils/minimatch'); const { resolve } = require('path'); function readdir(dir, strict) { return new Promise((resolve, reject) => { fs.readdir(dir, { withFileTypes: true }, (err, files) => { if (err) { switch (err.code) { case 'ENOTDIR': // Not a directory if (strict) { reject(err); } else { resolve([]); } break; case 'ENOTSUP': // Operation not supported case 'ENOENT': // No such file or directory case 'ENAMETOOLONG': // Filename too long case 'UNKNOWN': resolve([]); break; case 'ELOOP': // Too many levels of symbolic links default: reject(err); break; } } else { resolve(files); } }); }); } function stat(file, followSymlinks) { return new Promise((resolve, reject) => { const statFunc = followSymlinks ? fs.stat : fs.lstat; statFunc(file, (err, stats) => { if (err) { switch (err.code) { case 'ENOENT': if (followSymlinks) { // Fallback to lstat to handle broken links as files resolve(stat(file, false)); } else { resolve(null); } break; default: resolve(null); break; } } else { resolve(stats); } }); }); } async function* exploreWalkAsync(dir, path, followSymlinks, useStat, shouldSkip, strict) { let files = await readdir(path + dir, strict); for (const file of files) { let name = file.name; if (name === undefined) { // undefined file.name means the `withFileTypes` options is not supported by node // we have to call the stat function to know if file is directory or not. name = file; useStat = true; } const filename = dir + '/' + name; const relative = filename.slice(1); // Remove the leading / const absolute = path + '/' + relative; let stats = null; if (useStat || followSymlinks) { stats = await stat(absolute, followSymlinks); } if (!stats && file.name !== undefined) { stats = file; } if (stats === null) { stats = { isDirectory: () => false }; } if (stats.isDirectory()) { if (!shouldSkip(relative)) { yield { relative, absolute, stats }; yield* exploreWalkAsync(filename, path, followSymlinks, useStat, shouldSkip, false); } } else { yield { relative, absolute, stats }; } } } async function* explore(path, followSymlinks, useStat, shouldSkip) { yield* exploreWalkAsync('', path, followSymlinks, useStat, shouldSkip, true); } function readOptions(options) { return { pattern: options.pattern, dot: !!options.dot, noglobstar: !!options.noglobstar, matchBase: !!options.matchBase, nocase: !!options.nocase, ignore: options.ignore, skip: options.skip, follow: !!options.follow, stat: !!options.stat, nodir: !!options.nodir, mark: !!options.mark, silent: !!options.silent, absolute: !!options.absolute }; } class ReaddirGlob extends EventEmitter { constructor(cwd, options, cb) { super(); if (typeof options === 'function') { cb = options; options = null; } this.options = readOptions(options || {}); this.matchers = []; if (this.options.pattern) { const matchers = Array.isArray(this.options.pattern) ? this.options.pattern : [this.options.pattern]; this.matchers = matchers.map(m => new Minimatch(m, { dot: this.options.dot, noglobstar: this.options.noglobstar, matchBase: this.options.matchBase, nocase: this.options.nocase }) ); } this.ignoreMatchers = []; if (this.options.ignore) { const ignorePatterns = Array.isArray(this.options.ignore) ? this.options.ignore : [this.options.ignore]; this.ignoreMatchers = ignorePatterns.map(ignore => new Minimatch(ignore, { dot: true }) ); } this.skipMatchers = []; if (this.options.skip) { const skipPatterns = Array.isArray(this.options.skip) ? this.options.skip : [this.options.skip]; this.skipMatchers = skipPatterns.map(skip => new Minimatch(skip, { dot: true }) ); } this.iterator = explore(resolve(cwd || '.'), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this)); this.paused = false; this.inactive = false; this.aborted = false; if (cb) { this._matches = []; this.on('match', match => this._matches.push(this.options.absolute ? match.absolute : match.relative)); this.on('error', err => cb(err)); this.on('end', () => cb(null, this._matches)); } setTimeout(() => this._next(), 0); } _shouldSkipDirectory(relative) { //console.log(relative, this.skipMatchers.some(m => m.match(relative))); return this.skipMatchers.some(m => m.match(relative)); } _fileMatches(relative, isDirectory) { const file = relative + (isDirectory ? '/' : ''); return (this.matchers.length === 0 || this.matchers.some(m => m.match(file))) && !this.ignoreMatchers.some(m => m.match(file)) && (!this.options.nodir || !isDirectory); } _next() { if (!this.paused && !this.aborted) { this.iterator.next() .then((obj) => { if (!obj.done) { const isDirectory = obj.value.stats.isDirectory(); if (this._fileMatches(obj.value.relative, isDirectory)) { let relative = obj.value.relative; let absolute = obj.value.absolute; if (this.options.mark && isDirectory) { relative += '/'; absolute += '/'; } if (this.options.stat) { this.emit('match', { relative, absolute, stat: obj.value.stats }); } else { this.emit('match', { relative, absolute }); } } this._next(this.iterator); } else { this.emit('end'); } }) .catch((err) => { this.abort(); this.emit('error', err); if (!err.code && !this.options.silent) { console.error(err); } }); } else { this.inactive = true; } } abort() { this.aborted = true; } pause() { this.paused = true; } resume() { this.paused = false; if (this.inactive) { this.inactive = false; this._next(); } } } function readdirGlob(pattern, options, cb) { return new ReaddirGlob(pattern, options, cb); } readdirGlob.ReaddirGlob = ReaddirGlob;