WatchmanWatcher.js 9.5 KB

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