fs.js 11 KB

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