watchexec_watcher.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use strict';
  2. const execa = require('execa');
  3. const { statSync } = require('fs');
  4. const path = require('path');
  5. const common = require('./common');
  6. const EventEmitter = require('events').EventEmitter;
  7. const { EOL } = require('os');
  8. /**
  9. * Constants
  10. */
  11. const CHANGE_EVENT = common.CHANGE_EVENT;
  12. const DELETE_EVENT = common.DELETE_EVENT;
  13. const ADD_EVENT = common.ADD_EVENT;
  14. const ALL_EVENT = common.ALL_EVENT;
  15. const typeMap = {
  16. rename: CHANGE_EVENT,
  17. write: CHANGE_EVENT,
  18. remove: DELETE_EVENT,
  19. create: ADD_EVENT,
  20. };
  21. const messageRegexp = /(rename|write|remove|create)\s(.+)/;
  22. /**
  23. * Manages streams from subprocess and turns into sane events
  24. *
  25. * @param {Stream} data
  26. * @private
  27. */
  28. function _messageHandler(data) {
  29. data
  30. .toString()
  31. .split(EOL)
  32. .filter(str => str.trim().length)
  33. .filter(str => messageRegexp.test(str))
  34. .map(line => {
  35. const [, command, path] = [...line.match(messageRegexp)];
  36. return [command, path];
  37. })
  38. .forEach(([command, file]) => {
  39. let stat;
  40. const type = typeMap[command];
  41. if (type === DELETE_EVENT) {
  42. stat = null;
  43. } else {
  44. try {
  45. stat = statSync(file);
  46. } catch (e) {
  47. // There is likely a delete coming down the pipe.
  48. if (e.code === 'ENOENT') {
  49. return;
  50. }
  51. throw e;
  52. }
  53. }
  54. this.emitEvent(type, path.relative(this.root, file), stat);
  55. });
  56. }
  57. /**
  58. * Export `WatchexecWatcher` class.
  59. * Watches `dir`.
  60. *
  61. * @class WatchexecWatcher
  62. * @param String dir
  63. * @param {Object} opts
  64. * @public
  65. */
  66. class WatchexecWatcher extends EventEmitter {
  67. constructor(dir, opts) {
  68. super();
  69. common.assignOptions(this, opts);
  70. this.root = path.resolve(dir);
  71. this._process = execa(
  72. 'watchexec',
  73. ['-n', '--', 'node', __dirname + '/watchexec_client.js'],
  74. { cwd: dir }
  75. );
  76. this._process.stdout.on('data', _messageHandler.bind(this));
  77. this._readyTimer = setTimeout(this.emit.bind(this, 'ready'), 1000);
  78. }
  79. close(callback) {
  80. clearTimeout(this._readyTimer);
  81. this.removeAllListeners();
  82. this._process && !this._process.killed && this._process.kill();
  83. if (typeof callback === 'function') {
  84. setImmediate(callback.bind(null, null, true));
  85. }
  86. }
  87. /**
  88. * Transform and emit an event comming from the poller.
  89. *
  90. * @param {EventEmitter} monitor
  91. * @public
  92. */
  93. emitEvent(type, file, stat) {
  94. this.emit(type, file, this.root, stat);
  95. this.emit(ALL_EVENT, type, file, this.root, stat);
  96. }
  97. }
  98. WatchexecWatcher._messageHandler = _messageHandler;
  99. module.exports = WatchexecWatcher;