UNPKG

10.8 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} Watcher
66 * @property {function(): void} close closes the watcher and all underlying file watchers
67 * @property {function(): void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call
68 * @property {function(): Set<string>=} getAggregatedChanges get current aggregated changes that have not yet send to callback
69 * @property {function(): Set<string>=} getAggregatedRemovals get current aggregated removals that have not yet send to callback
70 * @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getFileTimeInfoEntries get info about files
71 * @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getContextTimeInfoEntries get info about directories
72 */
73
74/**
75 * @callback WatchMethod
76 * @param {Iterable<string>} files watched files
77 * @param {Iterable<string>} directories watched directories
78 * @param {Iterable<string>} missing watched exitance entries
79 * @param {number} startTime timestamp of start time
80 * @param {WatchOptions} options options object
81 * @param {function(Error=, Map<string, FileSystemInfoEntry | "ignore">, Map<string, FileSystemInfoEntry | "ignore">, Set<string>, Set<string>): void} callback aggregated callback
82 * @param {function(string, number): void} callbackUndelayed callback when the first change was detected
83 * @returns {Watcher} a watcher
84 */
85
86// TODO webpack 6 make optional methods required
87
88/**
89 * @typedef {Object} OutputFileSystem
90 * @property {function(string, Buffer|string, Callback): void} writeFile
91 * @property {function(string, Callback): void} mkdir
92 * @property {function(string, DirentArrayCallback): void=} readdir
93 * @property {function(string, Callback): void=} rmdir
94 * @property {function(string, Callback): void=} unlink
95 * @property {function(string, StatsCallback): void} stat
96 * @property {function(string, BufferOrStringCallback): void} readFile
97 * @property {(function(string, string): string)=} join
98 * @property {(function(string, string): string)=} relative
99 * @property {(function(string): string)=} dirname
100 */
101
102/**
103 * @typedef {Object} InputFileSystem
104 * @property {function(string, BufferOrStringCallback): void} readFile
105 * @property {(function(string, ReadJsonCallback): void)=} readJson
106 * @property {function(string, BufferOrStringCallback): void} readlink
107 * @property {function(string, DirentArrayCallback): void} readdir
108 * @property {function(string, StatsCallback): void} stat
109 * @property {function(string, StatsCallback): void=} lstat
110 * @property {(function(string, BufferOrStringCallback): void)=} realpath
111 * @property {(function(string=): void)=} purge
112 * @property {(function(string, string): string)=} join
113 * @property {(function(string, string): string)=} relative
114 * @property {(function(string): string)=} dirname
115 */
116
117/**
118 * @typedef {Object} WatchFileSystem
119 * @property {WatchMethod} watch
120 */
121
122/**
123 * @typedef {Object} IntermediateFileSystemExtras
124 * @property {function(string): void} mkdirSync
125 * @property {function(string): NodeJS.WritableStream} createWriteStream
126 * @property {function(string, string, NumberCallback): void} open
127 * @property {function(number, Buffer, number, number, number, NumberCallback): void} read
128 * @property {function(number, Callback): void} close
129 * @property {function(string, string, Callback): void} rename
130 */
131
132/** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */
133
134/**
135 *
136 * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
137 * @param {string} rootPath the root path
138 * @param {string} targetPath the target path
139 * @returns {string} location of targetPath relative to rootPath
140 */
141const relative = (fs, rootPath, targetPath) => {
142 if (fs && fs.relative) {
143 return fs.relative(rootPath, targetPath);
144 } else if (path.posix.isAbsolute(rootPath)) {
145 return path.posix.relative(rootPath, targetPath);
146 } else if (path.win32.isAbsolute(rootPath)) {
147 return path.win32.relative(rootPath, targetPath);
148 } else {
149 throw new Error(
150 `${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system`
151 );
152 }
153};
154exports.relative = relative;
155
156/**
157 * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
158 * @param {string} rootPath a path
159 * @param {string} filename a filename
160 * @returns {string} the joined path
161 */
162const join = (fs, rootPath, filename) => {
163 if (fs && fs.join) {
164 return fs.join(rootPath, filename);
165 } else if (path.posix.isAbsolute(rootPath)) {
166 return path.posix.join(rootPath, filename);
167 } else if (path.win32.isAbsolute(rootPath)) {
168 return path.win32.join(rootPath, filename);
169 } else {
170 throw new Error(
171 `${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system`
172 );
173 }
174};
175exports.join = join;
176
177/**
178 * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system
179 * @param {string} absPath an absolute path
180 * @returns {string} the parent directory of the absolute path
181 */
182const dirname = (fs, absPath) => {
183 if (fs && fs.dirname) {
184 return fs.dirname(absPath);
185 } else if (path.posix.isAbsolute(absPath)) {
186 return path.posix.dirname(absPath);
187 } else if (path.win32.isAbsolute(absPath)) {
188 return path.win32.dirname(absPath);
189 } else {
190 throw new Error(
191 `${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system`
192 );
193 }
194};
195exports.dirname = dirname;
196
197/**
198 * @param {OutputFileSystem} fs a file system
199 * @param {string} p an absolute path
200 * @param {function(Error=): void} callback callback function for the error
201 * @returns {void}
202 */
203const mkdirp = (fs, p, callback) => {
204 fs.mkdir(p, err => {
205 if (err) {
206 if (err.code === "ENOENT") {
207 const dir = dirname(fs, p);
208 if (dir === p) {
209 callback(err);
210 return;
211 }
212 mkdirp(fs, dir, err => {
213 if (err) {
214 callback(err);
215 return;
216 }
217 fs.mkdir(p, err => {
218 if (err) {
219 if (err.code === "EEXIST") {
220 callback();
221 return;
222 }
223 callback(err);
224 return;
225 }
226 callback();
227 });
228 });
229 return;
230 } else if (err.code === "EEXIST") {
231 callback();
232 return;
233 }
234 callback(err);
235 return;
236 }
237 callback();
238 });
239};
240exports.mkdirp = mkdirp;
241
242/**
243 * @param {IntermediateFileSystem} fs a file system
244 * @param {string} p an absolute path
245 * @returns {void}
246 */
247const mkdirpSync = (fs, p) => {
248 try {
249 fs.mkdirSync(p);
250 } catch (err) {
251 if (err) {
252 if (err.code === "ENOENT") {
253 const dir = dirname(fs, p);
254 if (dir === p) {
255 throw err;
256 }
257 mkdirpSync(fs, dir);
258 fs.mkdirSync(p);
259 return;
260 } else if (err.code === "EEXIST") {
261 return;
262 }
263 throw err;
264 }
265 }
266};
267exports.mkdirpSync = mkdirpSync;
268
269/**
270 * @param {InputFileSystem} fs a file system
271 * @param {string} p an absolute path
272 * @param {ReadJsonCallback} callback callback
273 * @returns {void}
274 */
275const readJson = (fs, p, callback) => {
276 if ("readJson" in fs) return fs.readJson(p, callback);
277 fs.readFile(p, (err, buf) => {
278 if (err) return callback(err);
279 let data;
280 try {
281 data = JSON.parse(buf.toString("utf-8"));
282 } catch (e) {
283 return callback(e);
284 }
285 return callback(null, data);
286 });
287};
288exports.readJson = readJson;
289
290/**
291 * @param {InputFileSystem} fs a file system
292 * @param {string} p an absolute path
293 * @param {ReadJsonCallback} callback callback
294 * @returns {void}
295 */
296const lstatReadlinkAbsolute = (fs, p, callback) => {
297 let i = 3;
298 const doReadLink = () => {
299 fs.readlink(p, (err, target) => {
300 if (err && --i > 0) {
301 // It might was just changed from symlink to file
302 // we retry 2 times to catch this case before throwing the error
303 return doStat();
304 }
305 if (err || !target) return doStat();
306 const value = target.toString();
307 callback(null, join(fs, dirname(fs, p), value));
308 });
309 };
310 const doStat = () => {
311 if ("lstat" in fs) {
312 return fs.lstat(p, (err, stats) => {
313 if (err) return callback(err);
314 if (stats.isSymbolicLink()) {
315 return doReadLink();
316 }
317 callback(null, stats);
318 });
319 } else {
320 return fs.stat(p, callback);
321 }
322 };
323 if ("lstat" in fs) return doStat();
324 doReadLink();
325};
326exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute;