You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
11 KiB
337 lines
11 KiB
/* |
|
MIT License http://www.opensource.org/licenses/mit-license.php |
|
Author Tobias Koppers @sokra |
|
*/ |
|
|
|
"use strict"; |
|
|
|
const path = require("path"); |
|
|
|
/** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */ |
|
/** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ |
|
|
|
/** |
|
* @typedef {Object} IStats |
|
* @property {() => boolean} isFile |
|
* @property {() => boolean} isDirectory |
|
* @property {() => boolean} isBlockDevice |
|
* @property {() => boolean} isCharacterDevice |
|
* @property {() => boolean} isSymbolicLink |
|
* @property {() => boolean} isFIFO |
|
* @property {() => boolean} isSocket |
|
* @property {number | bigint} dev |
|
* @property {number | bigint} ino |
|
* @property {number | bigint} mode |
|
* @property {number | bigint} nlink |
|
* @property {number | bigint} uid |
|
* @property {number | bigint} gid |
|
* @property {number | bigint} rdev |
|
* @property {number | bigint} size |
|
* @property {number | bigint} blksize |
|
* @property {number | bigint} blocks |
|
* @property {number | bigint} atimeMs |
|
* @property {number | bigint} mtimeMs |
|
* @property {number | bigint} ctimeMs |
|
* @property {number | bigint} birthtimeMs |
|
* @property {Date} atime |
|
* @property {Date} mtime |
|
* @property {Date} ctime |
|
* @property {Date} birthtime |
|
*/ |
|
|
|
/** |
|
* @typedef {Object} IDirent |
|
* @property {() => boolean} isFile |
|
* @property {() => boolean} isDirectory |
|
* @property {() => boolean} isBlockDevice |
|
* @property {() => boolean} isCharacterDevice |
|
* @property {() => boolean} isSymbolicLink |
|
* @property {() => boolean} isFIFO |
|
* @property {() => boolean} isSocket |
|
* @property {string | Buffer} name |
|
*/ |
|
|
|
/** @typedef {function((NodeJS.ErrnoException | null)=): void} Callback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, Buffer=): void} BufferCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, Buffer|string=): void} BufferOrStringCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, (string | Buffer)[] | IDirent[]=): void} DirentArrayCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, string=): void} StringCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, number=): void} NumberCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | null)=, IStats=): void} StatsCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | Error | null)=, any=): void} ReadJsonCallback */ |
|
/** @typedef {function((NodeJS.ErrnoException | Error | null)=, IStats|string=): void} LstatReadlinkAbsoluteCallback */ |
|
|
|
/** |
|
* @typedef {Object} WatcherInfo |
|
* @property {Set<string>} changes get current aggregated changes that have not yet send to callback |
|
* @property {Set<string>} removals get current aggregated removals that have not yet send to callback |
|
* @property {Map<string, FileSystemInfoEntry | "ignore">} fileTimeInfoEntries get info about files |
|
* @property {Map<string, FileSystemInfoEntry | "ignore">} contextTimeInfoEntries get info about directories |
|
*/ |
|
|
|
// TODO webpack 6 deprecate missing getInfo |
|
/** |
|
* @typedef {Object} Watcher |
|
* @property {function(): void} close closes the watcher and all underlying file watchers |
|
* @property {function(): void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call |
|
* @property {function(): Set<string>=} getAggregatedChanges get current aggregated changes that have not yet send to callback |
|
* @property {function(): Set<string>=} getAggregatedRemovals get current aggregated removals that have not yet send to callback |
|
* @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getFileTimeInfoEntries get info about files |
|
* @property {function(): Map<string, FileSystemInfoEntry | "ignore">} getContextTimeInfoEntries get info about directories |
|
* @property {function(): WatcherInfo=} getInfo get info about timestamps and changes |
|
*/ |
|
|
|
/** |
|
* @callback WatchMethod |
|
* @param {Iterable<string>} files watched files |
|
* @param {Iterable<string>} directories watched directories |
|
* @param {Iterable<string>} missing watched exitance entries |
|
* @param {number} startTime timestamp of start time |
|
* @param {WatchOptions} options options object |
|
* @param {function(Error=, Map<string, FileSystemInfoEntry | "ignore">, Map<string, FileSystemInfoEntry | "ignore">, Set<string>, Set<string>): void} callback aggregated callback |
|
* @param {function(string, number): void} callbackUndelayed callback when the first change was detected |
|
* @returns {Watcher} a watcher |
|
*/ |
|
|
|
// TODO webpack 6 make optional methods required |
|
|
|
/** |
|
* @typedef {Object} OutputFileSystem |
|
* @property {function(string, Buffer|string, Callback): void} writeFile |
|
* @property {function(string, Callback): void} mkdir |
|
* @property {function(string, DirentArrayCallback): void=} readdir |
|
* @property {function(string, Callback): void=} rmdir |
|
* @property {function(string, Callback): void=} unlink |
|
* @property {function(string, StatsCallback): void} stat |
|
* @property {function(string, StatsCallback): void=} lstat |
|
* @property {function(string, BufferOrStringCallback): void} readFile |
|
* @property {(function(string, string): string)=} join |
|
* @property {(function(string, string): string)=} relative |
|
* @property {(function(string): string)=} dirname |
|
*/ |
|
|
|
/** |
|
* @typedef {Object} InputFileSystem |
|
* @property {function(string, BufferOrStringCallback): void} readFile |
|
* @property {(function(string, ReadJsonCallback): void)=} readJson |
|
* @property {function(string, BufferOrStringCallback): void} readlink |
|
* @property {function(string, DirentArrayCallback): void} readdir |
|
* @property {function(string, StatsCallback): void} stat |
|
* @property {function(string, StatsCallback): void=} lstat |
|
* @property {(function(string, BufferOrStringCallback): void)=} realpath |
|
* @property {(function(string=): void)=} purge |
|
* @property {(function(string, string): string)=} join |
|
* @property {(function(string, string): string)=} relative |
|
* @property {(function(string): string)=} dirname |
|
*/ |
|
|
|
/** |
|
* @typedef {Object} WatchFileSystem |
|
* @property {WatchMethod} watch |
|
*/ |
|
|
|
/** |
|
* @typedef {Object} IntermediateFileSystemExtras |
|
* @property {function(string): void} mkdirSync |
|
* @property {function(string): NodeJS.WritableStream} createWriteStream |
|
* @property {function(string, string, NumberCallback): void} open |
|
* @property {function(number, Buffer, number, number, number, NumberCallback): void} read |
|
* @property {function(number, Callback): void} close |
|
* @property {function(string, string, Callback): void} rename |
|
*/ |
|
|
|
/** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */ |
|
|
|
/** |
|
* |
|
* @param {InputFileSystem|OutputFileSystem|undefined} fs a file system |
|
* @param {string} rootPath the root path |
|
* @param {string} targetPath the target path |
|
* @returns {string} location of targetPath relative to rootPath |
|
*/ |
|
const relative = (fs, rootPath, targetPath) => { |
|
if (fs && fs.relative) { |
|
return fs.relative(rootPath, targetPath); |
|
} else if (path.posix.isAbsolute(rootPath)) { |
|
return path.posix.relative(rootPath, targetPath); |
|
} else if (path.win32.isAbsolute(rootPath)) { |
|
return path.win32.relative(rootPath, targetPath); |
|
} else { |
|
throw new Error( |
|
`${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system` |
|
); |
|
} |
|
}; |
|
exports.relative = relative; |
|
|
|
/** |
|
* @param {InputFileSystem|OutputFileSystem|undefined} fs a file system |
|
* @param {string} rootPath a path |
|
* @param {string} filename a filename |
|
* @returns {string} the joined path |
|
*/ |
|
const join = (fs, rootPath, filename) => { |
|
if (fs && fs.join) { |
|
return fs.join(rootPath, filename); |
|
} else if (path.posix.isAbsolute(rootPath)) { |
|
return path.posix.join(rootPath, filename); |
|
} else if (path.win32.isAbsolute(rootPath)) { |
|
return path.win32.join(rootPath, filename); |
|
} else { |
|
throw new Error( |
|
`${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system` |
|
); |
|
} |
|
}; |
|
exports.join = join; |
|
|
|
/** |
|
* @param {InputFileSystem|OutputFileSystem|undefined} fs a file system |
|
* @param {string} absPath an absolute path |
|
* @returns {string} the parent directory of the absolute path |
|
*/ |
|
const dirname = (fs, absPath) => { |
|
if (fs && fs.dirname) { |
|
return fs.dirname(absPath); |
|
} else if (path.posix.isAbsolute(absPath)) { |
|
return path.posix.dirname(absPath); |
|
} else if (path.win32.isAbsolute(absPath)) { |
|
return path.win32.dirname(absPath); |
|
} else { |
|
throw new Error( |
|
`${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system` |
|
); |
|
} |
|
}; |
|
exports.dirname = dirname; |
|
|
|
/** |
|
* @param {OutputFileSystem} fs a file system |
|
* @param {string} p an absolute path |
|
* @param {function(Error=): void} callback callback function for the error |
|
* @returns {void} |
|
*/ |
|
const mkdirp = (fs, p, callback) => { |
|
fs.mkdir(p, err => { |
|
if (err) { |
|
if (err.code === "ENOENT") { |
|
const dir = dirname(fs, p); |
|
if (dir === p) { |
|
callback(err); |
|
return; |
|
} |
|
mkdirp(fs, dir, err => { |
|
if (err) { |
|
callback(err); |
|
return; |
|
} |
|
fs.mkdir(p, err => { |
|
if (err) { |
|
if (err.code === "EEXIST") { |
|
callback(); |
|
return; |
|
} |
|
callback(err); |
|
return; |
|
} |
|
callback(); |
|
}); |
|
}); |
|
return; |
|
} else if (err.code === "EEXIST") { |
|
callback(); |
|
return; |
|
} |
|
callback(err); |
|
return; |
|
} |
|
callback(); |
|
}); |
|
}; |
|
exports.mkdirp = mkdirp; |
|
|
|
/** |
|
* @param {IntermediateFileSystem} fs a file system |
|
* @param {string} p an absolute path |
|
* @returns {void} |
|
*/ |
|
const mkdirpSync = (fs, p) => { |
|
try { |
|
fs.mkdirSync(p); |
|
} catch (err) { |
|
if (err) { |
|
if (err.code === "ENOENT") { |
|
const dir = dirname(fs, p); |
|
if (dir === p) { |
|
throw err; |
|
} |
|
mkdirpSync(fs, dir); |
|
fs.mkdirSync(p); |
|
return; |
|
} else if (err.code === "EEXIST") { |
|
return; |
|
} |
|
throw err; |
|
} |
|
} |
|
}; |
|
exports.mkdirpSync = mkdirpSync; |
|
|
|
/** |
|
* @param {InputFileSystem} fs a file system |
|
* @param {string} p an absolute path |
|
* @param {ReadJsonCallback} callback callback |
|
* @returns {void} |
|
*/ |
|
const readJson = (fs, p, callback) => { |
|
if ("readJson" in fs) return fs.readJson(p, callback); |
|
fs.readFile(p, (err, buf) => { |
|
if (err) return callback(err); |
|
let data; |
|
try { |
|
data = JSON.parse(buf.toString("utf-8")); |
|
} catch (e) { |
|
return callback(e); |
|
} |
|
return callback(null, data); |
|
}); |
|
}; |
|
exports.readJson = readJson; |
|
|
|
/** |
|
* @param {InputFileSystem} fs a file system |
|
* @param {string} p an absolute path |
|
* @param {ReadJsonCallback} callback callback |
|
* @returns {void} |
|
*/ |
|
const lstatReadlinkAbsolute = (fs, p, callback) => { |
|
let i = 3; |
|
const doReadLink = () => { |
|
fs.readlink(p, (err, target) => { |
|
if (err && --i > 0) { |
|
// It might was just changed from symlink to file |
|
// we retry 2 times to catch this case before throwing the error |
|
return doStat(); |
|
} |
|
if (err || !target) return doStat(); |
|
const value = target.toString(); |
|
callback(null, join(fs, dirname(fs, p), value)); |
|
}); |
|
}; |
|
const doStat = () => { |
|
if ("lstat" in fs) { |
|
return fs.lstat(p, (err, stats) => { |
|
if (err) return callback(err); |
|
if (stats.isSymbolicLink()) { |
|
return doReadLink(); |
|
} |
|
callback(null, stats); |
|
}); |
|
} else { |
|
return fs.stat(p, callback); |
|
} |
|
}; |
|
if ("lstat" in fs) return doStat(); |
|
doReadLink(); |
|
}; |
|
exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute;
|
|
|