UNPKG

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