You've already forked setup-python
							
							
				mirror of
				https://github.com/actions/setup-python.git
				synced 2025-11-04 00:36:40 +07:00 
			
		
		
		
	Initial pass
This commit is contained in:
		
							
								
								
									
										76
									
								
								node_modules/sane/src/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								node_modules/sane/src/cli.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const sane = require('../');
 | 
			
		||||
const argv = require('minimist')(process.argv.slice(2));
 | 
			
		||||
const execshell = require('exec-sh');
 | 
			
		||||
 | 
			
		||||
if (argv._.length === 0) {
 | 
			
		||||
  const msg =
 | 
			
		||||
    'Usage: sane <command> [...directory] [--glob=<filePattern>] ' +
 | 
			
		||||
    '[--ignored=<filePattern>] [--poll] [--watchman] [--watchman-path=<watchmanBinaryPath>] [--dot] ' +
 | 
			
		||||
    '[--wait=<seconds>] [--only-changes] [--quiet]';
 | 
			
		||||
  console.error(msg);
 | 
			
		||||
  process.exit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const opts = {};
 | 
			
		||||
const command = argv._[0];
 | 
			
		||||
const dir = argv._[1] || process.cwd();
 | 
			
		||||
const waitTime = Number(argv.wait || argv.w);
 | 
			
		||||
const dot = argv.dot || argv.d;
 | 
			
		||||
const glob = argv.glob || argv.g;
 | 
			
		||||
const ignored = argv.ignored || argv.i;
 | 
			
		||||
const poll = argv.poll || argv.p;
 | 
			
		||||
const watchman = argv.watchman || argv.w;
 | 
			
		||||
const watchmanPath = argv['watchman-path'];
 | 
			
		||||
const onlyChanges = argv['only-changes'] | argv.o;
 | 
			
		||||
const quiet = argv.quiet | argv.q;
 | 
			
		||||
 | 
			
		||||
if (dot) {
 | 
			
		||||
  opts.dot = true;
 | 
			
		||||
}
 | 
			
		||||
if (glob) {
 | 
			
		||||
  opts.glob = glob;
 | 
			
		||||
}
 | 
			
		||||
if (ignored) {
 | 
			
		||||
  opts.ignored = ignored;
 | 
			
		||||
}
 | 
			
		||||
if (poll) {
 | 
			
		||||
  opts.poll = true;
 | 
			
		||||
}
 | 
			
		||||
if (watchman) {
 | 
			
		||||
  opts.watchman = true;
 | 
			
		||||
}
 | 
			
		||||
if (watchmanPath) {
 | 
			
		||||
  opts.watchmanPath = watchmanPath;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let wait = false;
 | 
			
		||||
const watcher = sane(dir, opts);
 | 
			
		||||
 | 
			
		||||
watcher.on('ready', function() {
 | 
			
		||||
  if (!quiet) {
 | 
			
		||||
    console.log('Watching: ', dir + '/' + (opts.glob || ''));
 | 
			
		||||
  }
 | 
			
		||||
  if (!onlyChanges) {
 | 
			
		||||
    execshell(command);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watcher.on('change', function(filepath) {
 | 
			
		||||
  if (wait) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!quiet) {
 | 
			
		||||
    console.log('Change detected in:', filepath);
 | 
			
		||||
  }
 | 
			
		||||
  execshell(command);
 | 
			
		||||
 | 
			
		||||
  if (waitTime > 0) {
 | 
			
		||||
    wait = true;
 | 
			
		||||
    setTimeout(function() {
 | 
			
		||||
      wait = false;
 | 
			
		||||
    }, waitTime * 1000);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										110
									
								
								node_modules/sane/src/common.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								node_modules/sane/src/common.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,110 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const walker = require('walker');
 | 
			
		||||
const anymatch = require('anymatch');
 | 
			
		||||
const micromatch = require('micromatch');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const platform = require('os').platform();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
exports.DEFAULT_DELAY = 100;
 | 
			
		||||
exports.CHANGE_EVENT = 'change';
 | 
			
		||||
exports.DELETE_EVENT = 'delete';
 | 
			
		||||
exports.ADD_EVENT = 'add';
 | 
			
		||||
exports.ALL_EVENT = 'all';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Assigns options to the watcher.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {NodeWatcher|PollWatcher|WatchmanWatcher} watcher
 | 
			
		||||
 * @param {?object} opts
 | 
			
		||||
 * @return {boolean}
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
exports.assignOptions = function(watcher, opts) {
 | 
			
		||||
  opts = opts || {};
 | 
			
		||||
  watcher.globs = opts.glob || [];
 | 
			
		||||
  watcher.dot = opts.dot || false;
 | 
			
		||||
  watcher.ignored = opts.ignored || false;
 | 
			
		||||
 | 
			
		||||
  if (!Array.isArray(watcher.globs)) {
 | 
			
		||||
    watcher.globs = [watcher.globs];
 | 
			
		||||
  }
 | 
			
		||||
  watcher.hasIgnore =
 | 
			
		||||
    Boolean(opts.ignored) && !(Array.isArray(opts) && opts.length > 0);
 | 
			
		||||
  watcher.doIgnore = opts.ignored ? anymatch(opts.ignored) : () => false;
 | 
			
		||||
 | 
			
		||||
  if (opts.watchman && opts.watchmanPath) {
 | 
			
		||||
    watcher.watchmanPath = opts.watchmanPath;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return opts;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks a file relative path against the globs array.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {array} globs
 | 
			
		||||
 * @param {string} relativePath
 | 
			
		||||
 * @return {boolean}
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
exports.isFileIncluded = function(globs, dot, doIgnore, relativePath) {
 | 
			
		||||
  if (doIgnore(relativePath)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return globs.length
 | 
			
		||||
    ? micromatch.some(relativePath, globs, { dot: dot })
 | 
			
		||||
    : dot || micromatch.some(relativePath, '**/*');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Traverse a directory recursively calling `callback` on every directory.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} dir
 | 
			
		||||
 * @param {function} dirCallback
 | 
			
		||||
 * @param {function} fileCallback
 | 
			
		||||
 * @param {function} endCallback
 | 
			
		||||
 * @param {*} ignored
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
exports.recReaddir = function(
 | 
			
		||||
  dir,
 | 
			
		||||
  dirCallback,
 | 
			
		||||
  fileCallback,
 | 
			
		||||
  endCallback,
 | 
			
		||||
  errorCallback,
 | 
			
		||||
  ignored
 | 
			
		||||
) {
 | 
			
		||||
  walker(dir)
 | 
			
		||||
    .filterDir(currentDir => !anymatch(ignored, currentDir))
 | 
			
		||||
    .on('dir', normalizeProxy(dirCallback))
 | 
			
		||||
    .on('file', normalizeProxy(fileCallback))
 | 
			
		||||
    .on('error', errorCallback)
 | 
			
		||||
    .on('end', () => {
 | 
			
		||||
      if (platform === 'win32') {
 | 
			
		||||
        setTimeout(endCallback, 1000);
 | 
			
		||||
      } else {
 | 
			
		||||
        endCallback();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a callback that when called will normalize a path and call the
 | 
			
		||||
 * original callback
 | 
			
		||||
 *
 | 
			
		||||
 * @param {function} callback
 | 
			
		||||
 * @return {function}
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function normalizeProxy(callback) {
 | 
			
		||||
  return (filepath, stats) => callback(path.normalize(filepath), stats);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										398
									
								
								node_modules/sane/src/node_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								node_modules/sane/src/node_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,398 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const common = require('./common');
 | 
			
		||||
const platform = require('os').platform();
 | 
			
		||||
const EventEmitter = require('events').EventEmitter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const DEFAULT_DELAY = common.DEFAULT_DELAY;
 | 
			
		||||
const CHANGE_EVENT = common.CHANGE_EVENT;
 | 
			
		||||
const DELETE_EVENT = common.DELETE_EVENT;
 | 
			
		||||
const ADD_EVENT = common.ADD_EVENT;
 | 
			
		||||
const ALL_EVENT = common.ALL_EVENT;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Export `NodeWatcher` class.
 | 
			
		||||
 * Watches `dir`.
 | 
			
		||||
 *
 | 
			
		||||
 * @class NodeWatcher
 | 
			
		||||
 * @param {String} dir
 | 
			
		||||
 * @param {Object} opts
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = class NodeWatcher extends EventEmitter {
 | 
			
		||||
  constructor(dir, opts) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    common.assignOptions(this, opts);
 | 
			
		||||
 | 
			
		||||
    this.watched = Object.create(null);
 | 
			
		||||
    this.changeTimers = Object.create(null);
 | 
			
		||||
    this.dirRegistery = Object.create(null);
 | 
			
		||||
    this.root = path.resolve(dir);
 | 
			
		||||
    this.watchdir = this.watchdir.bind(this);
 | 
			
		||||
    this.register = this.register.bind(this);
 | 
			
		||||
    this.checkedEmitError = this.checkedEmitError.bind(this);
 | 
			
		||||
 | 
			
		||||
    this.watchdir(this.root);
 | 
			
		||||
    common.recReaddir(
 | 
			
		||||
      this.root,
 | 
			
		||||
      this.watchdir,
 | 
			
		||||
      this.register,
 | 
			
		||||
      this.emit.bind(this, 'ready'),
 | 
			
		||||
      this.checkedEmitError,
 | 
			
		||||
      this.ignored
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Register files that matches our globs to know what to type of event to
 | 
			
		||||
   * emit in the future.
 | 
			
		||||
   *
 | 
			
		||||
   * Registery looks like the following:
 | 
			
		||||
   *
 | 
			
		||||
   *  dirRegister => Map {
 | 
			
		||||
   *    dirpath => Map {
 | 
			
		||||
   *       filename => true
 | 
			
		||||
   *    }
 | 
			
		||||
   *  }
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} filepath
 | 
			
		||||
   * @return {boolean} whether or not we have registered the file.
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  register(filepath) {
 | 
			
		||||
    let relativePath = path.relative(this.root, filepath);
 | 
			
		||||
    if (
 | 
			
		||||
      !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
 | 
			
		||||
    ) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let dir = path.dirname(filepath);
 | 
			
		||||
    if (!this.dirRegistery[dir]) {
 | 
			
		||||
      this.dirRegistery[dir] = Object.create(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let filename = path.basename(filepath);
 | 
			
		||||
    this.dirRegistery[dir][filename] = true;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes a file from the registery.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} filepath
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  unregister(filepath) {
 | 
			
		||||
    let dir = path.dirname(filepath);
 | 
			
		||||
    if (this.dirRegistery[dir]) {
 | 
			
		||||
      let filename = path.basename(filepath);
 | 
			
		||||
      delete this.dirRegistery[dir][filename];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Removes a dir from the registery.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dirpath
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  unregisterDir(dirpath) {
 | 
			
		||||
    if (this.dirRegistery[dirpath]) {
 | 
			
		||||
      delete this.dirRegistery[dirpath];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Checks if a file or directory exists in the registery.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} fullpath
 | 
			
		||||
   * @return {boolean}
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  registered(fullpath) {
 | 
			
		||||
    let dir = path.dirname(fullpath);
 | 
			
		||||
    return (
 | 
			
		||||
      this.dirRegistery[fullpath] ||
 | 
			
		||||
      (this.dirRegistery[dir] &&
 | 
			
		||||
        this.dirRegistery[dir][path.basename(fullpath)])
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Emit "error" event if it's not an ignorable event
 | 
			
		||||
   *
 | 
			
		||||
   * @param error
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  checkedEmitError(error) {
 | 
			
		||||
    if (!isIgnorableFileError(error)) {
 | 
			
		||||
      this.emit('error', error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Watch a directory.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dir
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  watchdir(dir) {
 | 
			
		||||
    if (this.watched[dir]) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let watcher = fs.watch(
 | 
			
		||||
      dir,
 | 
			
		||||
      { persistent: true },
 | 
			
		||||
      this.normalizeChange.bind(this, dir)
 | 
			
		||||
    );
 | 
			
		||||
    this.watched[dir] = watcher;
 | 
			
		||||
 | 
			
		||||
    watcher.on('error', this.checkedEmitError);
 | 
			
		||||
 | 
			
		||||
    if (this.root !== dir) {
 | 
			
		||||
      this.register(dir);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop watching a directory.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dir
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  stopWatching(dir) {
 | 
			
		||||
    if (this.watched[dir]) {
 | 
			
		||||
      this.watched[dir].close();
 | 
			
		||||
      delete this.watched[dir];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * End watching.
 | 
			
		||||
   *
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  close(callback) {
 | 
			
		||||
    Object.keys(this.watched).forEach(this.stopWatching, this);
 | 
			
		||||
    this.removeAllListeners();
 | 
			
		||||
    if (typeof callback === 'function') {
 | 
			
		||||
      setImmediate(callback.bind(null, null, true));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * On some platforms, as pointed out on the fs docs (most likely just win32)
 | 
			
		||||
   * the file argument might be missing from the fs event. Try to detect what
 | 
			
		||||
   * change by detecting if something was deleted or the most recent file change.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dir
 | 
			
		||||
   * @param {string} event
 | 
			
		||||
   * @param {string} file
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  detectChangedFile(dir, event, callback) {
 | 
			
		||||
    if (!this.dirRegistery[dir]) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let found = false;
 | 
			
		||||
    let closest = { mtime: 0 };
 | 
			
		||||
    let c = 0;
 | 
			
		||||
    Object.keys(this.dirRegistery[dir]).forEach(function(file, i, arr) {
 | 
			
		||||
      fs.lstat(
 | 
			
		||||
        path.join(dir, file),
 | 
			
		||||
        function(error, stat) {
 | 
			
		||||
          if (found) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (error) {
 | 
			
		||||
            if (isIgnorableFileError(error)) {
 | 
			
		||||
              found = true;
 | 
			
		||||
              callback(file);
 | 
			
		||||
            } else {
 | 
			
		||||
              this.emit('error', error);
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            if (stat.mtime > closest.mtime) {
 | 
			
		||||
              stat.file = file;
 | 
			
		||||
              closest = stat;
 | 
			
		||||
            }
 | 
			
		||||
            if (arr.length === ++c) {
 | 
			
		||||
              callback(closest.file);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }.bind(this)
 | 
			
		||||
      );
 | 
			
		||||
    }, this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Normalize fs events and pass it on to be processed.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dir
 | 
			
		||||
   * @param {string} event
 | 
			
		||||
   * @param {string} file
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  normalizeChange(dir, event, file) {
 | 
			
		||||
    if (!file) {
 | 
			
		||||
      this.detectChangedFile(
 | 
			
		||||
        dir,
 | 
			
		||||
        event,
 | 
			
		||||
        function(actualFile) {
 | 
			
		||||
          if (actualFile) {
 | 
			
		||||
            this.processChange(dir, event, actualFile);
 | 
			
		||||
          }
 | 
			
		||||
        }.bind(this)
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      this.processChange(dir, event, path.normalize(file));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Process changes.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} dir
 | 
			
		||||
   * @param {string} event
 | 
			
		||||
   * @param {string} file
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  processChange(dir, event, file) {
 | 
			
		||||
    let fullPath = path.join(dir, file);
 | 
			
		||||
    let relativePath = path.join(path.relative(this.root, dir), file);
 | 
			
		||||
 | 
			
		||||
    fs.lstat(
 | 
			
		||||
      fullPath,
 | 
			
		||||
      function(error, stat) {
 | 
			
		||||
        if (error && error.code !== 'ENOENT') {
 | 
			
		||||
          this.emit('error', error);
 | 
			
		||||
        } else if (!error && stat.isDirectory()) {
 | 
			
		||||
          // win32 emits usless change events on dirs.
 | 
			
		||||
          if (event !== 'change') {
 | 
			
		||||
            this.watchdir(fullPath);
 | 
			
		||||
            if (
 | 
			
		||||
              common.isFileIncluded(
 | 
			
		||||
                this.globs,
 | 
			
		||||
                this.dot,
 | 
			
		||||
                this.doIgnore,
 | 
			
		||||
                relativePath
 | 
			
		||||
              )
 | 
			
		||||
            ) {
 | 
			
		||||
              this.emitEvent(ADD_EVENT, relativePath, stat);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          let registered = this.registered(fullPath);
 | 
			
		||||
          if (error && error.code === 'ENOENT') {
 | 
			
		||||
            this.unregister(fullPath);
 | 
			
		||||
            this.stopWatching(fullPath);
 | 
			
		||||
            this.unregisterDir(fullPath);
 | 
			
		||||
            if (registered) {
 | 
			
		||||
              this.emitEvent(DELETE_EVENT, relativePath);
 | 
			
		||||
            }
 | 
			
		||||
          } else if (registered) {
 | 
			
		||||
            this.emitEvent(CHANGE_EVENT, relativePath, stat);
 | 
			
		||||
          } else {
 | 
			
		||||
            if (this.register(fullPath)) {
 | 
			
		||||
              this.emitEvent(ADD_EVENT, relativePath, stat);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }.bind(this)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Triggers a 'change' event after debounding it to take care of duplicate
 | 
			
		||||
   * events on os x.
 | 
			
		||||
   *
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  emitEvent(type, file, stat) {
 | 
			
		||||
    let key = type + '-' + file;
 | 
			
		||||
    let addKey = ADD_EVENT + '-' + file;
 | 
			
		||||
    if (type === CHANGE_EVENT && this.changeTimers[addKey]) {
 | 
			
		||||
      // Ignore the change event that is immediately fired after an add event.
 | 
			
		||||
      // (This happens on Linux).
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    clearTimeout(this.changeTimers[key]);
 | 
			
		||||
    this.changeTimers[key] = setTimeout(
 | 
			
		||||
      function() {
 | 
			
		||||
        delete this.changeTimers[key];
 | 
			
		||||
        if (type === ADD_EVENT && stat.isDirectory()) {
 | 
			
		||||
          // Recursively emit add events and watch for sub-files/folders
 | 
			
		||||
          common.recReaddir(
 | 
			
		||||
            path.resolve(this.root, file),
 | 
			
		||||
            function emitAddDir(dir, stats) {
 | 
			
		||||
              this.watchdir(dir);
 | 
			
		||||
              this.rawEmitEvent(
 | 
			
		||||
                ADD_EVENT,
 | 
			
		||||
                path.relative(this.root, dir),
 | 
			
		||||
                stats
 | 
			
		||||
              );
 | 
			
		||||
            }.bind(this),
 | 
			
		||||
            function emitAddFile(file, stats) {
 | 
			
		||||
              this.register(file);
 | 
			
		||||
              this.rawEmitEvent(
 | 
			
		||||
                ADD_EVENT,
 | 
			
		||||
                path.relative(this.root, file),
 | 
			
		||||
                stats
 | 
			
		||||
              );
 | 
			
		||||
            }.bind(this),
 | 
			
		||||
            function endCallback() {},
 | 
			
		||||
            this.checkedEmitError,
 | 
			
		||||
            this.ignored
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          this.rawEmitEvent(type, file, stat);
 | 
			
		||||
        }
 | 
			
		||||
      }.bind(this),
 | 
			
		||||
      DEFAULT_DELAY
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Actually emit the events
 | 
			
		||||
   */
 | 
			
		||||
  rawEmitEvent(type, file, stat) {
 | 
			
		||||
    this.emit(type, file, this.root, stat);
 | 
			
		||||
    this.emit(ALL_EVENT, type, file, this.root, stat);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * Determine if a given FS error can be ignored
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
function isIgnorableFileError(error) {
 | 
			
		||||
  return (
 | 
			
		||||
    error.code === 'ENOENT' ||
 | 
			
		||||
    // Workaround Windows node issue #4337.
 | 
			
		||||
    (error.code === 'EPERM' && platform === 'win32')
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								node_modules/sane/src/poll_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								node_modules/sane/src/poll_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const watch = require('@cnakazawa/watch');
 | 
			
		||||
const common = require('./common');
 | 
			
		||||
const EventEmitter = require('events').EventEmitter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const DEFAULT_DELAY = common.DEFAULT_DELAY;
 | 
			
		||||
const CHANGE_EVENT = common.CHANGE_EVENT;
 | 
			
		||||
const DELETE_EVENT = common.DELETE_EVENT;
 | 
			
		||||
const ADD_EVENT = common.ADD_EVENT;
 | 
			
		||||
const ALL_EVENT = common.ALL_EVENT;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Export `PollWatcher` class.
 | 
			
		||||
 * Watches `dir`.
 | 
			
		||||
 *
 | 
			
		||||
 * @class PollWatcher
 | 
			
		||||
 * @param String dir
 | 
			
		||||
 * @param {Object} opts
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = class PollWatcher extends EventEmitter {
 | 
			
		||||
  constructor(dir, opts) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    opts = common.assignOptions(this, opts);
 | 
			
		||||
 | 
			
		||||
    this.watched = Object.create(null);
 | 
			
		||||
    this.root = path.resolve(dir);
 | 
			
		||||
 | 
			
		||||
    watch.createMonitor(
 | 
			
		||||
      this.root,
 | 
			
		||||
      {
 | 
			
		||||
        interval: (opts.interval || DEFAULT_DELAY) / 1000,
 | 
			
		||||
        filter: this.filter.bind(this),
 | 
			
		||||
      },
 | 
			
		||||
      this.init.bind(this)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Given a fullpath of a file or directory check if we need to watch it.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {string} filepath
 | 
			
		||||
   * @param {object} stat
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  filter(filepath, stat) {
 | 
			
		||||
    return (
 | 
			
		||||
      stat.isDirectory() ||
 | 
			
		||||
      common.isFileIncluded(
 | 
			
		||||
        this.globs,
 | 
			
		||||
        this.dot,
 | 
			
		||||
        this.doIgnore,
 | 
			
		||||
        path.relative(this.root, filepath)
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Initiate the polling file watcher with the event emitter passed from
 | 
			
		||||
   * `watch.watchTree`.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {EventEmitter} monitor
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  init(monitor) {
 | 
			
		||||
    this.watched = monitor.files;
 | 
			
		||||
    monitor.on('changed', this.emitEvent.bind(this, CHANGE_EVENT));
 | 
			
		||||
    monitor.on('removed', this.emitEvent.bind(this, DELETE_EVENT));
 | 
			
		||||
    monitor.on('created', this.emitEvent.bind(this, ADD_EVENT));
 | 
			
		||||
    // 1 second wait because mtime is second-based.
 | 
			
		||||
    setTimeout(this.emit.bind(this, 'ready'), 1000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Transform and emit an event comming from the poller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {EventEmitter} monitor
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  emitEvent(type, file, stat) {
 | 
			
		||||
    file = path.relative(this.root, file);
 | 
			
		||||
 | 
			
		||||
    if (type === DELETE_EVENT) {
 | 
			
		||||
      // Matching the non-polling API
 | 
			
		||||
      stat = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.emit(type, file, this.root, stat);
 | 
			
		||||
    this.emit(ALL_EVENT, type, file, this.root, stat);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * End watching.
 | 
			
		||||
   *
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  close(callback) {
 | 
			
		||||
    Object.keys(this.watched).forEach(filepath => fs.unwatchFile(filepath));
 | 
			
		||||
    this.removeAllListeners();
 | 
			
		||||
    if (typeof callback === 'function') {
 | 
			
		||||
      setImmediate(callback.bind(null, null, true));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										51
									
								
								node_modules/sane/src/utils/recrawl-warning-dedupe.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								node_modules/sane/src/utils/recrawl-warning-dedupe.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
class RecrawlWarning {
 | 
			
		||||
  constructor(root, count) {
 | 
			
		||||
    this.root = root;
 | 
			
		||||
    this.count = count;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static findByRoot(root) {
 | 
			
		||||
    for (let i = 0; i < this.RECRAWL_WARNINGS.length; i++) {
 | 
			
		||||
      let warning = this.RECRAWL_WARNINGS[i];
 | 
			
		||||
      if (warning.root === root) {
 | 
			
		||||
        return warning;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static isRecrawlWarningDupe(warningMessage) {
 | 
			
		||||
    if (typeof warningMessage !== 'string') {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    let match = warningMessage.match(this.REGEXP);
 | 
			
		||||
    if (!match) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    let count = Number(match[1]);
 | 
			
		||||
    let root = match[2];
 | 
			
		||||
 | 
			
		||||
    let warning = this.findByRoot(root);
 | 
			
		||||
 | 
			
		||||
    if (warning) {
 | 
			
		||||
      // only keep the highest count, assume count to either stay the same or
 | 
			
		||||
      // increase.
 | 
			
		||||
      if (warning.count >= count) {
 | 
			
		||||
        return true;
 | 
			
		||||
      } else {
 | 
			
		||||
        // update the existing warning to the latest (highest) count
 | 
			
		||||
        warning.count = count;
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.RECRAWL_WARNINGS.push(new RecrawlWarning(root, count));
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RecrawlWarning.RECRAWL_WARNINGS = [];
 | 
			
		||||
RecrawlWarning.REGEXP = /Recrawled this watch (\d+) times, most recently because:\n([^:]+)/;
 | 
			
		||||
 | 
			
		||||
module.exports = RecrawlWarning;
 | 
			
		||||
							
								
								
									
										63
									
								
								node_modules/sane/src/watchexec_client.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								node_modules/sane/src/watchexec_client.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
/*
 | 
			
		||||
   This file is the executable run by watchexec
 | 
			
		||||
   when a change is detected.
 | 
			
		||||
 | 
			
		||||
   It will extract changes from the environment variables
 | 
			
		||||
   set by watchexec and write to stdout in a format
 | 
			
		||||
   readable by the file `../watchexec_watcher.js`.
 | 
			
		||||
*/
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { EOL } = require('os');
 | 
			
		||||
 | 
			
		||||
function withPrefixes(prefixes) {
 | 
			
		||||
  return function withPrefix(arr, i) {
 | 
			
		||||
    return arr.map(str => {
 | 
			
		||||
      return `${prefixes[i]} ${str}`;
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let allPrefixes = ['write', 'rename', 'remove', 'create'];
 | 
			
		||||
 | 
			
		||||
function extractChanges(context) {
 | 
			
		||||
  const {
 | 
			
		||||
    WATCHEXEC_COMMON_PATH,
 | 
			
		||||
    WATCHEXEC_WRITTEN_PATH,
 | 
			
		||||
    WATCHEXEC_RENAMED_PATH,
 | 
			
		||||
    WATCHEXEC_REMOVED_PATH,
 | 
			
		||||
    WATCHEXEC_CREATED_PATH,
 | 
			
		||||
  } = context;
 | 
			
		||||
 | 
			
		||||
  let events = [
 | 
			
		||||
    WATCHEXEC_WRITTEN_PATH,
 | 
			
		||||
    WATCHEXEC_RENAMED_PATH,
 | 
			
		||||
    WATCHEXEC_REMOVED_PATH,
 | 
			
		||||
    WATCHEXEC_CREATED_PATH,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  let currentPrefixes = events
 | 
			
		||||
    .map((l, i) => l && allPrefixes[i])
 | 
			
		||||
    .filter(Boolean);
 | 
			
		||||
 | 
			
		||||
  function toFullPath(arr) {
 | 
			
		||||
    return arr.map(path => (WATCHEXEC_COMMON_PATH || '') + path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let message = events
 | 
			
		||||
    .filter(Boolean)
 | 
			
		||||
    .map(str => str.split(':'))
 | 
			
		||||
    .map(toFullPath)
 | 
			
		||||
    .map(withPrefixes(currentPrefixes))
 | 
			
		||||
    .reduce((e, memo) => memo.concat(e), [])
 | 
			
		||||
    .join(EOL);
 | 
			
		||||
 | 
			
		||||
  return message;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (require.main === module) {
 | 
			
		||||
  let message = extractChanges(process.env);
 | 
			
		||||
  console.log(message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = extractChanges;
 | 
			
		||||
							
								
								
									
										116
									
								
								node_modules/sane/src/watchexec_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								node_modules/sane/src/watchexec_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const execa = require('execa');
 | 
			
		||||
 | 
			
		||||
const { statSync } = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const common = require('./common');
 | 
			
		||||
const EventEmitter = require('events').EventEmitter;
 | 
			
		||||
 | 
			
		||||
const { EOL } = require('os');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const CHANGE_EVENT = common.CHANGE_EVENT;
 | 
			
		||||
const DELETE_EVENT = common.DELETE_EVENT;
 | 
			
		||||
const ADD_EVENT = common.ADD_EVENT;
 | 
			
		||||
const ALL_EVENT = common.ALL_EVENT;
 | 
			
		||||
 | 
			
		||||
const typeMap = {
 | 
			
		||||
  rename: CHANGE_EVENT,
 | 
			
		||||
  write: CHANGE_EVENT,
 | 
			
		||||
  remove: DELETE_EVENT,
 | 
			
		||||
  create: ADD_EVENT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const messageRegexp = /(rename|write|remove|create)\s(.+)/;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages streams from subprocess and turns into sane events
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Stream} data
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
function _messageHandler(data) {
 | 
			
		||||
  data
 | 
			
		||||
    .toString()
 | 
			
		||||
    .split(EOL)
 | 
			
		||||
    .filter(str => str.trim().length)
 | 
			
		||||
    .filter(str => messageRegexp.test(str))
 | 
			
		||||
    .map(line => {
 | 
			
		||||
      const [, command, path] = [...line.match(messageRegexp)];
 | 
			
		||||
      return [command, path];
 | 
			
		||||
    })
 | 
			
		||||
    .forEach(([command, file]) => {
 | 
			
		||||
      let stat;
 | 
			
		||||
      const type = typeMap[command];
 | 
			
		||||
      if (type === DELETE_EVENT) {
 | 
			
		||||
        stat = null;
 | 
			
		||||
      } else {
 | 
			
		||||
        try {
 | 
			
		||||
          stat = statSync(file);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          // There is likely a delete coming down the pipe.
 | 
			
		||||
          if (e.code === 'ENOENT') {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          throw e;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.emitEvent(type, path.relative(this.root, file), stat);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Export `WatchexecWatcher` class.
 | 
			
		||||
 * Watches `dir`.
 | 
			
		||||
 *
 | 
			
		||||
 * @class WatchexecWatcher
 | 
			
		||||
 * @param String dir
 | 
			
		||||
 * @param {Object} opts
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
class WatchexecWatcher extends EventEmitter {
 | 
			
		||||
  constructor(dir, opts) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    common.assignOptions(this, opts);
 | 
			
		||||
 | 
			
		||||
    this.root = path.resolve(dir);
 | 
			
		||||
 | 
			
		||||
    this._process = execa(
 | 
			
		||||
      'watchexec',
 | 
			
		||||
      ['-n', '--', 'node', __dirname + '/watchexec_client.js'],
 | 
			
		||||
      { cwd: dir }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this._process.stdout.on('data', _messageHandler.bind(this));
 | 
			
		||||
    this._readyTimer = setTimeout(this.emit.bind(this, 'ready'), 1000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  close(callback) {
 | 
			
		||||
    clearTimeout(this._readyTimer);
 | 
			
		||||
    this.removeAllListeners();
 | 
			
		||||
    this._process && !this._process.killed && this._process.kill();
 | 
			
		||||
    if (typeof callback === 'function') {
 | 
			
		||||
      setImmediate(callback.bind(null, null, true));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Transform and emit an event comming from the poller.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {EventEmitter} monitor
 | 
			
		||||
   * @public
 | 
			
		||||
   */
 | 
			
		||||
  emitEvent(type, file, stat) {
 | 
			
		||||
    this.emit(type, file, this.root, stat);
 | 
			
		||||
    this.emit(ALL_EVENT, type, file, this.root, stat);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WatchexecWatcher._messageHandler = _messageHandler;
 | 
			
		||||
 | 
			
		||||
module.exports = WatchexecWatcher;
 | 
			
		||||
							
								
								
									
										525
									
								
								node_modules/sane/src/watchman_client.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										525
									
								
								node_modules/sane/src/watchman_client.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,525 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const watchman = require('fb-watchman');
 | 
			
		||||
const captureExit = require('capture-exit');
 | 
			
		||||
 | 
			
		||||
function values(obj) {
 | 
			
		||||
  return Object.keys(obj).map(key => obj[key]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Singleton that provides a public API for a connection to a watchman instance for 'sane'.
 | 
			
		||||
 * It tries to abstract/remove as much of the boilerplate processing as necessary
 | 
			
		||||
 * from WatchmanWatchers that use it. In particular, they have no idea whether
 | 
			
		||||
 * we're using 'watch-project' or 'watch', what the 'project root' is when
 | 
			
		||||
 * we internally use watch-project, whether a connection has been lost
 | 
			
		||||
 * and reestablished, etc. Also switched to doing things with promises and known-name
 | 
			
		||||
 * methods in WatchmanWatcher, so as much information as possible can be kept in
 | 
			
		||||
 * the WatchmanClient, ultimately making this the only object listening directly
 | 
			
		||||
 * to watchman.Client, then forwarding appropriately (via the known-name methods) to
 | 
			
		||||
 * the relevant WatchmanWatcher(s).
 | 
			
		||||
 *
 | 
			
		||||
 * Note: WatchmanWatcher also added a 'watchmanPath' option for use with the sane CLI.
 | 
			
		||||
 * Because of that, we actually need a map of watchman binary path to WatchmanClient instance.
 | 
			
		||||
 * That is set up in getInstance(). Once the WatchmanWatcher has a given client, it doesn't
 | 
			
		||||
 * change.
 | 
			
		||||
 *
 | 
			
		||||
 * @class WatchmanClient
 | 
			
		||||
 * @param String watchmanBinaryPath
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class WatchmanClient {
 | 
			
		||||
  constructor(watchmanBinaryPath) {
 | 
			
		||||
    captureExit.captureExit();
 | 
			
		||||
 | 
			
		||||
    // define/clear some local state. The properties will be initialized
 | 
			
		||||
    // in _handleClientAndCheck(). This is also called again in _onEnd when
 | 
			
		||||
    // trying to reestablish connection to watchman.
 | 
			
		||||
    this._clearLocalVars();
 | 
			
		||||
 | 
			
		||||
    this._watchmanBinaryPath = watchmanBinaryPath;
 | 
			
		||||
 | 
			
		||||
    this._backoffTimes = this._setupBackoffTimes();
 | 
			
		||||
 | 
			
		||||
    this._clientListeners = null; // direct listeners from here to watchman.Client.
 | 
			
		||||
 | 
			
		||||
    // Define a handler for if somehow the Node process gets interrupted. We need to
 | 
			
		||||
    // close down the watchman.Client, if we have one.
 | 
			
		||||
    captureExit.onExit(() => this._clearLocalVars());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Define 'wildmatch' property, which must be available when we call the
 | 
			
		||||
  // WatchmanWatcher.createOptions() method.
 | 
			
		||||
  get wildmatch() {
 | 
			
		||||
    return this._wildmatch;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called from WatchmanWatcher (or WatchmanClient during reconnect) to create
 | 
			
		||||
   * a watcherInfo entry in our _watcherMap and issue a 'subscribe' to the
 | 
			
		||||
   * watchman.Client, to be handled here.
 | 
			
		||||
   */
 | 
			
		||||
  subscribe(watchmanWatcher, root) {
 | 
			
		||||
    let subscription;
 | 
			
		||||
    let watcherInfo;
 | 
			
		||||
 | 
			
		||||
    return this._setupClient()
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        watcherInfo = this._createWatcherInfo(watchmanWatcher);
 | 
			
		||||
        subscription = watcherInfo.subscription;
 | 
			
		||||
        return this._watch(subscription, root);
 | 
			
		||||
      })
 | 
			
		||||
      .then(() => this._clock(subscription))
 | 
			
		||||
      .then(() => this._subscribe(subscription));
 | 
			
		||||
    // Note: callers are responsible for noting any subscription failure.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove the information about a specific WatchmanWatcher.
 | 
			
		||||
   * Once done, if no watchers are left, clear the local vars,
 | 
			
		||||
   * which will end the connection to the watchman.Client, too.
 | 
			
		||||
   */
 | 
			
		||||
  closeWatcher(watchmanWatcher) {
 | 
			
		||||
    let watcherInfos = values(this._watcherMap);
 | 
			
		||||
    let numWatchers = watcherInfos.length;
 | 
			
		||||
 | 
			
		||||
    if (numWatchers > 0) {
 | 
			
		||||
      let watcherInfo;
 | 
			
		||||
 | 
			
		||||
      for (let info of watcherInfos) {
 | 
			
		||||
        if (info.watchmanWatcher === watchmanWatcher) {
 | 
			
		||||
          watcherInfo = info;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (watcherInfo) {
 | 
			
		||||
        delete this._watcherMap[watcherInfo.subscription];
 | 
			
		||||
 | 
			
		||||
        numWatchers--;
 | 
			
		||||
 | 
			
		||||
        if (numWatchers === 0) {
 | 
			
		||||
          this._clearLocalVars(); // nobody watching, so shut the watchman.Client down.
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Simple backoff-time iterator. next() returns times in ms.
 | 
			
		||||
   * When it's at the last value, it stays there until reset()
 | 
			
		||||
   * is called.
 | 
			
		||||
   */
 | 
			
		||||
  _setupBackoffTimes() {
 | 
			
		||||
    return {
 | 
			
		||||
      _times: [0, 1000, 5000, 10000, 60000],
 | 
			
		||||
 | 
			
		||||
      _next: 0,
 | 
			
		||||
 | 
			
		||||
      next() {
 | 
			
		||||
        let val = this._times[this._next];
 | 
			
		||||
        if (this._next < this._times.length - 1) {
 | 
			
		||||
          this._next++;
 | 
			
		||||
        }
 | 
			
		||||
        return val;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      reset() {
 | 
			
		||||
        this._next = 0;
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set up the connection to the watchman client. Return a promise
 | 
			
		||||
   * that is fulfilled when we have a client that has finished the
 | 
			
		||||
   * capabilityCheck.
 | 
			
		||||
   */
 | 
			
		||||
  _setupClient() {
 | 
			
		||||
    if (!this._clientPromise) {
 | 
			
		||||
      this._clientPromise = new Promise((resolve, reject) => {
 | 
			
		||||
        this._handleClientAndCheck(resolve, reject);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return this._clientPromise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle the process of creating a client and doing a capability check and
 | 
			
		||||
   * getting a valid response, then setting up local data based on that.
 | 
			
		||||
   *
 | 
			
		||||
   * This is split from _setupClient and _createClientAndCheck so it can
 | 
			
		||||
   * provide the backoff handling needed during attempts to reconnect.
 | 
			
		||||
   */
 | 
			
		||||
  _handleClientAndCheck(resolve, reject) {
 | 
			
		||||
    this._createClientAndCheck().then(
 | 
			
		||||
      value => {
 | 
			
		||||
        let resp = value.resp;
 | 
			
		||||
        let client = value.client;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          this._wildmatch = resp.capabilities.wildmatch;
 | 
			
		||||
          this._relative_root = resp.capabilities.relative_root;
 | 
			
		||||
          this._client = client;
 | 
			
		||||
 | 
			
		||||
          client.on('subscription', this._onSubscription.bind(this));
 | 
			
		||||
          client.on('error', this._onError.bind(this));
 | 
			
		||||
          client.on('end', this._onEnd.bind(this));
 | 
			
		||||
 | 
			
		||||
          this._backoffTimes.reset();
 | 
			
		||||
          resolve(this);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          // somehow, even though we supposedly got a valid value back, it's
 | 
			
		||||
          // malformed, or some other internal error occurred. Reject so
 | 
			
		||||
          // the promise itself doesn't hang forever.
 | 
			
		||||
          reject(error);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      () => {
 | 
			
		||||
        // create & capability check failed in any of several ways,
 | 
			
		||||
        // do the retry with backoff.
 | 
			
		||||
 | 
			
		||||
        // XXX May want to change this later to actually reject/terminate with
 | 
			
		||||
        // an error in certain of the inner errors (e.g. when we
 | 
			
		||||
        // can figure out the server is definitely not coming
 | 
			
		||||
        // back, or something else is not recoverable by just waiting).
 | 
			
		||||
        // Could also decide after N retries to just quit.
 | 
			
		||||
 | 
			
		||||
        let backoffMillis = this._backoffTimes.next();
 | 
			
		||||
 | 
			
		||||
        // XXX may want to fact we'll attempt reconnect in backoffMillis ms.
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          this._handleClientAndCheck(resolve, reject);
 | 
			
		||||
        }, backoffMillis);
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a promise that will only be fulfilled when either
 | 
			
		||||
   * we correctly get capabilities back or we get an 'error' or 'end'
 | 
			
		||||
   * callback, indicating a problem. The caller _handleClientAndCheck
 | 
			
		||||
   * then deals with providing a retry and backoff mechanism.
 | 
			
		||||
   */
 | 
			
		||||
  _createClientAndCheck() {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let client;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        client = new watchman.Client(
 | 
			
		||||
          this._watchmanBinaryPath
 | 
			
		||||
            ? { watchmanBinaryPath: this._watchmanBinaryPath }
 | 
			
		||||
            : {}
 | 
			
		||||
        );
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        // if we're here, either the binary path is bad or something
 | 
			
		||||
        // else really bad happened. The client doesn't even attempt
 | 
			
		||||
        // to connect until we send it a command, though.
 | 
			
		||||
        reject(error);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      client.on('error', error => {
 | 
			
		||||
        client.removeAllListeners();
 | 
			
		||||
        reject(error);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      client.on('end', () => {
 | 
			
		||||
        client.removeAllListeners();
 | 
			
		||||
        reject(new Error('Disconnected during client capabilities check'));
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      client.capabilityCheck(
 | 
			
		||||
        { optional: ['wildmatch', 'relative_root'] },
 | 
			
		||||
        (error, resp) => {
 | 
			
		||||
          try {
 | 
			
		||||
            client.removeAllListeners();
 | 
			
		||||
 | 
			
		||||
            if (error) {
 | 
			
		||||
              reject(error);
 | 
			
		||||
            } else {
 | 
			
		||||
              resolve({ resp, client });
 | 
			
		||||
            }
 | 
			
		||||
          } catch (err) {
 | 
			
		||||
            // In case we get something weird in the block using 'resp'.
 | 
			
		||||
            // XXX We could also just remove the try/catch if we believe
 | 
			
		||||
            // the resp stuff should always work, but just in case...
 | 
			
		||||
            reject(err);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clear out local state at the beginning and if we end up
 | 
			
		||||
   * getting disconnected and try to reconnect.
 | 
			
		||||
   */
 | 
			
		||||
  _clearLocalVars() {
 | 
			
		||||
    if (this._client) {
 | 
			
		||||
      this._client.removeAllListeners();
 | 
			
		||||
      this._client.end();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._client = null;
 | 
			
		||||
    this._clientPromise = null;
 | 
			
		||||
    this._wildmatch = false;
 | 
			
		||||
    this._relative_root = false;
 | 
			
		||||
    this._subscriptionId = 1;
 | 
			
		||||
    this._watcherMap = Object.create(null);
 | 
			
		||||
 | 
			
		||||
    // Note that we do not clear _clientListeners or _watchmanBinaryPath.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _genSubscription() {
 | 
			
		||||
    let val = 'sane_' + this._subscriptionId++;
 | 
			
		||||
    return val;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a new watcherInfo entry for the given watchmanWatcher and
 | 
			
		||||
   * initialize it.
 | 
			
		||||
   */
 | 
			
		||||
  _createWatcherInfo(watchmanWatcher) {
 | 
			
		||||
    let watcherInfo = {
 | 
			
		||||
      subscription: this._genSubscription(),
 | 
			
		||||
      watchmanWatcher: watchmanWatcher,
 | 
			
		||||
      root: null, // set during 'watch' or 'watch-project'
 | 
			
		||||
      relativePath: null, // same
 | 
			
		||||
      since: null, // set during 'clock'
 | 
			
		||||
      options: null, // created and set during 'subscribe'.
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this._watcherMap[watcherInfo.subscription] = watcherInfo;
 | 
			
		||||
 | 
			
		||||
    return watcherInfo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Find an existing watcherInfo instance.
 | 
			
		||||
   */
 | 
			
		||||
  _getWatcherInfo(subscription) {
 | 
			
		||||
    return this._watcherMap[subscription];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Given a watchmanWatcher and a root, issue the correct 'watch'
 | 
			
		||||
   * or 'watch-project' command and handle it with the callback.
 | 
			
		||||
   * Because we're operating in 'sane', we'll keep the results
 | 
			
		||||
   * of the 'watch' or 'watch-project' here.
 | 
			
		||||
   */
 | 
			
		||||
  _watch(subscription, root) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let watcherInfo = this._getWatcherInfo(subscription);
 | 
			
		||||
 | 
			
		||||
      if (this._relative_root) {
 | 
			
		||||
        this._client.command(['watch-project', root], (error, resp) => {
 | 
			
		||||
          if (error) {
 | 
			
		||||
            reject(error);
 | 
			
		||||
          } else {
 | 
			
		||||
            watcherInfo.root = resp.watch;
 | 
			
		||||
            watcherInfo.relativePath = resp.relative_path
 | 
			
		||||
              ? resp.relative_path
 | 
			
		||||
              : '';
 | 
			
		||||
            resolve(resp);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        this._client.command(['watch', root], (error, resp) => {
 | 
			
		||||
          if (error) {
 | 
			
		||||
            reject(error);
 | 
			
		||||
          } else {
 | 
			
		||||
            watcherInfo.root = root;
 | 
			
		||||
            watcherInfo.relativePath = '';
 | 
			
		||||
            resolve(resp);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Issue the 'clock' command to get the time value for use with the 'since'
 | 
			
		||||
   * option during 'subscribe'.
 | 
			
		||||
   */
 | 
			
		||||
  _clock(subscription) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let watcherInfo = this._getWatcherInfo(subscription);
 | 
			
		||||
 | 
			
		||||
      this._client.command(['clock', watcherInfo.root], (error, resp) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          reject(error);
 | 
			
		||||
        } else {
 | 
			
		||||
          watcherInfo.since = resp.clock;
 | 
			
		||||
          resolve(resp);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Do the internal handling of calling the watchman.Client for
 | 
			
		||||
   * a subscription.
 | 
			
		||||
   */
 | 
			
		||||
  _subscribe(subscription) {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      let watcherInfo = this._getWatcherInfo(subscription);
 | 
			
		||||
 | 
			
		||||
      // create the 'bare' options w/o 'since' or relative_root.
 | 
			
		||||
      // Store in watcherInfo for later use if we need to reset
 | 
			
		||||
      // things after an 'end' caught here.
 | 
			
		||||
      let options = watcherInfo.watchmanWatcher.createOptions();
 | 
			
		||||
      watcherInfo.options = options;
 | 
			
		||||
 | 
			
		||||
      // Dup the options object so we can add 'relative_root' and 'since'
 | 
			
		||||
      // and leave the original options object alone. We'll do this again
 | 
			
		||||
      // later if we need to resubscribe after 'end' and reconnect.
 | 
			
		||||
      options = Object.assign({}, options);
 | 
			
		||||
 | 
			
		||||
      if (this._relative_root) {
 | 
			
		||||
        options.relative_root = watcherInfo.relativePath;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      options.since = watcherInfo.since;
 | 
			
		||||
 | 
			
		||||
      this._client.command(
 | 
			
		||||
        ['subscribe', watcherInfo.root, subscription, options],
 | 
			
		||||
        (error, resp) => {
 | 
			
		||||
          if (error) {
 | 
			
		||||
            reject(error);
 | 
			
		||||
          } else {
 | 
			
		||||
            resolve(resp);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle the 'subscription' (file change) event, by calling the
 | 
			
		||||
   * handler on the relevant WatchmanWatcher.
 | 
			
		||||
   */
 | 
			
		||||
  _onSubscription(resp) {
 | 
			
		||||
    let watcherInfo = this._getWatcherInfo(resp.subscription);
 | 
			
		||||
 | 
			
		||||
    if (watcherInfo) {
 | 
			
		||||
      // we're assuming the watchmanWatcher does not throw during
 | 
			
		||||
      // handling of the change event.
 | 
			
		||||
      watcherInfo.watchmanWatcher.handleChangeEvent(resp);
 | 
			
		||||
    } else {
 | 
			
		||||
      // Note it in the log, but otherwise ignore it
 | 
			
		||||
      console.error(
 | 
			
		||||
        "WatchmanClient error - received 'subscription' event " +
 | 
			
		||||
          "for non-existent subscription '" +
 | 
			
		||||
          resp.subscription +
 | 
			
		||||
          "'"
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle the 'error' event by forwarding to the
 | 
			
		||||
   * handler on all WatchmanWatchers (errors are generally during processing
 | 
			
		||||
   * a particular command, but it's not given which command that was, or
 | 
			
		||||
   * which subscription it belonged to).
 | 
			
		||||
   */
 | 
			
		||||
  _onError(error) {
 | 
			
		||||
    values(this._watcherMap).forEach(watcherInfo =>
 | 
			
		||||
      watcherInfo.watchmanWatcher.handleErrorEvent(error)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle the 'end' event by creating a new watchman.Client and
 | 
			
		||||
   * attempting to resubscribe all the existing subscriptions, but
 | 
			
		||||
   * without notifying the WatchmanWatchers about it. They should
 | 
			
		||||
   * not be aware the connection was lost and recreated.
 | 
			
		||||
   * If something goes wrong during any part of the reconnect/setup,
 | 
			
		||||
   * call the error handler on each existing WatchmanWatcher.
 | 
			
		||||
   */
 | 
			
		||||
  _onEnd() {
 | 
			
		||||
    console.warn(
 | 
			
		||||
      '[sane.WatchmanClient] Warning: Lost connection to watchman, reconnecting..'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Hold the old watcher map so we use it to recreate all subscriptions.
 | 
			
		||||
    let oldWatcherInfos = values(this._watcherMap);
 | 
			
		||||
 | 
			
		||||
    this._clearLocalVars();
 | 
			
		||||
 | 
			
		||||
    this._setupClient().then(
 | 
			
		||||
      () => {
 | 
			
		||||
        let promises = oldWatcherInfos.map(watcherInfo =>
 | 
			
		||||
          this.subscribe(
 | 
			
		||||
            watcherInfo.watchmanWatcher,
 | 
			
		||||
            watcherInfo.watchmanWatcher.root
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
        Promise.all(promises).then(
 | 
			
		||||
          () => {
 | 
			
		||||
            console.log('[sane.WatchmanClient]: Reconnected to watchman');
 | 
			
		||||
          },
 | 
			
		||||
          error => {
 | 
			
		||||
            console.error(
 | 
			
		||||
              '[sane.WatchmanClient]: Reconnected to watchman, but failed to ' +
 | 
			
		||||
                'reestablish at least one subscription, cannot continue'
 | 
			
		||||
            );
 | 
			
		||||
            console.error(error);
 | 
			
		||||
            oldWatcherInfos.forEach(watcherInfo =>
 | 
			
		||||
              watcherInfo.watchmanWatcher.handleErrorEvent(error)
 | 
			
		||||
            );
 | 
			
		||||
            // XXX not sure whether to clear all _watcherMap instances here,
 | 
			
		||||
            // but basically this client is inconsistent now, since at least one
 | 
			
		||||
            // subscribe failed.
 | 
			
		||||
          }
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      error => {
 | 
			
		||||
        console.error(
 | 
			
		||||
          '[sane.WatchmanClient]: Lost connection to watchman, ' +
 | 
			
		||||
            'reconnect failed, cannot continue'
 | 
			
		||||
        );
 | 
			
		||||
        console.error(error);
 | 
			
		||||
        oldWatcherInfos.forEach(watcherInfo =>
 | 
			
		||||
          watcherInfo.watchmanWatcher.handleErrorEvent(error)
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  /**
 | 
			
		||||
   * Create/retrieve an instance of the WatchmanClient. See the header comment
 | 
			
		||||
   * about the map of client instances, one per watchmanPath.
 | 
			
		||||
   * Export the getInstance method by itself so the callers cannot do anything until
 | 
			
		||||
   * they get a real WatchmanClient instance here.
 | 
			
		||||
   */
 | 
			
		||||
  getInstance(watchmanBinaryPath) {
 | 
			
		||||
    let clientMap = WatchmanClient.prototype._clientMap;
 | 
			
		||||
 | 
			
		||||
    if (!clientMap) {
 | 
			
		||||
      clientMap = Object.create(null);
 | 
			
		||||
      WatchmanClient.prototype._clientMap = clientMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (watchmanBinaryPath == undefined || watchmanBinaryPath === null) {
 | 
			
		||||
      watchmanBinaryPath = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let watchmanClient = clientMap[watchmanBinaryPath];
 | 
			
		||||
 | 
			
		||||
    if (!watchmanClient) {
 | 
			
		||||
      watchmanClient = new WatchmanClient(watchmanBinaryPath);
 | 
			
		||||
      clientMap[watchmanBinaryPath] = watchmanClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return watchmanClient;
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										249
									
								
								node_modules/sane/src/watchman_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								node_modules/sane/src/watchman_watcher.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,249 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const common = require('./common');
 | 
			
		||||
const watchmanClient = require('./watchman_client');
 | 
			
		||||
const EventEmitter = require('events').EventEmitter;
 | 
			
		||||
const RecrawlWarning = require('./utils/recrawl-warning-dedupe');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Constants
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const CHANGE_EVENT = common.CHANGE_EVENT;
 | 
			
		||||
const DELETE_EVENT = common.DELETE_EVENT;
 | 
			
		||||
const ADD_EVENT = common.ADD_EVENT;
 | 
			
		||||
const ALL_EVENT = common.ALL_EVENT;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Export `WatchmanWatcher` class.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = WatchmanWatcher;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Watches `dir`.
 | 
			
		||||
 *
 | 
			
		||||
 * @class WatchmanWatcher
 | 
			
		||||
 * @param String dir
 | 
			
		||||
 * @param {Object} opts
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function WatchmanWatcher(dir, opts) {
 | 
			
		||||
  common.assignOptions(this, opts);
 | 
			
		||||
  this.root = path.resolve(dir);
 | 
			
		||||
  this._init();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype.__proto__ = EventEmitter.prototype;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Run the watchman `watch` command on the root and subscribe to changes.
 | 
			
		||||
 *
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
WatchmanWatcher.prototype._init = function() {
 | 
			
		||||
  if (this._client) {
 | 
			
		||||
    this._client = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Get the WatchmanClient instance corresponding to our watchmanPath (or nothing).
 | 
			
		||||
  // Then subscribe, which will do the appropriate setup so that we will receive
 | 
			
		||||
  // calls to handleChangeEvent when files change.
 | 
			
		||||
  this._client = watchmanClient.getInstance(this.watchmanPath);
 | 
			
		||||
 | 
			
		||||
  return this._client.subscribe(this, this.root).then(
 | 
			
		||||
    resp => {
 | 
			
		||||
      this._handleWarning(resp);
 | 
			
		||||
      this.emit('ready');
 | 
			
		||||
    },
 | 
			
		||||
    error => {
 | 
			
		||||
      this._handleError(error);
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Called by WatchmanClient to create the options, either during initial 'subscribe'
 | 
			
		||||
 * or to resubscribe after a disconnect+reconnect. Note that we are leaving out
 | 
			
		||||
 * the watchman 'since' and 'relative_root' options, which are handled inside the
 | 
			
		||||
 * WatchmanClient.
 | 
			
		||||
 */
 | 
			
		||||
WatchmanWatcher.prototype.createOptions = function() {
 | 
			
		||||
  let options = {
 | 
			
		||||
    fields: ['name', 'exists', 'new'],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // If the server has the wildmatch capability available it supports
 | 
			
		||||
  // the recursive **/*.foo style match and we can offload our globs
 | 
			
		||||
  // to the watchman server.  This saves both on data size to be
 | 
			
		||||
  // communicated back to us and compute for evaluating the globs
 | 
			
		||||
  // in our node process.
 | 
			
		||||
  if (this._client.wildmatch) {
 | 
			
		||||
    if (this.globs.length === 0) {
 | 
			
		||||
      if (!this.dot) {
 | 
			
		||||
        // Make sure we honor the dot option if even we're not using globs.
 | 
			
		||||
        options.expression = [
 | 
			
		||||
          'match',
 | 
			
		||||
          '**',
 | 
			
		||||
          'wholename',
 | 
			
		||||
          {
 | 
			
		||||
            includedotfiles: false,
 | 
			
		||||
          },
 | 
			
		||||
        ];
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      options.expression = ['anyof'];
 | 
			
		||||
      for (let i in this.globs) {
 | 
			
		||||
        options.expression.push([
 | 
			
		||||
          'match',
 | 
			
		||||
          this.globs[i],
 | 
			
		||||
          'wholename',
 | 
			
		||||
          {
 | 
			
		||||
            includedotfiles: this.dot,
 | 
			
		||||
          },
 | 
			
		||||
        ]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return options;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Called by WatchmanClient when it receives an error from the watchman daemon.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} resp
 | 
			
		||||
 */
 | 
			
		||||
WatchmanWatcher.prototype.handleErrorEvent = function(error) {
 | 
			
		||||
  this.emit('error', error);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Called by the WatchmanClient when it is notified about a file change in
 | 
			
		||||
 * the tree for this particular watcher's root.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} resp
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype.handleChangeEvent = function(resp) {
 | 
			
		||||
  if (Array.isArray(resp.files)) {
 | 
			
		||||
    resp.files.forEach(this.handleFileChange, this);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles a single change event record.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} changeDescriptor
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype.handleFileChange = function(changeDescriptor) {
 | 
			
		||||
  let absPath;
 | 
			
		||||
  let relativePath;
 | 
			
		||||
 | 
			
		||||
  relativePath = changeDescriptor.name;
 | 
			
		||||
  absPath = path.join(this.root, relativePath);
 | 
			
		||||
 | 
			
		||||
  if (
 | 
			
		||||
    !(this._client.wildmatch && !this.hasIgnore) &&
 | 
			
		||||
    !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
 | 
			
		||||
  ) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!changeDescriptor.exists) {
 | 
			
		||||
    this.emitEvent(DELETE_EVENT, relativePath, this.root);
 | 
			
		||||
  } else {
 | 
			
		||||
    fs.lstat(absPath, (error, stat) => {
 | 
			
		||||
      // Files can be deleted between the event and the lstat call
 | 
			
		||||
      // the most reliable thing to do here is to ignore the event.
 | 
			
		||||
      if (error && error.code === 'ENOENT') {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this._handleError(error)) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let eventType = changeDescriptor.new ? ADD_EVENT : CHANGE_EVENT;
 | 
			
		||||
 | 
			
		||||
      // Change event on dirs are mostly useless.
 | 
			
		||||
      if (!(eventType === CHANGE_EVENT && stat.isDirectory())) {
 | 
			
		||||
        this.emitEvent(eventType, relativePath, this.root, stat);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dispatches an event.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} eventType
 | 
			
		||||
 * @param {string} filepath
 | 
			
		||||
 * @param {string} root
 | 
			
		||||
 * @param {fs.Stat} stat
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype.emitEvent = function(
 | 
			
		||||
  eventType,
 | 
			
		||||
  filepath,
 | 
			
		||||
  root,
 | 
			
		||||
  stat
 | 
			
		||||
) {
 | 
			
		||||
  this.emit(eventType, filepath, root, stat);
 | 
			
		||||
  this.emit(ALL_EVENT, eventType, filepath, root, stat);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Closes the watcher.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {function} callback
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype.close = function(callback) {
 | 
			
		||||
  this._client.closeWatcher(this);
 | 
			
		||||
  callback && callback(null, true);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles an error and returns true if exists.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {WatchmanWatcher} self
 | 
			
		||||
 * @param {Error} error
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype._handleError = function(error) {
 | 
			
		||||
  if (error != null) {
 | 
			
		||||
    this.emit('error', error);
 | 
			
		||||
    return true;
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles a warning in the watchman resp object.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {object} resp
 | 
			
		||||
 * @private
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
WatchmanWatcher.prototype._handleWarning = function(resp) {
 | 
			
		||||
  if ('warning' in resp) {
 | 
			
		||||
    if (RecrawlWarning.isRecrawlWarningDupe(resp.warning)) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    console.warn(resp.warning);
 | 
			
		||||
    return true;
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user