nodemon.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. var debug = require('debug')('nodemon');
  2. var path = require('path');
  3. var monitor = require('./monitor');
  4. var cli = require('./cli');
  5. var version = require('./version');
  6. var util = require('util');
  7. var utils = require('./utils');
  8. var bus = utils.bus;
  9. var help = require('./help');
  10. var config = require('./config');
  11. var spawn = require('./spawn');
  12. const defaults = require('./config/defaults')
  13. var eventHandlers = {};
  14. // this is fairly dirty, but theoretically sound since it's part of the
  15. // stable module API
  16. config.required = utils.isRequired;
  17. function nodemon(settings) {
  18. bus.emit('boot');
  19. nodemon.reset();
  20. // allow the cli string as the argument to nodemon, and allow for
  21. // `node nodemon -V app.js` or just `-V app.js`
  22. if (typeof settings === 'string') {
  23. settings = settings.trim();
  24. if (settings.indexOf('node') !== 0) {
  25. if (settings.indexOf('nodemon') !== 0) {
  26. settings = 'nodemon ' + settings;
  27. }
  28. settings = 'node ' + settings;
  29. }
  30. settings = cli.parse(settings);
  31. }
  32. // set the debug flag as early as possible to get all the detailed logging
  33. if (settings.verbose) {
  34. utils.debug = true;
  35. }
  36. if (settings.help) {
  37. process.stdout._handle.setBlocking(true); // nodejs/node#6456
  38. console.log(help(settings.help));
  39. if (!config.required) {
  40. process.exit(0);
  41. }
  42. }
  43. if (settings.version) {
  44. version().then(function (v) {
  45. console.log(v);
  46. if (!config.required) {
  47. process.exit(0);
  48. }
  49. });
  50. return;
  51. }
  52. // nodemon tools like grunt-nodemon. This affects where
  53. // the script is being run from, and will affect where
  54. // nodemon looks for the nodemon.json files
  55. if (settings.cwd) {
  56. // this is protection to make sure we haven't dont the chdir already...
  57. // say like in cli/parse.js (which is where we do this once already!)
  58. if (process.cwd() !== path.resolve(config.system.cwd, settings.cwd)) {
  59. process.chdir(settings.cwd);
  60. }
  61. }
  62. const cwd = process.cwd();
  63. config.load(settings, function (config) {
  64. if (!config.options.dump && !config.options.execOptions.script &&
  65. config.options.execOptions.exec === 'node') {
  66. if (!config.required) {
  67. console.log(help('usage'));
  68. process.exit();
  69. }
  70. return;
  71. }
  72. // before we print anything, update the colour setting on logging
  73. utils.colours = config.options.colours;
  74. // always echo out the current version
  75. utils.log.info(version.pinned);
  76. const cwd = process.cwd();
  77. if (config.options.cwd) {
  78. utils.log.detail('process root: ' + cwd);
  79. }
  80. config.loaded.map(file => file.replace(cwd, '.')).forEach(file => {
  81. utils.log.detail('reading config ' + file);
  82. });
  83. if (config.options.stdin && config.options.restartable) {
  84. // allow nodemon to restart when the use types 'rs\n'
  85. process.stdin.resume();
  86. process.stdin.setEncoding('utf8');
  87. process.stdin.on('data', data => {
  88. const str = data.toString().trim().toLowerCase();
  89. // if the keys entered match the restartable value, then restart!
  90. if (str === config.options.restartable) {
  91. bus.emit('restart');
  92. } else if (data.charCodeAt(0) === 12) { // ctrl+l
  93. console.clear();
  94. }
  95. });
  96. } else if (config.options.stdin) {
  97. // so let's make sure we don't eat the key presses
  98. // but also, since we're wrapping, watch out for
  99. // special keys, like ctrl+c x 2 or '.exit' or ctrl+d or ctrl+l
  100. var ctrlC = false;
  101. var buffer = '';
  102. process.stdin.on('data', function (data) {
  103. data = data.toString();
  104. buffer += data;
  105. const chr = data.charCodeAt(0);
  106. // if restartable, echo back
  107. if (chr === 3) {
  108. if (ctrlC) {
  109. process.exit(0);
  110. }
  111. ctrlC = true;
  112. return;
  113. } else if (buffer === '.exit' || chr === 4) { // ctrl+d
  114. process.exit();
  115. } else if (chr === 13 || chr === 10) { // enter / carriage return
  116. buffer = '';
  117. } else if (chr === 12) { // ctrl+l
  118. console.clear();
  119. buffer = '';
  120. }
  121. ctrlC = false;
  122. });
  123. if (process.stdin.setRawMode) {
  124. process.stdin.setRawMode(true);
  125. }
  126. }
  127. if (config.options.restartable) {
  128. utils.log.info('to restart at any time, enter `' +
  129. config.options.restartable + '`');
  130. }
  131. if (!config.required) {
  132. const restartSignal = config.options.signal === 'SIGUSR2' ? 'SIGHUP' : 'SIGUSR2';
  133. process.on(restartSignal, nodemon.restart);
  134. utils.bus.on('error', () => {
  135. utils.log.fail((new Error().stack));
  136. });
  137. utils.log.detail((config.options.restartable ? 'or ' : '') + 'send ' +
  138. restartSignal + ' to ' + process.pid + ' to restart');
  139. }
  140. const ignoring = config.options.monitor.map(function (rule) {
  141. if (rule.slice(0, 1) !== '!') {
  142. return false;
  143. }
  144. rule = rule.slice(1);
  145. // don't notify of default ignores
  146. if (defaults.ignoreRoot.indexOf(rule) !== -1) {
  147. return false;
  148. return rule.slice(3).slice(0, -3);
  149. }
  150. if (rule.startsWith(cwd)) {
  151. return rule.replace(cwd, '.');
  152. }
  153. return rule;
  154. }).filter(Boolean).join(' ');
  155. if (ignoring) utils.log.detail('ignoring: ' + ignoring);
  156. utils.log.info('watching path(s): ' + config.options.monitor.map(function (rule) {
  157. if (rule.slice(0, 1) !== '!') {
  158. try {
  159. rule = path.relative(process.cwd(), rule);
  160. } catch (e) {}
  161. return rule;
  162. }
  163. return false;
  164. }).filter(Boolean).join(' '));
  165. utils.log.info('watching extensions: ' + (config.options.execOptions.ext || '(all)'));
  166. if (config.options.dump) {
  167. utils.log._log('log', '--------------');
  168. utils.log._log('log', 'node: ' + process.version);
  169. utils.log._log('log', 'nodemon: ' + version.pinned);
  170. utils.log._log('log', 'command: ' + process.argv.join(' '));
  171. utils.log._log('log', 'cwd: ' + cwd);
  172. utils.log._log('log', ['OS:', process.platform, process.arch].join(' '));
  173. utils.log._log('log', '--------------');
  174. utils.log._log('log', util.inspect(config, { depth: null }));
  175. utils.log._log('log', '--------------');
  176. if (!config.required) {
  177. process.exit();
  178. }
  179. return;
  180. }
  181. config.run = true;
  182. if (config.options.stdout === false) {
  183. nodemon.on('start', function () {
  184. nodemon.stdout = bus.stdout;
  185. nodemon.stderr = bus.stderr;
  186. bus.emit('readable');
  187. });
  188. }
  189. if (config.options.events && Object.keys(config.options.events).length) {
  190. Object.keys(config.options.events).forEach(function (key) {
  191. utils.log.detail('bind ' + key + ' -> `' +
  192. config.options.events[key] + '`');
  193. nodemon.on(key, function () {
  194. if (config.options && config.options.events) {
  195. spawn(config.options.events[key], config,
  196. [].slice.apply(arguments));
  197. }
  198. });
  199. });
  200. }
  201. monitor.run(config.options);
  202. });
  203. return nodemon;
  204. }
  205. nodemon.restart = function () {
  206. utils.log.status('restarting child process');
  207. bus.emit('restart');
  208. return nodemon;
  209. };
  210. nodemon.addListener = nodemon.on = function (event, handler) {
  211. if (!eventHandlers[event]) { eventHandlers[event] = []; }
  212. eventHandlers[event].push(handler);
  213. bus.on(event, handler);
  214. return nodemon;
  215. };
  216. nodemon.once = function (event, handler) {
  217. if (!eventHandlers[event]) { eventHandlers[event] = []; }
  218. eventHandlers[event].push(handler);
  219. bus.once(event, function () {
  220. debug('bus.once(%s)', event);
  221. eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 1);
  222. handler.apply(this, arguments);
  223. });
  224. return nodemon;
  225. };
  226. nodemon.emit = function () {
  227. bus.emit.apply(bus, [].slice.call(arguments));
  228. return nodemon;
  229. };
  230. nodemon.removeAllListeners = function (event) {
  231. // unbind only the `nodemon.on` event handlers
  232. Object.keys(eventHandlers).filter(function (e) {
  233. return event ? e === event : true;
  234. }).forEach(function (event) {
  235. eventHandlers[event].forEach(function (handler) {
  236. bus.removeListener(event, handler);
  237. eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 1);
  238. });
  239. });
  240. return nodemon;
  241. };
  242. nodemon.reset = function (done) {
  243. bus.emit('reset', done);
  244. };
  245. bus.on('reset', function (done) {
  246. debug('reset');
  247. nodemon.removeAllListeners();
  248. monitor.run.kill(true, function () {
  249. utils.reset();
  250. config.reset();
  251. config.run = false;
  252. if (done) {
  253. done();
  254. }
  255. });
  256. });
  257. // expose the full config
  258. nodemon.config = config;
  259. module.exports = nodemon;