match.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. const minimatch = require('minimatch');
  2. const path = require('path');
  3. const fs = require('fs');
  4. const debug = require('debug')('nodemon:match');
  5. const utils = require('../utils');
  6. module.exports = match;
  7. module.exports.rulesToMonitor = rulesToMonitor;
  8. function rulesToMonitor(watch, ignore, config) {
  9. var monitor = [];
  10. if (!Array.isArray(ignore)) {
  11. if (ignore) {
  12. ignore = [ignore];
  13. } else {
  14. ignore = [];
  15. }
  16. }
  17. if (!Array.isArray(watch)) {
  18. if (watch) {
  19. watch = [watch];
  20. } else {
  21. watch = [];
  22. }
  23. }
  24. if (watch && watch.length) {
  25. monitor = utils.clone(watch);
  26. }
  27. if (ignore) {
  28. [].push.apply(monitor, (ignore || []).map(function (rule) {
  29. return '!' + rule;
  30. }));
  31. }
  32. var cwd = process.cwd();
  33. // next check if the monitored paths are actual directories
  34. // or just patterns - and expand the rule to include *.*
  35. monitor = monitor.map(function (rule) {
  36. var not = rule.slice(0, 1) === '!';
  37. if (not) {
  38. rule = rule.slice(1);
  39. }
  40. if (rule === '.' || rule === '.*') {
  41. rule = '*.*';
  42. }
  43. var dir = path.resolve(cwd, rule);
  44. try {
  45. var stat = fs.statSync(dir);
  46. if (stat.isDirectory()) {
  47. rule = dir;
  48. if (rule.slice(-1) !== '/') {
  49. rule += '/';
  50. }
  51. rule += '**/*';
  52. // `!not` ... sorry.
  53. if (!not) {
  54. config.dirs.push(dir);
  55. }
  56. } else {
  57. // ensures we end up in the check that tries to get a base directory
  58. // and then adds it to the watch list
  59. throw new Error();
  60. }
  61. } catch (e) {
  62. var base = tryBaseDir(dir);
  63. if (!not && base) {
  64. if (config.dirs.indexOf(base) === -1) {
  65. config.dirs.push(base);
  66. }
  67. }
  68. }
  69. if (rule.slice(-1) === '/') {
  70. // just slap on a * anyway
  71. rule += '*';
  72. }
  73. // if the url ends with * but not **/* and not *.*
  74. // then convert to **/* - somehow it was missed :-\
  75. if (rule.slice(-4) !== '**/*' &&
  76. rule.slice(-1) === '*' &&
  77. rule.indexOf('*.') === -1) {
  78. if (rule.slice(-2) !== '**') {
  79. rule += '*/*';
  80. }
  81. }
  82. return (not ? '!' : '') + rule;
  83. });
  84. return monitor;
  85. }
  86. function tryBaseDir(dir) {
  87. var stat;
  88. if (/[?*\{\[]+/.test(dir)) { // if this is pattern, then try to find the base
  89. try {
  90. var base = path.dirname(dir.replace(/([?*\{\[]+.*$)/, 'foo'));
  91. stat = fs.statSync(base);
  92. if (stat.isDirectory()) {
  93. return base;
  94. }
  95. } catch (error) {
  96. // console.log(error);
  97. }
  98. } else {
  99. try {
  100. stat = fs.statSync(dir);
  101. // if this path is actually a single file that exists, then just monitor
  102. // that, *specifically*.
  103. if (stat.isFile() || stat.isDirectory()) {
  104. return dir;
  105. }
  106. } catch (e) { }
  107. }
  108. return false;
  109. }
  110. function match(files, monitor, ext) {
  111. // sort the rules by highest specificity (based on number of slashes)
  112. // ignore rules (!) get sorted highest as they take precedent
  113. const cwd = process.cwd();
  114. var rules = monitor.sort(function (a, b) {
  115. var r = b.split(path.sep).length - a.split(path.sep).length;
  116. var aIsIgnore = a.slice(0, 1) === '!';
  117. var bIsIgnore = b.slice(0, 1) === '!';
  118. if (aIsIgnore || bIsIgnore) {
  119. if (aIsIgnore) {
  120. return -1;
  121. }
  122. return 1;
  123. }
  124. if (r === 0) {
  125. return b.length - a.length;
  126. }
  127. return r;
  128. }).map(function (s) {
  129. var prefix = s.slice(0, 1);
  130. if (prefix === '!') {
  131. if (s.indexOf('!' + cwd) === 0) {
  132. return s;
  133. }
  134. // if it starts with a period, then let's get the relative path
  135. if (s.indexOf('!.') === 0) {
  136. return '!' + path.resolve(cwd, s.substring(1));
  137. }
  138. return '!**' + (prefix !== path.sep ? path.sep : '') + s.slice(1);
  139. }
  140. // if it starts with a period, then let's get the relative path
  141. if (s.indexOf('.') === 0) {
  142. return path.resolve(cwd, s);
  143. }
  144. if (s.indexOf(cwd) === 0) {
  145. return s;
  146. }
  147. return '**' + (prefix !== path.sep ? path.sep : '') + s;
  148. });
  149. debug('rules', rules);
  150. var good = [];
  151. var whitelist = []; // files that we won't check against the extension
  152. var ignored = 0;
  153. var watched = 0;
  154. var usedRules = [];
  155. var minimatchOpts = {
  156. dot: true,
  157. };
  158. // enable case-insensitivity on Windows
  159. if (utils.isWindows) {
  160. minimatchOpts.nocase = true;
  161. }
  162. files.forEach(function (file) {
  163. file = path.resolve(cwd, file);
  164. var matched = false;
  165. for (var i = 0; i < rules.length; i++) {
  166. if (rules[i].slice(0, 1) === '!') {
  167. if (!minimatch(file, rules[i], minimatchOpts)) {
  168. debug('ignored', file, 'rule:', rules[i]);
  169. ignored++;
  170. matched = true;
  171. break;
  172. }
  173. } else {
  174. debug('matched', file, 'rule:', rules[i]);
  175. if (minimatch(file, rules[i], minimatchOpts)) {
  176. watched++;
  177. // don't repeat the output if a rule is matched
  178. if (usedRules.indexOf(rules[i]) === -1) {
  179. usedRules.push(rules[i]);
  180. utils.log.detail('matched rule: ' + rules[i]);
  181. }
  182. // if the rule doesn't match the WATCH EVERYTHING
  183. // but *does* match a rule that ends with *.*, then
  184. // white list it - in that we don't run it through
  185. // the extension check too.
  186. if (rules[i] !== '**' + path.sep + '*.*' &&
  187. rules[i].slice(-3) === '*.*') {
  188. whitelist.push(file);
  189. } else if (path.basename(file) === path.basename(rules[i])) {
  190. // if the file matches the actual rule, then it's put on whitelist
  191. whitelist.push(file);
  192. } else {
  193. good.push(file);
  194. }
  195. matched = true;
  196. break;
  197. } else {
  198. // utils.log.detail('no match: ' + rules[i], file);
  199. }
  200. }
  201. }
  202. if (!matched) {
  203. ignored++;
  204. }
  205. });
  206. debug('good', good)
  207. // finally check the good files against the extensions that we're monitoring
  208. if (ext) {
  209. if (ext.indexOf(',') === -1) {
  210. ext = '**/*.' + ext;
  211. } else {
  212. ext = '**/*.{' + ext + '}';
  213. }
  214. good = good.filter(function (file) {
  215. // only compare the filename to the extension test
  216. return minimatch(path.basename(file), ext, minimatchOpts);
  217. });
  218. } // else assume *.*
  219. var result = good.concat(whitelist);
  220. if (utils.isWindows) {
  221. // fix for windows testing - I *think* this is okay to do
  222. result = result.map(function (file) {
  223. return file.slice(0, 1).toLowerCase() + file.slice(1);
  224. });
  225. }
  226. return {
  227. result: result,
  228. ignored: ignored,
  229. watched: watched,
  230. total: files.length,
  231. };
  232. }