UNPKG

11.4 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const 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 */
152const 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};
165exports.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 */
173const 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};
186exports.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 */
193const 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};
206exports.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 */
214const 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};
251exports.mkdirp = mkdirp;
252
253/**
254 * @param {IntermediateFileSystem} fs a file system
255 * @param {string} p an absolute path
256 * @returns {void}
257 */
258const 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};
278exports.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 */
286const 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};
299exports.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 */
307const 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};
337exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute;