watch.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * grunt-contrib-watch
  3. * http://gruntjs.com/
  4. *
  5. * Copyright (c) 2018 "Cowboy" Ben Alman, contributors
  6. * Licensed under the MIT license.
  7. */
  8. 'use strict';
  9. var path = require('path');
  10. var Gaze = require('gaze').Gaze;
  11. var _ = require('lodash');
  12. var waiting = 'Waiting...';
  13. var changedFiles = Object.create(null);
  14. var watchers = [];
  15. module.exports = function(grunt) {
  16. var taskrun = require('./lib/taskrunner')(grunt);
  17. // Default date format logged
  18. var dateFormat = function(time) {
  19. grunt.log.writeln(String(
  20. 'Completed in ' +
  21. time.toFixed(3) +
  22. 's at ' +
  23. (new Date()).toString()
  24. ).cyan + ' - ' + waiting);
  25. };
  26. // When task runner has started
  27. taskrun.on('start', function() {
  28. Object.keys(changedFiles).forEach(function(filepath) {
  29. // Log which file has changed, and how.
  30. grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
  31. });
  32. // Reset changedFiles
  33. changedFiles = Object.create(null);
  34. });
  35. // When task runner has ended
  36. taskrun.on('end', function(time) {
  37. if (time > 0) {
  38. dateFormat(time);
  39. }
  40. });
  41. // When a task run has been interrupted
  42. taskrun.on('interrupt', function() {
  43. grunt.log.writeln('').write('Scheduled tasks have been interrupted...'.yellow);
  44. });
  45. // When taskrun is reloaded
  46. taskrun.on('reload', function() {
  47. taskrun.clearRequireCache(Object.keys(changedFiles));
  48. grunt.log.writeln('').writeln('Reloading watch config...'.cyan);
  49. });
  50. grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
  51. var self = this;
  52. var name = self.name || 'watch';
  53. // Close any previously opened watchers
  54. watchers.forEach(function(watcher) {
  55. watcher.close();
  56. });
  57. watchers = [];
  58. // Never gonna give you up, never gonna let you down
  59. if (grunt.config([name, 'options', 'forever']) !== false) {
  60. taskrun.forever();
  61. }
  62. // If a custom dateFormat function
  63. var df = grunt.config([name, 'options', 'dateFormat']);
  64. if (typeof df === 'function') {
  65. dateFormat = df;
  66. }
  67. if (taskrun.running === false) {
  68. grunt.log.writeln(waiting);
  69. }
  70. // Initialize taskrun
  71. var targets = taskrun.init(name, {target: target});
  72. targets.forEach(function(target) {
  73. if (typeof target.files === 'string') {
  74. target.files = [target.files];
  75. }
  76. // Process into raw patterns
  77. var patterns = _.chain(target.files).flatten().map(function(pattern) {
  78. return grunt.config.process(pattern);
  79. }).value();
  80. // Validate the event option
  81. if (typeof target.options.event === 'string') {
  82. target.options.event = [target.options.event];
  83. }
  84. var eventCwd = process.cwd();
  85. if (target.options.cwd && target.options.cwd.event) {
  86. eventCwd = target.options.cwd.event;
  87. }
  88. // Set cwd if options.cwd.file is set
  89. if (typeof target.options.cwd !== 'string' && target.options.cwd.files) {
  90. target.options.cwd = target.options.cwd.files;
  91. }
  92. // Create watcher per target
  93. watchers.push(new Gaze(patterns, target.options, function(err) {
  94. if (err) {
  95. if (typeof err === 'string') {
  96. err = new Error(err);
  97. }
  98. grunt.log.writeln('ERROR'.red);
  99. grunt.fatal(err);
  100. return taskrun.done();
  101. }
  102. // Log all watched files with --verbose set
  103. if (grunt.option('verbose')) {
  104. var watched = this.watched();
  105. Object.keys(watched).forEach(function(watchedDir) {
  106. watched[watchedDir].forEach(function(watchedFile) {
  107. grunt.log.writeln('Watching ' + path.relative(process.cwd(), watchedFile) + ' for changes.');
  108. });
  109. });
  110. }
  111. // On changed/added/deleted
  112. this.on('all', function(status, filepath) {
  113. // Skip events not specified
  114. if (!_.includes(target.options.event, 'all') &&
  115. !_.includes(target.options.event, status)) {
  116. return;
  117. }
  118. filepath = path.relative(eventCwd, filepath);
  119. // Skip empty filepaths
  120. if (filepath === '') {
  121. return;
  122. }
  123. // If Gruntfile.js changed, reload self task
  124. if (target.options.reload || /gruntfile\.(js|coffee)/i.test(filepath)) {
  125. taskrun.reload = true;
  126. }
  127. // Emit watch events if anyone is listening
  128. if (grunt.event.listeners('watch').length > 0) {
  129. grunt.event.emit('watch', status, filepath, target.name);
  130. }
  131. // Group changed files only for display
  132. changedFiles[filepath] = status;
  133. // Add changed files to the target
  134. if (taskrun.targets[target.name]) {
  135. if (!taskrun.targets[target.name].changedFiles) {
  136. taskrun.targets[target.name].changedFiles = Object.create(null);
  137. }
  138. taskrun.targets[target.name].changedFiles[filepath] = status;
  139. }
  140. // Queue the target
  141. if (taskrun.queue.indexOf(target.name) === -1) {
  142. taskrun.queue.push(target.name);
  143. }
  144. // Run the tasks
  145. taskrun.run();
  146. });
  147. // On watcher error
  148. this.on('error', function(err) {
  149. if (typeof err === 'string') {
  150. err = new Error(err);
  151. }
  152. grunt.log.error(err.message);
  153. });
  154. }));
  155. });
  156. });
  157. };