UNPKG

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