fs.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  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("watchpack").Entry} Entry */
  9. /** @typedef {import("watchpack").OnlySafeTimeEntry} OnlySafeTimeEntry */
  10. /** @typedef {import("watchpack").ExistenceOnlyTimeEntry} ExistenceOnlyTimeEntry */
  11. /**
  12. * @template T
  13. * @typedef {object} IStatsBase
  14. * @property {() => boolean} isFile
  15. * @property {() => boolean} isDirectory
  16. * @property {() => boolean} isBlockDevice
  17. * @property {() => boolean} isCharacterDevice
  18. * @property {() => boolean} isSymbolicLink
  19. * @property {() => boolean} isFIFO
  20. * @property {() => boolean} isSocket
  21. * @property {T} dev
  22. * @property {T} ino
  23. * @property {T} mode
  24. * @property {T} nlink
  25. * @property {T} uid
  26. * @property {T} gid
  27. * @property {T} rdev
  28. * @property {T} size
  29. * @property {T} blksize
  30. * @property {T} blocks
  31. * @property {T} atimeMs
  32. * @property {T} mtimeMs
  33. * @property {T} ctimeMs
  34. * @property {T} birthtimeMs
  35. * @property {Date} atime
  36. * @property {Date} mtime
  37. * @property {Date} ctime
  38. * @property {Date} birthtime
  39. */
  40. /**
  41. * @typedef {IStatsBase<number>} IStats
  42. */
  43. /**
  44. * @typedef {IStatsBase<bigint> & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats
  45. */
  46. /**
  47. * @template {string | Buffer} [T=string]
  48. * @typedef {object} Dirent
  49. * @property {() => boolean} isFile true when is file, otherwise false
  50. * @property {() => boolean} isDirectory true when is directory, otherwise false
  51. * @property {() => boolean} isBlockDevice true when is block device, otherwise false
  52. * @property {() => boolean} isCharacterDevice true when is character device, otherwise false
  53. * @property {() => boolean} isSymbolicLink true when is symbolic link, otherwise false
  54. * @property {() => boolean} isFIFO true when is FIFO, otherwise false
  55. * @property {() => boolean} isSocket true when is socket, otherwise false
  56. * @property {T} name name
  57. * @property {string} parentPath path
  58. * @property {string=} path path
  59. */
  60. /** @typedef {string | number | boolean | null} JsonPrimitive */
  61. /** @typedef {JsonValue[]} JsonArray */
  62. /** @typedef {{ [Key in string]?: JsonValue }} JsonObject */
  63. /** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */
  64. /** @typedef {(err: NodeJS.ErrnoException | null) => void} NoParamCallback */
  65. /** @typedef {(err: NodeJS.ErrnoException | null, result?: string) => void} StringCallback */
  66. /** @typedef {(err: NodeJS.ErrnoException | null, result?: Buffer) => void} BufferCallback */
  67. /** @typedef {(err: NodeJS.ErrnoException | null, result?: string | Buffer) => void} StringOrBufferCallback */
  68. /** @typedef {(err: NodeJS.ErrnoException | null, result?: string[]) => void} ReaddirStringCallback */
  69. /** @typedef {(err: NodeJS.ErrnoException | null, result?: Buffer[]) => void} ReaddirBufferCallback */
  70. /** @typedef {(err: NodeJS.ErrnoException | null, result?: string[] | Buffer[]) => void} ReaddirStringOrBufferCallback */
  71. /** @typedef {(err: NodeJS.ErrnoException | null, result?: Dirent[]) => void} ReaddirDirentCallback */
  72. /** @typedef {(err: NodeJS.ErrnoException | null, result?: Dirent<Buffer>[]) => void} ReaddirDirentBufferCallback */
  73. /** @typedef {(err: NodeJS.ErrnoException | null, result?: IStats) => void} StatsCallback */
  74. /** @typedef {(err: NodeJS.ErrnoException | null, result?: IBigIntStats) => void} BigIntStatsCallback */
  75. /** @typedef {(err: NodeJS.ErrnoException | null, result?: IStats | IBigIntStats) => void} StatsOrBigIntStatsCallback */
  76. /** @typedef {(err: NodeJS.ErrnoException | null, result?: number) => void} NumberCallback */
  77. /** @typedef {(err: NodeJS.ErrnoException | Error | null, result?: JsonObject) => void} ReadJsonCallback */
  78. /** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null | "ignore">} TimeInfoEntries */
  79. /** @typedef {Set<string>} Changes */
  80. /** @typedef {Set<string>} Removals */
  81. /**
  82. * @typedef {object} WatcherInfo
  83. * @property {Changes | null} changes get current aggregated changes that have not yet send to callback
  84. * @property {Removals | null} removals get current aggregated removals that have not yet send to callback
  85. * @property {TimeInfoEntries} fileTimeInfoEntries get info about files
  86. * @property {TimeInfoEntries} contextTimeInfoEntries get info about directories
  87. */
  88. // TODO webpack 6 deprecate missing getInfo
  89. /**
  90. * @typedef {object} Watcher
  91. * @property {() => void} close closes the watcher and all underlying file watchers
  92. * @property {() => void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call
  93. * @property {(() => Changes | null)=} getAggregatedChanges get current aggregated changes that have not yet send to callback
  94. * @property {(() => Removals | null)=} getAggregatedRemovals get current aggregated removals that have not yet send to callback
  95. * @property {() => TimeInfoEntries} getFileTimeInfoEntries get info about files
  96. * @property {() => TimeInfoEntries} getContextTimeInfoEntries get info about directories
  97. * @property {() => WatcherInfo=} getInfo get info about timestamps and changes
  98. */
  99. /**
  100. * @callback WatchMethod
  101. * @param {Iterable<string>} files watched files
  102. * @param {Iterable<string>} directories watched directories
  103. * @param {Iterable<string>} missing watched existence entries
  104. * @param {number} startTime timestamp of start time
  105. * @param {WatchOptions} options options object
  106. * @param {(err: Error | null, timeInfoEntries1?: TimeInfoEntries, timeInfoEntries2?: TimeInfoEntries, changes?: Changes, removals?: Removals) => void} callback aggregated callback
  107. * @param {(value: string, num: number) => void} callbackUndelayed callback when the first change was detected
  108. * @returns {Watcher} a watcher
  109. */
  110. // TODO webpack 6 make optional methods required and avoid using non standard methods like `join`, `relative`, `dirname`, move IntermediateFileSystemExtras methods to InputFilesystem or OutputFilesystem
  111. /**
  112. * @typedef {string | Buffer | URL} PathLike
  113. */
  114. /**
  115. * @typedef {PathLike | number} PathOrFileDescriptor
  116. */
  117. /**
  118. * @typedef {object} ObjectEncodingOptions
  119. * @property {BufferEncoding | null | undefined=} encoding
  120. */
  121. /**
  122. * @typedef {{
  123. * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void,
  124. * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void,
  125. * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void,
  126. * (path: PathOrFileDescriptor, callback: BufferCallback): void,
  127. * }} ReadFile
  128. */
  129. /**
  130. * @typedef {{
  131. * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer,
  132. * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string,
  133. * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer,
  134. * }} ReadFileSync
  135. */
  136. /**
  137. * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption
  138. */
  139. /**
  140. * @typedef {"buffer" | { encoding: "buffer" }} BufferEncodingOption
  141. */
  142. /**
  143. * @typedef {object} StatOptions
  144. * @property {(boolean | undefined)=} bigint
  145. */
  146. /**
  147. * @typedef {object} StatSyncOptions
  148. * @property {(boolean | undefined)=} bigint
  149. * @property {(boolean | undefined)=} throwIfNoEntry
  150. */
  151. /**
  152. * @typedef {{
  153. * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
  154. * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
  155. * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
  156. * (path: PathLike, callback: StringCallback): void,
  157. * }} Readlink
  158. */
  159. /**
  160. * @typedef {{
  161. * (path: PathLike, options?: EncodingOption): string,
  162. * (path: PathLike, options: BufferEncodingOption): Buffer,
  163. * (path: PathLike, options?: EncodingOption): string | Buffer,
  164. * }} ReadlinkSync
  165. */
  166. /**
  167. * @typedef {{
  168. * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
  169. * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer", callback: (err: NodeJS.ErrnoException | null, files?: Buffer[]) => void): void,
  170. * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: (err: NodeJS.ErrnoException | null, files?: string[] | Buffer[]) => void): void,
  171. * (path: PathLike, callback: (err: NodeJS.ErrnoException | null, files?: string[]) => void): void,
  172. * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files?: Dirent<string>[]) => void): void,
  173. * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }, callback: (err: NodeJS.ErrnoException | null, files: Dirent<Buffer>[]) => void): void,
  174. * }} Readdir
  175. */
  176. /**
  177. * @typedef {{
  178. * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[],
  179. * (path: PathLike, options: { encoding: "buffer", withFileTypes?: false | undefined, recursive?: boolean | undefined } | "buffer"): Buffer[],
  180. * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[],
  181. * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[],
  182. * (path: PathLike, options: { encoding: "buffer", withFileTypes: true, recursive?: boolean | undefined }): Dirent<Buffer>[],
  183. * }} ReaddirSync
  184. */
  185. /**
  186. * @typedef {{
  187. * (path: PathLike, callback: StatsCallback): void,
  188. * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
  189. * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
  190. * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
  191. * }} Stat
  192. */
  193. /**
  194. * @typedef {{
  195. * (path: PathLike, options?: undefined): IStats,
  196. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
  197. * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
  198. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
  199. * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
  200. * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
  201. * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
  202. * }} StatSync
  203. */
  204. /**
  205. * @typedef {{
  206. * (path: PathLike, callback: StatsCallback): void,
  207. * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void,
  208. * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void,
  209. * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void,
  210. * }} LStat
  211. */
  212. /**
  213. * @typedef {{
  214. * (path: PathLike, options?: undefined): IStats,
  215. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined,
  216. * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined,
  217. * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats,
  218. * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats,
  219. * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats,
  220. * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined,
  221. * }} LStatSync
  222. */
  223. /**
  224. * @typedef {{
  225. * (path: PathLike, options: EncodingOption, callback: StringCallback): void,
  226. * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void,
  227. * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void,
  228. * (path: PathLike, callback: StringCallback): void,
  229. * }} RealPath
  230. */
  231. /**
  232. * @typedef {{
  233. * (path: PathLike, options?: EncodingOption): string,
  234. * (path: PathLike, options: BufferEncodingOption): Buffer,
  235. * (path: PathLike, options?: EncodingOption): string | Buffer,
  236. * }} RealPathSync
  237. */
  238. /**
  239. * @typedef {(pathOrFileDescriptor: PathOrFileDescriptor, callback: ReadJsonCallback) => void} ReadJson
  240. */
  241. /**
  242. * @typedef {(pathOrFileDescriptor: PathOrFileDescriptor) => JsonObject} ReadJsonSync
  243. */
  244. /**
  245. * @typedef {(value?: string | string[] | Set<string>) => void} Purge
  246. */
  247. /**
  248. * @typedef {object} InputFileSystem
  249. * @property {ReadFile} readFile
  250. * @property {ReadFileSync=} readFileSync
  251. * @property {Readlink} readlink
  252. * @property {ReadlinkSync=} readlinkSync
  253. * @property {Readdir} readdir
  254. * @property {ReaddirSync=} readdirSync
  255. * @property {Stat} stat
  256. * @property {StatSync=} statSync
  257. * @property {LStat=} lstat
  258. * @property {LStatSync=} lstatSync
  259. * @property {RealPath=} realpath
  260. * @property {RealPathSync=} realpathSync
  261. * @property {ReadJson=} readJson
  262. * @property {ReadJsonSync=} readJsonSync
  263. * @property {Purge=} purge
  264. * @property {((path1: string, path2: string) => string)=} join
  265. * @property {((from: string, to: string) => string)=} relative
  266. * @property {((dirname: string) => string)=} dirname
  267. */
  268. /**
  269. * @typedef {number | string} Mode
  270. */
  271. /**
  272. * @typedef {(ObjectEncodingOptions & import("events").Abortable & { mode?: Mode | undefined, flag?: string | undefined, flush?: boolean | undefined }) | BufferEncoding | null} WriteFileOptions
  273. */
  274. /**
  275. * @typedef {{
  276. * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options: WriteFileOptions, callback: NoParamCallback): void,
  277. * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, callback: NoParamCallback): void,
  278. * }} WriteFile
  279. */
  280. /**
  281. * @typedef {{ recursive?: boolean | undefined, mode?: Mode | undefined }} MakeDirectoryOptions
  282. */
  283. /**
  284. * @typedef {{
  285. * (file: PathLike, options: MakeDirectoryOptions & { recursive: true }, callback: StringCallback): void,
  286. * (file: PathLike, options: Mode | (MakeDirectoryOptions & { recursive?: false | undefined }) | null | undefined, callback: NoParamCallback): void,
  287. * (file: PathLike, options: Mode | MakeDirectoryOptions | null | undefined, callback: StringCallback): void,
  288. * (file: PathLike, callback: NoParamCallback): void,
  289. * }} Mkdir
  290. */
  291. /**
  292. * @typedef {{ (file: PathLike, callback: NoParamCallback): void }} Rmdir
  293. */
  294. /**
  295. * @typedef {(pathLike: PathLike, callback: NoParamCallback) => void} Unlink
  296. */
  297. /**
  298. * @typedef {FSImplementation & { read: (...args: EXPECTED_ANY[]) => EXPECTED_ANY }} CreateReadStreamFSImplementation
  299. */
  300. /**
  301. * @typedef {StreamOptions & { fs?: CreateReadStreamFSImplementation | null | undefined, end?: number | undefined }} ReadStreamOptions
  302. */
  303. /**
  304. * @typedef {(path: PathLike, options?: BufferEncoding | ReadStreamOptions) => NodeJS.ReadableStream} CreateReadStream
  305. */
  306. /**
  307. * @typedef {object} OutputFileSystem
  308. * @property {Mkdir} mkdir
  309. * @property {Readdir=} readdir
  310. * @property {Rmdir=} rmdir
  311. * @property {WriteFile} writeFile
  312. * @property {Unlink=} unlink
  313. * @property {Stat} stat
  314. * @property {LStat=} lstat
  315. * @property {ReadFile} readFile
  316. * @property {CreateReadStream=} createReadStream
  317. * @property {((path1: string, path2: string) => string)=} join
  318. * @property {((from: string, to: string) => string)=} relative
  319. * @property {((dirname: string) => string)=} dirname
  320. */
  321. /**
  322. * @typedef {object} WatchFileSystem
  323. * @property {WatchMethod} watch
  324. */
  325. /**
  326. * @typedef {{
  327. * (path: PathLike, options: MakeDirectoryOptions & { recursive: true }): string | undefined,
  328. * (path: PathLike, options?: Mode | (MakeDirectoryOptions & { recursive?: false | undefined }) | null): void,
  329. * (path: PathLike, options?: Mode | MakeDirectoryOptions | null): string | undefined,
  330. * }} MkdirSync
  331. */
  332. /**
  333. * @typedef {object} StreamOptions
  334. * @property {(string | undefined)=} flags
  335. * @property {(BufferEncoding | undefined)} encoding
  336. * @property {(number | EXPECTED_ANY | undefined)=} fd
  337. * @property {(number | undefined)=} mode
  338. * @property {(boolean | undefined)=} autoClose
  339. * @property {(boolean | undefined)=} emitClose
  340. * @property {(number | undefined)=} start
  341. * @property {(AbortSignal | null | undefined)=} signal
  342. */
  343. /**
  344. * @typedef {object} FSImplementation
  345. * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} open
  346. * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} close
  347. */
  348. /**
  349. * @typedef {FSImplementation & { write: (...args: EXPECTED_ANY[]) => EXPECTED_ANY, close?: (...args: EXPECTED_ANY[]) => EXPECTED_ANY }} CreateWriteStreamFSImplementation
  350. */
  351. /**
  352. * @typedef {StreamOptions & { fs?: CreateWriteStreamFSImplementation | null | undefined, flush?: boolean | undefined }} WriteStreamOptions
  353. */
  354. /**
  355. * @typedef {(pathLike: PathLike, result?: BufferEncoding | WriteStreamOptions) => NodeJS.WritableStream} CreateWriteStream
  356. */
  357. /**
  358. * @typedef {number | string} OpenMode
  359. */
  360. /**
  361. * @typedef {{
  362. * (file: PathLike, flags: OpenMode | undefined, mode: Mode | undefined | null, callback: NumberCallback): void,
  363. * (file: PathLike, flags: OpenMode | undefined, callback: NumberCallback): void,
  364. * (file: PathLike, callback: NumberCallback): void,
  365. * }} Open
  366. */
  367. /**
  368. * @typedef {number | bigint} ReadPosition
  369. */
  370. /**
  371. * @typedef {object} ReadSyncOptions
  372. * @property {(number | undefined)=} offset
  373. * @property {(number | undefined)=} length
  374. * @property {(ReadPosition | null | undefined)=} position
  375. */
  376. /**
  377. * @template {NodeJS.ArrayBufferView} TBuffer
  378. * @typedef {object} ReadAsyncOptions
  379. * @property {(number | undefined)=} offset
  380. * @property {(number | undefined)=} length
  381. * @property {(ReadPosition | null | undefined)=} position
  382. * @property {TBuffer=} buffer
  383. */
  384. /**
  385. * @template {NodeJS.ArrayBufferView} [TBuffer=NodeJS.ArrayBufferView]
  386. * @typedef {{
  387. * (fd: number, buffer: TBuffer, offset: number, length: number, position: ReadPosition | null, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void,
  388. * (fd: number, options: ReadAsyncOptions<TBuffer>, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void,
  389. * (fd: number, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: NodeJS.ArrayBufferView) => void): void,
  390. * }} Read
  391. */
  392. /** @typedef {(df: number, callback: NoParamCallback) => void} Close */
  393. /** @typedef {(a: PathLike, b: PathLike, callback: NoParamCallback) => void} Rename */
  394. /**
  395. * @typedef {object} IntermediateFileSystemExtras
  396. * @property {MkdirSync} mkdirSync
  397. * @property {CreateWriteStream} createWriteStream
  398. * @property {Open} open
  399. * @property {Read} read
  400. * @property {Close} close
  401. * @property {Rename} rename
  402. */
  403. /** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */
  404. /**
  405. * @param {InputFileSystem | OutputFileSystem | undefined} fs a file system
  406. * @param {string} rootPath the root path
  407. * @param {string} targetPath the target path
  408. * @returns {string} location of targetPath relative to rootPath
  409. */
  410. const relative = (fs, rootPath, targetPath) => {
  411. if (fs && fs.relative) {
  412. return fs.relative(rootPath, targetPath);
  413. } else if (path.posix.isAbsolute(rootPath)) {
  414. return path.posix.relative(rootPath, targetPath);
  415. } else if (path.win32.isAbsolute(rootPath)) {
  416. return path.win32.relative(rootPath, targetPath);
  417. }
  418. throw new Error(
  419. `${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system`
  420. );
  421. };
  422. /**
  423. * @param {InputFileSystem | OutputFileSystem | undefined} fs a file system
  424. * @param {string} rootPath a path
  425. * @param {string} filename a filename
  426. * @returns {string} the joined path
  427. */
  428. const join = (fs, rootPath, filename) => {
  429. if (fs && fs.join) {
  430. return fs.join(rootPath, filename);
  431. } else if (path.posix.isAbsolute(rootPath)) {
  432. return path.posix.join(rootPath, filename);
  433. } else if (path.win32.isAbsolute(rootPath)) {
  434. return path.win32.join(rootPath, filename);
  435. }
  436. throw new Error(
  437. `${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system`
  438. );
  439. };
  440. /**
  441. * @param {InputFileSystem | OutputFileSystem | undefined} fs a file system
  442. * @param {string} absPath an absolute path
  443. * @returns {string} the parent directory of the absolute path
  444. */
  445. const dirname = (fs, absPath) => {
  446. if (fs && fs.dirname) {
  447. return fs.dirname(absPath);
  448. } else if (path.posix.isAbsolute(absPath)) {
  449. return path.posix.dirname(absPath);
  450. } else if (path.win32.isAbsolute(absPath)) {
  451. return path.win32.dirname(absPath);
  452. }
  453. throw new Error(
  454. `${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system`
  455. );
  456. };
  457. /**
  458. * @param {OutputFileSystem} fs a file system
  459. * @param {string} p an absolute path
  460. * @param {(err?: Error) => void} callback callback function for the error
  461. * @returns {void}
  462. */
  463. const mkdirp = (fs, p, callback) => {
  464. fs.mkdir(p, (err) => {
  465. if (err) {
  466. if (err.code === "ENOENT") {
  467. const dir = dirname(fs, p);
  468. if (dir === p) {
  469. callback(err);
  470. return;
  471. }
  472. mkdirp(fs, dir, (err) => {
  473. if (err) {
  474. callback(err);
  475. return;
  476. }
  477. fs.mkdir(p, (err) => {
  478. if (err) {
  479. if (err.code === "EEXIST") {
  480. callback();
  481. return;
  482. }
  483. callback(err);
  484. return;
  485. }
  486. callback();
  487. });
  488. });
  489. return;
  490. } else if (err.code === "EEXIST") {
  491. callback();
  492. return;
  493. }
  494. callback(err);
  495. return;
  496. }
  497. callback();
  498. });
  499. };
  500. /**
  501. * @param {IntermediateFileSystem} fs a file system
  502. * @param {string} p an absolute path
  503. * @returns {void}
  504. */
  505. const mkdirpSync = (fs, p) => {
  506. try {
  507. fs.mkdirSync(p);
  508. } catch (err) {
  509. if (err) {
  510. if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") {
  511. const dir = dirname(fs, p);
  512. if (dir === p) {
  513. throw err;
  514. }
  515. mkdirpSync(fs, dir);
  516. fs.mkdirSync(p);
  517. return;
  518. } else if (/** @type {NodeJS.ErrnoException} */ (err).code === "EEXIST") {
  519. return;
  520. }
  521. throw err;
  522. }
  523. }
  524. };
  525. /**
  526. * @param {InputFileSystem} fs a file system
  527. * @param {string} p an absolute path
  528. * @param {ReadJsonCallback} callback callback
  529. * @returns {void}
  530. */
  531. const readJson = (fs, p, callback) => {
  532. if ("readJson" in fs) {
  533. return /** @type {NonNullable<InputFileSystem["readJson"]>} */ (
  534. fs.readJson
  535. )(p, callback);
  536. }
  537. fs.readFile(p, (err, buf) => {
  538. if (err) return callback(err);
  539. /** @type {JsonObject} */
  540. let data;
  541. try {
  542. data = JSON.parse(/** @type {Buffer} */ (buf).toString("utf8"));
  543. } catch (err1) {
  544. return callback(/** @type {Error} */ (err1));
  545. }
  546. return callback(null, data);
  547. });
  548. };
  549. /**
  550. * @param {InputFileSystem} fs a file system
  551. * @param {string} p an absolute path
  552. * @param {(err: NodeJS.ErrnoException | Error | null, stats?: IStats | string) => void} callback callback
  553. * @returns {void}
  554. */
  555. const lstatReadlinkAbsolute = (fs, p, callback) => {
  556. let i = 3;
  557. const doReadLink = () => {
  558. fs.readlink(p, (err, target) => {
  559. if (err && --i > 0) {
  560. // It might was just changed from symlink to file
  561. // we retry 2 times to catch this case before throwing the error
  562. return doStat();
  563. }
  564. if (err) return callback(err);
  565. const value = /** @type {string} */ (target).toString();
  566. callback(null, join(fs, dirname(fs, p), value));
  567. });
  568. };
  569. const doStat = () => {
  570. if ("lstat" in fs) {
  571. return /** @type {NonNullable<InputFileSystem["lstat"]>} */ (fs.lstat)(
  572. p,
  573. (err, stats) => {
  574. if (err) return callback(err);
  575. if (/** @type {IStats} */ (stats).isSymbolicLink()) {
  576. return doReadLink();
  577. }
  578. callback(null, stats);
  579. }
  580. );
  581. }
  582. return fs.stat(p, callback);
  583. };
  584. if ("lstat" in fs) return doStat();
  585. doReadLink();
  586. };
  587. /**
  588. * @param {string} pathname a path
  589. * @returns {boolean} is absolute
  590. */
  591. const isAbsolute = (pathname) =>
  592. path.posix.isAbsolute(pathname) || path.win32.isAbsolute(pathname);
  593. module.exports.dirname = dirname;
  594. module.exports.isAbsolute = isAbsolute;
  595. module.exports.join = join;
  596. module.exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute;
  597. module.exports.mkdirp = mkdirp;
  598. module.exports.mkdirpSync = mkdirpSync;
  599. module.exports.readJson = readJson;
  600. module.exports.relative = relative;