1 | /*
|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
3 | Author Tobias Koppers @sokra
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | const path = require("path");
|
9 |
|
10 | /** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */
|
11 | /** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */
|
12 |
|
13 | /**
|
14 | * @typedef {Object} IStats
|
15 | * @property {() => boolean} isFile
|
16 | * @property {() => boolean} isDirectory
|
17 | * @property {() => boolean} isBlockDevice
|
18 | * @property {() => boolean} isCharacterDevice
|
19 | * @property {() => boolean} isSymbolicLink
|
20 | * @property {() => boolean} isFIFO
|
21 | * @property {() => boolean} isSocket
|
22 | * @property {number | bigint} dev
|
23 | * @property {number | bigint} ino
|
24 | * @property {number | bigint} mode
|
25 | * @property {number | bigint} nlink
|
26 | * @property {number | bigint} uid
|
27 | * @property {number | bigint} gid
|
28 | * @property {number | bigint} rdev
|
29 | * @property {number | bigint} size
|
30 | * @property {number | bigint} blksize
|
31 | * @property {number | bigint} blocks
|
32 | * @property {number | bigint} atimeMs
|
33 | * @property {number | bigint} mtimeMs
|
34 | * @property {number | bigint} ctimeMs
|
35 | * @property {number | bigint} birthtimeMs
|
36 | * @property {Date} atime
|
37 | * @property {Date} mtime
|
38 | * @property {Date} ctime
|
39 | * @property {Date} birthtime
|
40 | */
|
41 |
|
42 | /**
|
43 | * @typedef {Object} IDirent
|
44 | * @property {() => boolean} isFile
|
45 | * @property {() => boolean} isDirectory
|
46 | * @property {() => boolean} isBlockDevice
|
47 | * @property {() => boolean} isCharacterDevice
|
48 | * @property {() => boolean} isSymbolicLink
|
49 | * @property {() => boolean} isFIFO
|
50 | * @property {() => boolean} isSocket
|
51 | * @property {string | Buffer} name
|
52 | */
|
53 |
|
54 | /** @typedef {function((NodeJS.ErrnoException | null)=): void} Callback */
|
55 | /** @typedef {function((NodeJS.ErrnoException | null)=, Buffer=): void} BufferCallback */
|
56 | /** @typedef {function((NodeJS.ErrnoException | null)=, Buffer|string=): void} BufferOrStringCallback */
|
57 | /** @typedef {function((NodeJS.ErrnoException | null)=, (string | Buffer)[] | IDirent[]=): void} DirentArrayCallback */
|
58 | /** @typedef {function((NodeJS.ErrnoException | null)=, string=): void} StringCallback */
|
59 | /** @typedef {function((NodeJS.ErrnoException | null)=, number=): void} NumberCallback */
|
60 | /** @typedef {function((NodeJS.ErrnoException | null)=, IStats=): void} StatsCallback */
|
61 | /** @typedef {function((NodeJS.ErrnoException | Error | null)=, any=): void} ReadJsonCallback */
|
62 | /** @typedef {function((NodeJS.ErrnoException | Error | null)=, IStats|string=): void} LstatReadlinkAbsoluteCallback */
|
63 |
|
64 | /**
|
65 | * @typedef {Object} WatcherInfo
|
66 | * @property {Set<string>} changes get current aggregated changes that have not yet send to callback
|
67 | * @property {Set<string>} removals get current aggregated removals that have not yet send to callback
|
68 | * @property {Map<string, FileSystemInfoEntry | "ignore">} fileTimeInfoEntries get info about files
|
69 | * @property {Map<string, FileSystemInfoEntry | "ignore">} contextTimeInfoEntries get info about directories
|
70 | */
|
71 |
|
72 | // TODO webpack 6 deprecate missing getInfo
|
73 | /**
|
74 | * @typedef {Object} Watcher
|
75 | * @property {function(): void} close closes the watcher and all underlying file watchers
|
76 | * @property {function(): void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call
|
77 | * @property {function(): Set<string>=} getAggregatedChanges get current aggregated changes that have not yet send to callback
|
78 | * @property {function(): Set<string>=} getAggregatedRemovals get current aggregated removals that have not yet send to callback
|
79 | * @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getFileTimeInfoEntries get info about files
|
80 | * @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getContextTimeInfoEntries get info about directories
|
81 | * @property {function(): WatcherInfo=} getInfo get info about timestamps and changes
|
82 | */
|
83 |
|
84 | /**
|
85 | * @callback WatchMethod
|
86 | * @param {Iterable<string>} files watched files
|
87 | * @param {Iterable<string>} directories watched directories
|
88 | * @param {Iterable<string>} missing watched exitance entries
|
89 | * @param {number} startTime timestamp of start time
|
90 | * @param {WatchOptions} options options object
|
91 | * @param {function(Error=, Map<string, FileSystemInfoEntry | "ignore">, Map<string, FileSystemInfoEntry | "ignore">, Set<string>, Set<string>): void} callback aggregated callback
|
92 | * @param {function(string, number): void} callbackUndelayed callback when the first change was detected
|
93 | * @returns {Watcher} a watcher
|
94 | */
|
95 |
|
96 | // TODO webpack 6 make optional methods required
|
97 |
|
98 | /**
|
99 | * @typedef {Object} OutputFileSystem
|
100 | * @property {function(string, Buffer|string, Callback): void} writeFile
|
101 | * @property {function(string, Callback): void} mkdir
|
102 | * @property {function(string, DirentArrayCallback): void=} readdir
|
103 | * @property {function(string, Callback): void=} rmdir
|
104 | * @property {function(string, Callback): void=} unlink
|
105 | * @property {function(string, StatsCallback): void} stat
|
106 | * @property {function(string, StatsCallback): void=} lstat
|
107 | * @property {function(string, BufferOrStringCallback): void} readFile
|
108 | * @property {(function(string, string): string)=} join
|
109 | * @property {(function(string, string): string)=} relative
|
110 | * @property {(function(string): string)=} dirname
|
111 | */
|
112 |
|
113 | /**
|
114 | * @typedef {Object} InputFileSystem
|
115 | * @property {function(string, BufferOrStringCallback): void} readFile
|
116 | * @property {(function(string, ReadJsonCallback): void)=} readJson
|
117 | * @property {function(string, BufferOrStringCallback): void} readlink
|
118 | * @property {function(string, DirentArrayCallback): void} readdir
|
119 | * @property {function(string, StatsCallback): void} stat
|
120 | * @property {function(string, StatsCallback): void=} lstat
|
121 | * @property {(function(string, BufferOrStringCallback): void)=} realpath
|
122 | * @property {(function(string=): void)=} purge
|
123 | * @property {(function(string, string): string)=} join
|
124 | * @property {(function(string, string): string)=} relative
|
125 | * @property {(function(string): string)=} dirname
|
126 | */
|
127 |
|
128 | /**
|
129 | * @typedef {Object} WatchFileSystem
|
130 | * @property {WatchMethod} watch
|
131 | */
|
132 |
|
133 | /**
|
134 | * @typedef {Object} IntermediateFileSystemExtras
|
135 | * @property {function(string): void} mkdirSync
|
136 | * @property {function(string): NodeJS.WritableStream} createWriteStream
|
137 | * @property {function(string, string, NumberCallback): void} open
|
138 | * @property {function(number, Buffer, number, number, number, NumberCallback): void} read
|
139 | * @property {function(number, Callback): void} close
|
140 | * @property {function(string, string, Callback): void} rename
|
141 | */
|
142 |
|
143 | /** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */
|
144 |
|
145 | /**
|
146 | *
|
147 | * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
|
148 | * @param {string} rootPath the root path
|
149 | * @param {string} targetPath the target path
|
150 | * @returns {string} location of targetPath relative to rootPath
|
151 | */
|
152 | const relative = (fs, rootPath, targetPath) => {
|
153 | if (fs && fs.relative) {
|
154 | return fs.relative(rootPath, targetPath);
|
155 | } else if (path.posix.isAbsolute(rootPath)) {
|
156 | return path.posix.relative(rootPath, targetPath);
|
157 | } else if (path.win32.isAbsolute(rootPath)) {
|
158 | return path.win32.relative(rootPath, targetPath);
|
159 | } else {
|
160 | throw new Error(
|
161 | `${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system`
|
162 | );
|
163 | }
|
164 | };
|
165 | exports.relative = relative;
|
166 |
|
167 | /**
|
168 | * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
|
169 | * @param {string} rootPath a path
|
170 | * @param {string} filename a filename
|
171 | * @returns {string} the joined path
|
172 | */
|
173 | const join = (fs, rootPath, filename) => {
|
174 | if (fs && fs.join) {
|
175 | return fs.join(rootPath, filename);
|
176 | } else if (path.posix.isAbsolute(rootPath)) {
|
177 | return path.posix.join(rootPath, filename);
|
178 | } else if (path.win32.isAbsolute(rootPath)) {
|
179 | return path.win32.join(rootPath, filename);
|
180 | } else {
|
181 | throw new Error(
|
182 | `${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system`
|
183 | );
|
184 | }
|
185 | };
|
186 | exports.join = join;
|
187 |
|
188 | /**
|
189 | * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
|
190 | * @param {string} absPath an absolute path
|
191 | * @returns {string} the parent directory of the absolute path
|
192 | */
|
193 | const dirname = (fs, absPath) => {
|
194 | if (fs && fs.dirname) {
|
195 | return fs.dirname(absPath);
|
196 | } else if (path.posix.isAbsolute(absPath)) {
|
197 | return path.posix.dirname(absPath);
|
198 | } else if (path.win32.isAbsolute(absPath)) {
|
199 | return path.win32.dirname(absPath);
|
200 | } else {
|
201 | throw new Error(
|
202 | `${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system`
|
203 | );
|
204 | }
|
205 | };
|
206 | exports.dirname = dirname;
|
207 |
|
208 | /**
|
209 | * @param {OutputFileSystem} fs a file system
|
210 | * @param {string} p an absolute path
|
211 | * @param {function(Error=): void} callback callback function for the error
|
212 | * @returns {void}
|
213 | */
|
214 | const mkdirp = (fs, p, callback) => {
|
215 | fs.mkdir(p, err => {
|
216 | if (err) {
|
217 | if (err.code === "ENOENT") {
|
218 | const dir = dirname(fs, p);
|
219 | if (dir === p) {
|
220 | callback(err);
|
221 | return;
|
222 | }
|
223 | mkdirp(fs, dir, err => {
|
224 | if (err) {
|
225 | callback(err);
|
226 | return;
|
227 | }
|
228 | fs.mkdir(p, err => {
|
229 | if (err) {
|
230 | if (err.code === "EEXIST") {
|
231 | callback();
|
232 | return;
|
233 | }
|
234 | callback(err);
|
235 | return;
|
236 | }
|
237 | callback();
|
238 | });
|
239 | });
|
240 | return;
|
241 | } else if (err.code === "EEXIST") {
|
242 | callback();
|
243 | return;
|
244 | }
|
245 | callback(err);
|
246 | return;
|
247 | }
|
248 | callback();
|
249 | });
|
250 | };
|
251 | exports.mkdirp = mkdirp;
|
252 |
|
253 | /**
|
254 | * @param {IntermediateFileSystem} fs a file system
|
255 | * @param {string} p an absolute path
|
256 | * @returns {void}
|
257 | */
|
258 | const mkdirpSync = (fs, p) => {
|
259 | try {
|
260 | fs.mkdirSync(p);
|
261 | } catch (err) {
|
262 | if (err) {
|
263 | if (err.code === "ENOENT") {
|
264 | const dir = dirname(fs, p);
|
265 | if (dir === p) {
|
266 | throw err;
|
267 | }
|
268 | mkdirpSync(fs, dir);
|
269 | fs.mkdirSync(p);
|
270 | return;
|
271 | } else if (err.code === "EEXIST") {
|
272 | return;
|
273 | }
|
274 | throw err;
|
275 | }
|
276 | }
|
277 | };
|
278 | exports.mkdirpSync = mkdirpSync;
|
279 |
|
280 | /**
|
281 | * @param {InputFileSystem} fs a file system
|
282 | * @param {string} p an absolute path
|
283 | * @param {ReadJsonCallback} callback callback
|
284 | * @returns {void}
|
285 | */
|
286 | const readJson = (fs, p, callback) => {
|
287 | if ("readJson" in fs) return fs.readJson(p, callback);
|
288 | fs.readFile(p, (err, buf) => {
|
289 | if (err) return callback(err);
|
290 | let data;
|
291 | try {
|
292 | data = JSON.parse(buf.toString("utf-8"));
|
293 | } catch (e) {
|
294 | return callback(e);
|
295 | }
|
296 | return callback(null, data);
|
297 | });
|
298 | };
|
299 | exports.readJson = readJson;
|
300 |
|
301 | /**
|
302 | * @param {InputFileSystem} fs a file system
|
303 | * @param {string} p an absolute path
|
304 | * @param {ReadJsonCallback} callback callback
|
305 | * @returns {void}
|
306 | */
|
307 | const lstatReadlinkAbsolute = (fs, p, callback) => {
|
308 | let i = 3;
|
309 | const doReadLink = () => {
|
310 | fs.readlink(p, (err, target) => {
|
311 | if (err && --i > 0) {
|
312 | // It might was just changed from symlink to file
|
313 | // we retry 2 times to catch this case before throwing the error
|
314 | return doStat();
|
315 | }
|
316 | if (err || !target) return doStat();
|
317 | const value = target.toString();
|
318 | callback(null, join(fs, dirname(fs, p), value));
|
319 | });
|
320 | };
|
321 | const doStat = () => {
|
322 | if ("lstat" in fs) {
|
323 | return fs.lstat(p, (err, stats) => {
|
324 | if (err) return callback(err);
|
325 | if (stats.isSymbolicLink()) {
|
326 | return doReadLink();
|
327 | }
|
328 | callback(null, stats);
|
329 | });
|
330 | } else {
|
331 | return fs.stat(p, callback);
|
332 | }
|
333 | };
|
334 | if ("lstat" in fs) return doStat();
|
335 | doReadLink();
|
336 | };
|
337 | exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute;
|