watchman_watcher.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = WatchmanWatcher;
  6. var _fs;
  7. function _load_fs() {
  8. return _fs = _interopRequireDefault(require('fs'));
  9. }
  10. var _path;
  11. function _load_path() {
  12. return _path = _interopRequireDefault(require('path'));
  13. }
  14. var _assert;
  15. function _load_assert() {
  16. return _assert = _interopRequireDefault(require('assert'));
  17. }
  18. var _common;
  19. function _load_common() {
  20. return _common = _interopRequireDefault(require('sane/src/common'));
  21. }
  22. var _fbWatchman;
  23. function _load_fbWatchman() {
  24. return _fbWatchman = _interopRequireDefault(require('fb-watchman'));
  25. }
  26. var _events;
  27. function _load_events() {
  28. return _events = require('events');
  29. }
  30. var _recrawlWarningDedupe;
  31. function _load_recrawlWarningDedupe() {
  32. return _recrawlWarningDedupe = _interopRequireDefault(require('sane/src/utils/recrawl-warning-dedupe'));
  33. }
  34. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  35. const CHANGE_EVENT = (_common || _load_common()).default.CHANGE_EVENT; /**
  36. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  37. *
  38. * This source code is licensed under the MIT license found in the
  39. * LICENSE file in the root directory of this source tree.
  40. */
  41. const DELETE_EVENT = (_common || _load_common()).default.DELETE_EVENT;
  42. const ADD_EVENT = (_common || _load_common()).default.ADD_EVENT;
  43. const ALL_EVENT = (_common || _load_common()).default.ALL_EVENT;
  44. const SUB_NAME = 'sane-sub';
  45. /**
  46. * Watches `dir`.
  47. *
  48. * @class PollWatcher
  49. * @param String dir
  50. * @param {Object} opts
  51. * @public
  52. */
  53. function WatchmanWatcher(dir, opts) {
  54. (_common || _load_common()).default.assignOptions(this, opts);
  55. this.root = (_path || _load_path()).default.resolve(dir);
  56. this.init();
  57. }
  58. // eslint-disable-next-line no-proto
  59. WatchmanWatcher.prototype.__proto__ = (_events || _load_events()).EventEmitter.prototype;
  60. /**
  61. * Run the watchman `watch` command on the root and subscribe to changes.
  62. *
  63. * @private
  64. */
  65. WatchmanWatcher.prototype.init = function () {
  66. if (this.client) {
  67. this.client.removeAllListeners();
  68. }
  69. const self = this;
  70. this.client = new (_fbWatchman || _load_fbWatchman()).default.Client();
  71. this.client.on('error', error => {
  72. self.emit('error', error);
  73. });
  74. this.client.on('subscription', this.handleChangeEvent.bind(this));
  75. this.client.on('end', () => {
  76. console.warn('[sane] Warning: Lost connection to watchman, reconnecting..');
  77. self.init();
  78. });
  79. this.watchProjectInfo = null;
  80. function getWatchRoot() {
  81. return self.watchProjectInfo ? self.watchProjectInfo.root : self.root;
  82. }
  83. function onCapability(error, resp) {
  84. if (handleError(self, error)) {
  85. // The Watchman watcher is unusable on this system, we cannot continue
  86. return;
  87. }
  88. handleWarning(resp);
  89. self.capabilities = resp.capabilities;
  90. if (self.capabilities.relative_root) {
  91. self.client.command(['watch-project', getWatchRoot()], onWatchProject);
  92. } else {
  93. self.client.command(['watch', getWatchRoot()], onWatch);
  94. }
  95. }
  96. function onWatchProject(error, resp) {
  97. if (handleError(self, error)) {
  98. return;
  99. }
  100. handleWarning(resp);
  101. self.watchProjectInfo = {
  102. relativePath: resp.relative_path ? resp.relative_path : '',
  103. root: resp.watch
  104. };
  105. self.client.command(['clock', getWatchRoot()], onClock);
  106. }
  107. function onWatch(error, resp) {
  108. if (handleError(self, error)) {
  109. return;
  110. }
  111. handleWarning(resp);
  112. self.client.command(['clock', getWatchRoot()], onClock);
  113. }
  114. function onClock(error, resp) {
  115. if (handleError(self, error)) {
  116. return;
  117. }
  118. handleWarning(resp);
  119. const options = {
  120. fields: ['name', 'exists', 'new'],
  121. since: resp.clock
  122. };
  123. // If the server has the wildmatch capability available it supports
  124. // the recursive **/*.foo style match and we can offload our globs
  125. // to the watchman server. This saves both on data size to be
  126. // communicated back to us and compute for evaluating the globs
  127. // in our node process.
  128. if (self.capabilities.wildmatch) {
  129. if (self.globs.length === 0) {
  130. if (!self.dot) {
  131. // Make sure we honor the dot option if even we're not using globs.
  132. options.expression = ['match', '**', 'wholename', {
  133. includedotfiles: false
  134. }];
  135. }
  136. } else {
  137. options.expression = ['anyof'];
  138. for (const i in self.globs) {
  139. options.expression.push(['match', self.globs[i], 'wholename', {
  140. includedotfiles: self.dot
  141. }]);
  142. }
  143. }
  144. }
  145. if (self.capabilities.relative_root) {
  146. options.relative_root = self.watchProjectInfo.relativePath;
  147. }
  148. self.client.command(['subscribe', getWatchRoot(), SUB_NAME, options], onSubscribe);
  149. }
  150. function onSubscribe(error, resp) {
  151. if (handleError(self, error)) {
  152. return;
  153. }
  154. handleWarning(resp);
  155. self.emit('ready');
  156. }
  157. self.client.capabilityCheck({
  158. optional: ['wildmatch', 'relative_root']
  159. }, onCapability);
  160. };
  161. /**
  162. * Handles a change event coming from the subscription.
  163. *
  164. * @param {Object} resp
  165. * @private
  166. */
  167. WatchmanWatcher.prototype.handleChangeEvent = function (resp) {
  168. (_assert || _load_assert()).default.equal(resp.subscription, SUB_NAME, 'Invalid subscription event.');
  169. if (resp.is_fresh_instance) {
  170. this.emit('fresh_instance');
  171. }
  172. if (resp.is_fresh_instance) {
  173. this.emit('fresh_instance');
  174. }
  175. if (Array.isArray(resp.files)) {
  176. resp.files.forEach(this.handleFileChange, this);
  177. }
  178. };
  179. /**
  180. * Handles a single change event record.
  181. *
  182. * @param {Object} changeDescriptor
  183. * @private
  184. */
  185. WatchmanWatcher.prototype.handleFileChange = function (changeDescriptor) {
  186. const self = this;
  187. let absPath;
  188. let relativePath;
  189. if (this.capabilities.relative_root) {
  190. relativePath = changeDescriptor.name;
  191. absPath = (_path || _load_path()).default.join(this.watchProjectInfo.root, this.watchProjectInfo.relativePath, relativePath);
  192. } else {
  193. absPath = (_path || _load_path()).default.join(this.root, changeDescriptor.name);
  194. relativePath = changeDescriptor.name;
  195. }
  196. if (!(self.capabilities.wildmatch && !this.hasIgnore) && !(_common || _load_common()).default.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)) {
  197. return;
  198. }
  199. if (!changeDescriptor.exists) {
  200. self.emitEvent(DELETE_EVENT, relativePath, self.root);
  201. } else {
  202. (_fs || _load_fs()).default.lstat(absPath, (error, stat) => {
  203. // Files can be deleted between the event and the lstat call
  204. // the most reliable thing to do here is to ignore the event.
  205. if (error && error.code === 'ENOENT') {
  206. return;
  207. }
  208. if (handleError(self, error)) {
  209. return;
  210. }
  211. const eventType = changeDescriptor.new ? ADD_EVENT : CHANGE_EVENT;
  212. // Change event on dirs are mostly useless.
  213. if (!(eventType === CHANGE_EVENT && stat.isDirectory())) {
  214. self.emitEvent(eventType, relativePath, self.root, stat);
  215. }
  216. });
  217. }
  218. };
  219. /**
  220. * Dispatches the event.
  221. *
  222. * @param {string} eventType
  223. * @param {string} filepath
  224. * @param {string} root
  225. * @param {fs.Stat} stat
  226. * @private
  227. */
  228. WatchmanWatcher.prototype.emitEvent = function (eventType, filepath, root, stat) {
  229. this.emit(eventType, filepath, root, stat);
  230. this.emit(ALL_EVENT, eventType, filepath, root, stat);
  231. };
  232. /**
  233. * Closes the watcher.
  234. *
  235. * @param {function} callback
  236. * @private
  237. */
  238. WatchmanWatcher.prototype.close = function (callback) {
  239. this.client.removeAllListeners();
  240. this.client.end();
  241. callback && callback(null, true);
  242. };
  243. /**
  244. * Handles an error and returns true if exists.
  245. *
  246. * @param {WatchmanWatcher} self
  247. * @param {Error} error
  248. * @private
  249. */
  250. function handleError(self, error) {
  251. if (error != null) {
  252. self.emit('error', error);
  253. return true;
  254. } else {
  255. return false;
  256. }
  257. }
  258. /**
  259. * Handles a warning in the watchman resp object.
  260. *
  261. * @param {object} resp
  262. * @private
  263. */
  264. function handleWarning(resp) {
  265. if ('warning' in resp) {
  266. if ((_recrawlWarningDedupe || _load_recrawlWarningDedupe()).default.isRecrawlWarningDupe(resp.warning)) {
  267. return true;
  268. }
  269. console.warn(resp.warning);
  270. return true;
  271. } else {
  272. return false;
  273. }
  274. }