| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 | 'use strict';const fs = require('fs');const { Readable } = require('stream');const sysPath = require('path');const { promisify } = require('util');const picomatch = require('picomatch');const readdir = promisify(fs.readdir);const stat = promisify(fs.stat);const lstat = promisify(fs.lstat);const realpath = promisify(fs.realpath);/** * @typedef {Object} EntryInfo * @property {String} path * @property {String} fullPath * @property {fs.Stats=} stats * @property {fs.Dirent=} dirent * @property {String} basename */const BANG = '!';const RECURSIVE_ERROR_CODE = 'READDIRP_RECURSIVE_ERROR';const NORMAL_FLOW_ERRORS = new Set(['ENOENT', 'EPERM', 'EACCES', 'ELOOP', RECURSIVE_ERROR_CODE]);const FILE_TYPE = 'files';const DIR_TYPE = 'directories';const FILE_DIR_TYPE = 'files_directories';const EVERYTHING_TYPE = 'all';const ALL_TYPES = [FILE_TYPE, DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE];const isNormalFlowError = error => NORMAL_FLOW_ERRORS.has(error.code);const [maj, min] = process.versions.node.split('.').slice(0, 2).map(n => Number.parseInt(n, 10));const wantBigintFsStats = process.platform === 'win32' && (maj > 10 || (maj === 10 && min >= 5));const normalizeFilter = filter => {  if (filter === undefined) return;  if (typeof filter === 'function') return filter;  if (typeof filter === 'string') {    const glob = picomatch(filter.trim());    return entry => glob(entry.basename);  }  if (Array.isArray(filter)) {    const positive = [];    const negative = [];    for (const item of filter) {      const trimmed = item.trim();      if (trimmed.charAt(0) === BANG) {        negative.push(picomatch(trimmed.slice(1)));      } else {        positive.push(picomatch(trimmed));      }    }    if (negative.length > 0) {      if (positive.length > 0) {        return entry =>          positive.some(f => f(entry.basename)) && !negative.some(f => f(entry.basename));      }      return entry => !negative.some(f => f(entry.basename));    }    return entry => positive.some(f => f(entry.basename));  }};class ReaddirpStream extends Readable {  static get defaultOptions() {    return {      root: '.',      /* eslint-disable no-unused-vars */      fileFilter: (path) => true,      directoryFilter: (path) => true,      /* eslint-enable no-unused-vars */      type: FILE_TYPE,      lstat: false,      depth: 2147483648,      alwaysStat: false    };  }  constructor(options = {}) {    super({      objectMode: true,      autoDestroy: true,      highWaterMark: options.highWaterMark || 4096    });    const opts = { ...ReaddirpStream.defaultOptions, ...options };    const { root, type } = opts;    this._fileFilter = normalizeFilter(opts.fileFilter);    this._directoryFilter = normalizeFilter(opts.directoryFilter);    const statMethod = opts.lstat ? lstat : stat;    // Use bigint stats if it's windows and stat() supports options (node 10+).    if (wantBigintFsStats) {      this._stat = path => statMethod(path, { bigint: true });    } else {      this._stat = statMethod;    }    this._maxDepth = opts.depth;    this._wantsDir = [DIR_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE].includes(type);    this._wantsFile = [FILE_TYPE, FILE_DIR_TYPE, EVERYTHING_TYPE].includes(type);    this._wantsEverything = type === EVERYTHING_TYPE;    this._root = sysPath.resolve(root);    this._isDirent = ('Dirent' in fs) && !opts.alwaysStat;    this._statsProp = this._isDirent ? 'dirent' : 'stats';    this._rdOptions = { encoding: 'utf8', withFileTypes: this._isDirent };    // Launch stream with one parent, the root dir.    this.parents = [this._exploreDir(root, 1)];    this.reading = false;    this.parent = undefined;  }  async _read(batch) {    if (this.reading) return;    this.reading = true;    try {      while (!this.destroyed && batch > 0) {        const { path, depth, files = [] } = this.parent || {};        if (files.length > 0) {          const slice = files.splice(0, batch).map(dirent => this._formatEntry(dirent, path));          for (const entry of await Promise.all(slice)) {            if (this.destroyed) return;            const entryType = await this._getEntryType(entry);            if (entryType === 'directory' && this._directoryFilter(entry)) {              if (depth <= this._maxDepth) {                this.parents.push(this._exploreDir(entry.fullPath, depth + 1));              }              if (this._wantsDir) {                this.push(entry);                batch--;              }            } else if ((entryType === 'file' || this._includeAsFile(entry)) && this._fileFilter(entry)) {              if (this._wantsFile) {                this.push(entry);                batch--;              }            }          }        } else {          const parent = this.parents.pop();          if (!parent) {            this.push(null);            break;          }          this.parent = await parent;          if (this.destroyed) return;        }      }    } catch (error) {      this.destroy(error);    } finally {      this.reading = false;    }  }  async _exploreDir(path, depth) {    let files;    try {      files = await readdir(path, this._rdOptions);    } catch (error) {      this._onError(error);    }    return { files, depth, path };  }  async _formatEntry(dirent, path) {    let entry;    try {      const basename = this._isDirent ? dirent.name : dirent;      const fullPath = sysPath.resolve(sysPath.join(path, basename));      entry = { path: sysPath.relative(this._root, fullPath), fullPath, basename };      entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);    } catch (err) {      this._onError(err);    }    return entry;  }  _onError(err) {    if (isNormalFlowError(err) && !this.destroyed) {      this.emit('warn', err);    } else {      this.destroy(err);    }  }  async _getEntryType(entry) {    // entry may be undefined, because a warning or an error were emitted    // and the statsProp is undefined    const stats = entry && entry[this._statsProp];    if (!stats) {      return;    }    if (stats.isFile()) {      return 'file';    }    if (stats.isDirectory()) {      return 'directory';    }    if (stats && stats.isSymbolicLink()) {      const full = entry.fullPath;      try {        const entryRealPath = await realpath(full);        const entryRealPathStats = await lstat(entryRealPath);        if (entryRealPathStats.isFile()) {          return 'file';        }        if (entryRealPathStats.isDirectory()) {          const len = entryRealPath.length;          if (full.startsWith(entryRealPath) && full.substr(len, 1) === sysPath.sep) {            const recursiveError = new Error(              `Circular symlink detected: "${full}" points to "${entryRealPath}"`            );            recursiveError.code = RECURSIVE_ERROR_CODE;            return this._onError(recursiveError);          }          return 'directory';        }      } catch (error) {        this._onError(error);      }    }  }  _includeAsFile(entry) {    const stats = entry && entry[this._statsProp];    return stats && this._wantsEverything && !stats.isDirectory();  }}/** * @typedef {Object} ReaddirpArguments * @property {Function=} fileFilter * @property {Function=} directoryFilter * @property {String=} type * @property {Number=} depth * @property {String=} root * @property {Boolean=} lstat * @property {Boolean=} bigint *//** * Main function which ends up calling readdirRec and reads all files and directories in given root recursively. * @param {String} root Root directory * @param {ReaddirpArguments=} options Options to specify root (start directory), filters and recursion depth */const readdirp = (root, options = {}) => {  let type = options.entryType || options.type;  if (type === 'both') type = FILE_DIR_TYPE; // backwards-compatibility  if (type) options.type = type;  if (!root) {    throw new Error('readdirp: root argument is required. Usage: readdirp(root, options)');  } else if (typeof root !== 'string') {    throw new TypeError('readdirp: root argument must be a string. Usage: readdirp(root, options)');  } else if (type && !ALL_TYPES.includes(type)) {    throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(', ')}`);  }  options.root = root;  return new ReaddirpStream(options);};const readdirpPromise = (root, options = {}) => {  return new Promise((resolve, reject) => {    const files = [];    readdirp(root, options)      .on('data', entry => files.push(entry))      .on('end', () => resolve(files))      .on('error', error => reject(error));  });};readdirp.promise = readdirpPromise;readdirp.ReaddirpStream = ReaddirpStream;readdirp.default = readdirp;module.exports = readdirp;
 |