295 lines
8.7 KiB
JavaScript
295 lines
8.7 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true,
|
|
});
|
|
exports.default = void 0;
|
|
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
|
|
require("../lib/normalizePathSeparatorsToSystem"),
|
|
);
|
|
var _AbstractWatcher = require("./AbstractWatcher");
|
|
var common = _interopRequireWildcard(require("./common"));
|
|
var _RecrawlWarning = _interopRequireDefault(require("./RecrawlWarning"));
|
|
var _assert = _interopRequireDefault(require("assert"));
|
|
var _crypto = require("crypto");
|
|
var _fbWatchman = _interopRequireDefault(require("fb-watchman"));
|
|
var _invariant = _interopRequireDefault(require("invariant"));
|
|
function _getRequireWildcardCache(e) {
|
|
if ("function" != typeof WeakMap) return null;
|
|
var r = new WeakMap(),
|
|
t = new WeakMap();
|
|
return (_getRequireWildcardCache = function (e) {
|
|
return e ? t : r;
|
|
})(e);
|
|
}
|
|
function _interopRequireWildcard(e, r) {
|
|
if (!r && e && e.__esModule) return e;
|
|
if (null === e || ("object" != typeof e && "function" != typeof e))
|
|
return { default: e };
|
|
var t = _getRequireWildcardCache(r);
|
|
if (t && t.has(e)) return t.get(e);
|
|
var n = { __proto__: null },
|
|
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
for (var u in e)
|
|
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
|
|
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
|
|
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
|
|
}
|
|
return ((n.default = e), t && t.set(e, n), n);
|
|
}
|
|
function _interopRequireDefault(e) {
|
|
return e && e.__esModule ? e : { default: e };
|
|
}
|
|
const debug = require("debug")("Metro:WatchmanWatcher");
|
|
const DELETE_EVENT = common.DELETE_EVENT;
|
|
const TOUCH_EVENT = common.TOUCH_EVENT;
|
|
const SUB_PREFIX = "metro-file-map";
|
|
class WatchmanWatcher extends _AbstractWatcher.AbstractWatcher {
|
|
#deferringStates = null;
|
|
constructor(dir, { watchmanDeferStates, ...opts }) {
|
|
super(dir, opts);
|
|
this.watchmanDeferStates = watchmanDeferStates;
|
|
const watchKey = (0, _crypto.createHash)("md5")
|
|
.update(this.root)
|
|
.digest("hex");
|
|
const readablePath = this.root
|
|
.replace(/[\/\\]/g, "-")
|
|
.replace(/[^\-\w]/g, "");
|
|
this.subscriptionName = `${SUB_PREFIX}-${process.pid}-${readablePath}-${watchKey}`;
|
|
}
|
|
async startWatching() {
|
|
await new Promise((resolve, reject) => this._init(resolve, reject));
|
|
}
|
|
_init(onReady, onError) {
|
|
if (this.client) {
|
|
this.client.removeAllListeners();
|
|
}
|
|
const self = this;
|
|
this.client = new _fbWatchman.default.Client();
|
|
this.client.on("error", (error) => {
|
|
this.emitError(error);
|
|
});
|
|
this.client.on("subscription", (changeEvent) =>
|
|
this._handleChangeEvent(changeEvent),
|
|
);
|
|
this.client.on("end", () => {
|
|
console.warn(
|
|
"[metro-file-map] Warning: Lost connection to Watchman, reconnecting..",
|
|
);
|
|
self._init(
|
|
() => {},
|
|
(error) => self.emitError(error),
|
|
);
|
|
});
|
|
this.watchProjectInfo = null;
|
|
function getWatchRoot() {
|
|
return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
|
|
}
|
|
function onWatchProject(error, resp) {
|
|
if (error) {
|
|
onError(error);
|
|
return;
|
|
}
|
|
debug("Received watch-project response: %s", resp.relative_path);
|
|
handleWarning(resp);
|
|
self.watchProjectInfo = {
|
|
relativePath: resp.relative_path
|
|
? (0, _normalizePathSeparatorsToSystem.default)(resp.relative_path)
|
|
: "",
|
|
root: (0, _normalizePathSeparatorsToSystem.default)(resp.watch),
|
|
};
|
|
self.client.command(["clock", getWatchRoot()], onClock);
|
|
}
|
|
function onClock(error, resp) {
|
|
if (error) {
|
|
onError(error);
|
|
return;
|
|
}
|
|
debug("Received clock response: %s", resp.clock);
|
|
const watchProjectInfo = self.watchProjectInfo;
|
|
(0, _invariant.default)(
|
|
watchProjectInfo != null,
|
|
"watch-project response should have been set before clock response",
|
|
);
|
|
handleWarning(resp);
|
|
const options = {
|
|
fields: ["name", "exists", "new", "type", "size", "mtime_ms"],
|
|
since: resp.clock,
|
|
defer: self.watchmanDeferStates,
|
|
relative_root: watchProjectInfo.relativePath,
|
|
};
|
|
if (self.globs.length === 0 && !self.dot) {
|
|
options.expression = [
|
|
"match",
|
|
"**",
|
|
"wholename",
|
|
{
|
|
includedotfiles: false,
|
|
},
|
|
];
|
|
}
|
|
self.client.command(
|
|
["subscribe", getWatchRoot(), self.subscriptionName, options],
|
|
onSubscribe,
|
|
);
|
|
}
|
|
const onSubscribe = (error, resp) => {
|
|
if (error) {
|
|
onError(error);
|
|
return;
|
|
}
|
|
debug("Received subscribe response: %s", resp.subscribe);
|
|
handleWarning(resp);
|
|
if (resp["asserted-states"] != null) {
|
|
this.#deferringStates = new Set(resp["asserted-states"]);
|
|
}
|
|
onReady();
|
|
};
|
|
self.client.command(["watch-project", getWatchRoot()], onWatchProject);
|
|
}
|
|
_handleChangeEvent(resp) {
|
|
debug(
|
|
"Received subscription response: %s (fresh: %s, files: %s, enter: %s, leave: %s, clock: %s)",
|
|
resp.subscription,
|
|
resp.is_fresh_instance,
|
|
resp.files?.length,
|
|
resp["state-enter"],
|
|
resp["state-leave"],
|
|
resp.clock,
|
|
);
|
|
_assert.default.equal(
|
|
resp.subscription,
|
|
this.subscriptionName,
|
|
"Invalid subscription event.",
|
|
);
|
|
if (Array.isArray(resp.files)) {
|
|
resp.files.forEach((change) =>
|
|
this._handleFileChange(change, resp.clock),
|
|
);
|
|
}
|
|
const { "state-enter": stateEnter, "state-leave": stateLeave } = resp;
|
|
if (
|
|
stateEnter != null &&
|
|
(this.watchmanDeferStates ?? []).includes(stateEnter)
|
|
) {
|
|
this.#deferringStates?.add(stateEnter);
|
|
debug(
|
|
'Watchman reports "%s" just started. Filesystem notifications are paused.',
|
|
stateEnter,
|
|
);
|
|
}
|
|
if (
|
|
stateLeave != null &&
|
|
(this.watchmanDeferStates ?? []).includes(stateLeave)
|
|
) {
|
|
this.#deferringStates?.delete(stateLeave);
|
|
debug(
|
|
'Watchman reports "%s" ended. Filesystem notifications resumed.',
|
|
stateLeave,
|
|
);
|
|
}
|
|
}
|
|
_handleFileChange(changeDescriptor, rawClock) {
|
|
const self = this;
|
|
const watchProjectInfo = self.watchProjectInfo;
|
|
(0, _invariant.default)(
|
|
watchProjectInfo != null,
|
|
"watch-project response should have been set before receiving subscription events",
|
|
);
|
|
const {
|
|
name: relativePosixPath,
|
|
new: isNew = false,
|
|
exists = false,
|
|
type,
|
|
mtime_ms,
|
|
size,
|
|
} = changeDescriptor;
|
|
const relativePath = (0, _normalizePathSeparatorsToSystem.default)(
|
|
relativePosixPath,
|
|
);
|
|
debug(
|
|
"Handling change to: %s (new: %s, exists: %s, type: %s)",
|
|
relativePath,
|
|
isNew,
|
|
exists,
|
|
type,
|
|
);
|
|
if (type != null && !(type === "f" || type === "d" || type === "l")) {
|
|
return;
|
|
}
|
|
if (
|
|
this.doIgnore(relativePath) ||
|
|
!common.includedByGlob(type, this.globs, this.dot, relativePath)
|
|
) {
|
|
return;
|
|
}
|
|
const clock =
|
|
typeof rawClock === "string" && this.watchProjectInfo != null
|
|
? [this.watchProjectInfo.root, rawClock]
|
|
: undefined;
|
|
if (!exists) {
|
|
self.emitFileEvent({
|
|
event: DELETE_EVENT,
|
|
clock,
|
|
relativePath,
|
|
});
|
|
} else {
|
|
(0, _invariant.default)(
|
|
type != null && mtime_ms != null && size != null,
|
|
'Watchman file change event for "%s" missing some requested metadata. ' +
|
|
"Got type: %s, mtime_ms: %s, size: %s",
|
|
relativePath,
|
|
type,
|
|
mtime_ms,
|
|
size,
|
|
);
|
|
if (!(type === "d" && !isNew)) {
|
|
const mtime = Number(mtime_ms);
|
|
self.emitFileEvent({
|
|
event: TOUCH_EVENT,
|
|
clock,
|
|
relativePath,
|
|
metadata: {
|
|
modifiedTime: mtime !== 0 ? mtime : null,
|
|
size,
|
|
type,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
async stopWatching() {
|
|
await super.stopWatching();
|
|
if (this.client) {
|
|
this.client.removeAllListeners();
|
|
this.client.end();
|
|
}
|
|
this.#deferringStates = null;
|
|
}
|
|
getPauseReason() {
|
|
if (this.#deferringStates == null || this.#deferringStates.size === 0) {
|
|
return null;
|
|
}
|
|
const states = [...this.#deferringStates];
|
|
if (states.length === 1) {
|
|
return `The watch is in the '${states[0]}' state.`;
|
|
}
|
|
return `The watch is in the ${states
|
|
.slice(0, -1)
|
|
.map((s) => `'${s}'`)
|
|
.join(", ")} and '${states[states.length - 1]}' states.`;
|
|
}
|
|
}
|
|
exports.default = WatchmanWatcher;
|
|
function handleWarning(resp) {
|
|
if ("warning" in resp) {
|
|
if (_RecrawlWarning.default.isRecrawlWarningDupe(resp.warning)) {
|
|
return true;
|
|
}
|
|
console.warn(resp.warning);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|