UNPKG

4.83 kBJavaScriptView Raw
1'use strict';
2var assign = require('object-assign');
3var path = require('path');
4var PluginError = require('plugin-error');
5var fancyLog = require('fancy-log');
6var colors = require('ansi-colors');
7var chokidar = require('chokidar');
8var Duplex = require('readable-stream').Duplex;
9var vinyl = require('vinyl-file');
10var File = require('vinyl');
11var anymatch = require('anymatch');
12var pathIsAbsolute = require('path-is-absolute');
13var globParent = require('glob-parent');
14var slash = require('slash');
15
16function normalizeGlobs(globs) {
17 if (!globs) {
18 throw new PluginError('gulp-watch', 'glob argument required');
19 }
20
21 if (typeof globs === 'string') {
22 globs = [globs];
23 }
24
25 if (!Array.isArray(globs)) {
26 throw new PluginError('gulp-watch', 'glob should be String or Array, not ' + (typeof globs));
27 }
28
29 return globs;
30}
31
32function watch(globs, opts, cb) {
33 var originalGlobs = globs;
34 globs = normalizeGlobs(globs);
35
36 if (typeof opts === 'function') {
37 cb = opts;
38 opts = {};
39 }
40
41 opts = assign({}, watch._defaultOptions, opts);
42 cb = cb || function () {};
43
44 function resolveFilepath(filepath) {
45 if (pathIsAbsolute(filepath)) {
46 return path.normalize(filepath);
47 }
48 return path.resolve(opts.cwd || process.cwd(), filepath);
49 }
50
51 function resolveGlob(glob) {
52 var mod = '';
53
54 if (glob[0] === '!') {
55 mod = glob[0];
56 glob = glob.slice(1);
57 }
58
59 return mod + slash(resolveFilepath(glob));
60 }
61 globs = globs.map(resolveGlob);
62
63 var baseForced = Boolean(opts.base);
64 var outputStream = new Duplex({objectMode: true, allowHalfOpen: true});
65
66 outputStream._write = function _write(file, enc, done) {
67 cb(file);
68 this.push(file);
69 done();
70 };
71
72 outputStream._read = function _read() { };
73
74 var watcher = chokidar.watch(globs, opts);
75
76 opts.events.forEach(function (ev) {
77 watcher.on(ev, processEvent.bind(undefined, ev));
78 });
79
80 ['add', 'change', 'unlink', 'addDir', 'unlinkDir', 'error', 'ready', 'raw']
81 .forEach(function (ev) {
82 watcher.on(ev, outputStream.emit.bind(outputStream, ev));
83 });
84
85 outputStream.add = function add(newGlobs) {
86 newGlobs = normalizeGlobs(newGlobs)
87 .map(resolveGlob);
88 watcher.add(newGlobs);
89 globs.push.apply(globs, newGlobs);
90 };
91 outputStream.unwatch = watcher.unwatch.bind(watcher);
92 outputStream.close = function () {
93 watcher.close();
94 this.emit('end');
95 };
96
97 function processEvent(event, filepath) {
98 filepath = resolveFilepath(filepath);
99 var fileOpts = assign({}, opts);
100
101 var glob;
102 var currentFilepath = filepath;
103 while (!(glob = globs[anymatch(globs, currentFilepath, true)]) && currentFilepath !== (currentFilepath = path.dirname(currentFilepath))) {} // eslint-disable-line no-empty-blocks/no-empty-blocks
104
105 if (!glob) {
106 fancyLog.info(
107 colors.cyan('[gulp-watch]'),
108 colors.yellow('Watched unexpected path. This is likely a bug. Please open this link to report the issue:\n') +
109 'https://github.com/floatdrop/gulp-watch/issues/new?title=' +
110 encodeURIComponent('Watched unexpected filepath') + '&body=' +
111 encodeURIComponent('Node.js version: `' + process.version + ' ' + process.platform + ' ' + process.arch + '`\ngulp-watch version: `' + require('./package.json').version + '`\nGlobs: `' + JSON.stringify(originalGlobs) + '`\nFilepath: `' + filepath + '`\nEvent: `' + event + '`\nProcess CWD: `' + process.cwd() + '`\nOptions:\n```js\n' + JSON.stringify(opts, null, 2) + '\n```')
112 );
113 return;
114 }
115
116 if (!baseForced) {
117 fileOpts.base = path.normalize(globParent(glob));
118 }
119
120 // Do not stat deleted files
121 if (event === 'unlink' || event === 'unlinkDir') {
122 fileOpts.path = filepath;
123
124 write(event, null, new File(fileOpts));
125 return;
126 }
127
128 // Workaround for early read
129 setTimeout(function () {
130 vinyl.read(filepath, fileOpts).then(function (file) {
131 write(event, null, file);
132 });
133 }, opts.readDelay);
134 }
135
136 function write(event, err, file) {
137 if (err) {
138 outputStream.emit('error', err);
139 return;
140 }
141
142 if (opts.verbose) {
143 log(event, file);
144 }
145
146 file.event = event;
147 outputStream.push(file);
148 cb(file);
149 }
150
151 function log(event, file) {
152 event = event[event.length - 1] === 'e' ? event + 'd' : event + 'ed';
153
154 var msg = [colors.magenta(file.relative), 'was', event];
155
156 if (opts.name) {
157 msg.unshift(colors.cyan(opts.name) + ' saw');
158 }
159
160 fancyLog.info.apply(null, msg);
161 }
162
163 return outputStream;
164}
165
166// This is not part of the public API as that would lead to global state (singleton) pollution,
167// and allow unexpected interference between unrelated modules that make use of gulp-watch.
168// This can be useful for unit tests and root application configuration, though.
169// Avoid modifying gulp-watch's default options inside a library/reusable package, please.
170watch._defaultOptions = {
171 events: ['add', 'change', 'unlink'],
172 ignoreInitial: true,
173 readDelay: 10
174};
175
176module.exports = watch;