UNPKG

readdir-glob

Version:

Recursive fs.readdir with streaming API and glob filtering.

209 lines 7.67 kB
import * as fs from 'fs'; import { EventEmitter } from 'events'; import { Minimatch } from 'minimatch'; import { resolve } from '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 getStat(file, followSymlinks) { return new Promise((resolve) => { 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(getStat(file, false)); } else { resolve(null); } break; default: resolve(null); break; } } else { resolve(stats); } }); }); } async function* exploreWalkAsync(dir, path, followSymlinks, useStat, shouldSkip, strict) { var _a; let files = await readdir(path + dir, strict); for (const file of files) { let name = file.name; const filename = dir + '/' + name; const relative = filename.slice(1); // Remove the leading / const absolute = path + '/' + relative; let stat = file; if (useStat || followSymlinks) { stat = (_a = await getStat(absolute, followSymlinks)) !== null && _a !== void 0 ? _a : stat; } if (stat.isDirectory()) { if (!shouldSkip(relative)) { yield { relative, absolute, stat }; yield* exploreWalkAsync(filename, path, followSymlinks, useStat, shouldSkip, false); } } else { yield { relative, absolute, stat }; } } } 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 }; } export class ReaddirGlob extends EventEmitter { constructor(cwd, options, cb) { super(); if (typeof options === 'function') { cb = options; options = undefined; } 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) { const nonNullCb = cb; const matches = []; this.on('match', match => matches.push(this.options.absolute ? match.absolute : match.relative)); this.on('error', err => nonNullCb(err)); this.on('end', () => nonNullCb(null, matches)); } setTimeout(() => this._next()); } _shouldSkipDirectory(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.stat.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.stat }); } else { this.emit('match', { relative, absolute }); } } this._next(); } 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(); } } } export const readdirGlob = (pattern, options, cb) => new ReaddirGlob(pattern, options, cb); readdirGlob.ReaddirGlob = ReaddirGlob; //# sourceMappingURL=index.js.map