mirror of
https://github.com/actions/setup-java.git
synced 2025-04-21 10:26:46 +00:00
Fix.
This commit is contained in:
parent
596a6da241
commit
c1a589c5b6
7078 changed files with 1882834 additions and 319 deletions
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;
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue