patchFs.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.extendFs = exports.patchFs = void 0;
  4. const util_1 = require("util");
  5. const NodePathFS_1 = require("../NodePathFS");
  6. const FileHandle_1 = require("./FileHandle");
  7. const SYNC_IMPLEMENTATIONS = new Set([
  8. `accessSync`,
  9. `appendFileSync`,
  10. `createReadStream`,
  11. `createWriteStream`,
  12. `chmodSync`,
  13. `fchmodSync`,
  14. `chownSync`,
  15. `fchownSync`,
  16. `closeSync`,
  17. `copyFileSync`,
  18. `linkSync`,
  19. `lstatSync`,
  20. `fstatSync`,
  21. `lutimesSync`,
  22. `mkdirSync`,
  23. `openSync`,
  24. `opendirSync`,
  25. `readlinkSync`,
  26. `readFileSync`,
  27. `readdirSync`,
  28. `readlinkSync`,
  29. `realpathSync`,
  30. `renameSync`,
  31. `rmdirSync`,
  32. `statSync`,
  33. `symlinkSync`,
  34. `truncateSync`,
  35. `ftruncateSync`,
  36. `unlinkSync`,
  37. `unwatchFile`,
  38. `utimesSync`,
  39. `watch`,
  40. `watchFile`,
  41. `writeFileSync`,
  42. `writeSync`,
  43. ]);
  44. const ASYNC_IMPLEMENTATIONS = new Set([
  45. `accessPromise`,
  46. `appendFilePromise`,
  47. `fchmodPromise`,
  48. `chmodPromise`,
  49. `fchownPromise`,
  50. `chownPromise`,
  51. `closePromise`,
  52. `copyFilePromise`,
  53. `linkPromise`,
  54. `fstatPromise`,
  55. `lstatPromise`,
  56. `lutimesPromise`,
  57. `mkdirPromise`,
  58. `openPromise`,
  59. `opendirPromise`,
  60. `readdirPromise`,
  61. `realpathPromise`,
  62. `readFilePromise`,
  63. `readdirPromise`,
  64. `readlinkPromise`,
  65. `renamePromise`,
  66. `rmdirPromise`,
  67. `statPromise`,
  68. `symlinkPromise`,
  69. `truncatePromise`,
  70. `ftruncatePromise`,
  71. `unlinkPromise`,
  72. `utimesPromise`,
  73. `writeFilePromise`,
  74. `writeSync`,
  75. ]);
  76. //#endregion
  77. function patchFs(patchedFs, fakeFs) {
  78. // We wrap the `fakeFs` with a `NodePathFS` to add support for all path types supported by Node
  79. fakeFs = new NodePathFS_1.NodePathFS(fakeFs);
  80. const setupFn = (target, name, replacement) => {
  81. const orig = target[name];
  82. target[name] = replacement;
  83. // Preserve any util.promisify implementations
  84. if (typeof (orig === null || orig === void 0 ? void 0 : orig[util_1.promisify.custom]) !== `undefined`) {
  85. replacement[util_1.promisify.custom] = orig[util_1.promisify.custom];
  86. }
  87. };
  88. /** Callback implementations */
  89. {
  90. setupFn(patchedFs, `exists`, (p, ...args) => {
  91. const hasCallback = typeof args[args.length - 1] === `function`;
  92. const callback = hasCallback ? args.pop() : () => { };
  93. process.nextTick(() => {
  94. fakeFs.existsPromise(p).then(exists => {
  95. callback(exists);
  96. }, () => {
  97. callback(false);
  98. });
  99. });
  100. });
  101. // Adapted from https://github.com/nodejs/node/blob/e5c1fd7a2a1801fd75bdde23b260488e85453eb2/lib/fs.js#L603-L667
  102. setupFn(patchedFs, `read`, (...args) => {
  103. let [fd, buffer, offset, length, position, callback] = args;
  104. if (args.length <= 3) {
  105. // Assume fs.read(fd, options, callback)
  106. let options = {};
  107. if (args.length < 3) {
  108. // This is fs.read(fd, callback)
  109. callback = args[1];
  110. }
  111. else {
  112. // This is fs.read(fd, {}, callback)
  113. options = args[1];
  114. callback = args[2];
  115. }
  116. ({
  117. buffer = Buffer.alloc(16384),
  118. offset = 0,
  119. length = buffer.byteLength,
  120. position,
  121. } = options);
  122. }
  123. if (offset == null)
  124. offset = 0;
  125. length |= 0;
  126. if (length === 0) {
  127. process.nextTick(() => {
  128. callback(null, 0, buffer);
  129. });
  130. return;
  131. }
  132. if (position == null)
  133. position = -1;
  134. process.nextTick(() => {
  135. fakeFs.readPromise(fd, buffer, offset, length, position).then(bytesRead => {
  136. callback(null, bytesRead, buffer);
  137. }, error => {
  138. // https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/fs.js#L655-L658
  139. // Known issue: bytesRead could theoretically be > than 0, but we currently always return 0
  140. callback(error, 0, buffer);
  141. });
  142. });
  143. });
  144. for (const fnName of ASYNC_IMPLEMENTATIONS) {
  145. const origName = fnName.replace(/Promise$/, ``);
  146. if (typeof patchedFs[origName] === `undefined`)
  147. continue;
  148. const fakeImpl = fakeFs[fnName];
  149. if (typeof fakeImpl === `undefined`)
  150. continue;
  151. const wrapper = (...args) => {
  152. const hasCallback = typeof args[args.length - 1] === `function`;
  153. const callback = hasCallback ? args.pop() : () => { };
  154. process.nextTick(() => {
  155. fakeImpl.apply(fakeFs, args).then((result) => {
  156. callback(null, result);
  157. }, (error) => {
  158. callback(error);
  159. });
  160. });
  161. };
  162. setupFn(patchedFs, origName, wrapper);
  163. }
  164. patchedFs.realpath.native = patchedFs.realpath;
  165. }
  166. /** Sync implementations */
  167. {
  168. setupFn(patchedFs, `existsSync`, (p) => {
  169. try {
  170. return fakeFs.existsSync(p);
  171. }
  172. catch (error) {
  173. return false;
  174. }
  175. });
  176. // Adapted from https://github.com/nodejs/node/blob/e5c1fd7a2a1801fd75bdde23b260488e85453eb2/lib/fs.js#L684-L725
  177. setupFn(patchedFs, `readSync`, (...args) => {
  178. let [fd, buffer, offset, length, position] = args;
  179. if (args.length <= 3) {
  180. // Assume fs.read(fd, buffer, options)
  181. const options = args[2] || {};
  182. ({ offset = 0, length = buffer.byteLength, position } = options);
  183. }
  184. if (offset == null)
  185. offset = 0;
  186. length |= 0;
  187. if (length === 0)
  188. return 0;
  189. if (position == null)
  190. position = -1;
  191. return fakeFs.readSync(fd, buffer, offset, length, position);
  192. });
  193. for (const fnName of SYNC_IMPLEMENTATIONS) {
  194. const origName = fnName;
  195. if (typeof patchedFs[origName] === `undefined`)
  196. continue;
  197. const fakeImpl = fakeFs[fnName];
  198. if (typeof fakeImpl === `undefined`)
  199. continue;
  200. setupFn(patchedFs, origName, fakeImpl.bind(fakeFs));
  201. }
  202. patchedFs.realpathSync.native = patchedFs.realpathSync;
  203. }
  204. /** Promise implementations */
  205. {
  206. // `fs.promises` is a getter that returns a reference to require(`fs/promises`),
  207. // so we can just patch `fs.promises` and both will be updated
  208. const origEmitWarning = process.emitWarning;
  209. process.emitWarning = () => { };
  210. let patchedFsPromises;
  211. try {
  212. patchedFsPromises = patchedFs.promises;
  213. }
  214. finally {
  215. process.emitWarning = origEmitWarning;
  216. }
  217. if (typeof patchedFsPromises !== `undefined`) {
  218. // `fs.promises.exists` doesn't exist
  219. for (const fnName of ASYNC_IMPLEMENTATIONS) {
  220. const origName = fnName.replace(/Promise$/, ``);
  221. if (typeof patchedFsPromises[origName] === `undefined`)
  222. continue;
  223. const fakeImpl = fakeFs[fnName];
  224. if (typeof fakeImpl === `undefined`)
  225. continue;
  226. // Open is a bit particular with fs.promises: it returns a file handle
  227. // instance instead of the traditional file descriptor number
  228. if (fnName === `open`)
  229. continue;
  230. setupFn(patchedFsPromises, origName, (pathLike, ...args) => {
  231. if (pathLike instanceof FileHandle_1.FileHandle) {
  232. return pathLike[origName].apply(pathLike, args);
  233. }
  234. else {
  235. return fakeImpl.call(fakeFs, pathLike, ...args);
  236. }
  237. });
  238. }
  239. setupFn(patchedFsPromises, `open`, async (...args) => {
  240. // @ts-expect-error
  241. const fd = await fakeFs.openPromise(...args);
  242. return new FileHandle_1.FileHandle(fd, fakeFs);
  243. });
  244. // `fs.promises.realpath` doesn't have a `native` property
  245. }
  246. }
  247. /** util.promisify implementations */
  248. {
  249. // TODO add promisified `fs.readv` and `fs.writev`, once they are implemented
  250. // Override the promisified versions of `fs.read` and `fs.write` to return an object as per
  251. // https://github.com/nodejs/node/blob/dc79f3f37caf6f25b8efee4623bec31e2c20f595/lib/fs.js#L559-L560
  252. // and
  253. // https://github.com/nodejs/node/blob/dc79f3f37caf6f25b8efee4623bec31e2c20f595/lib/fs.js#L690-L691
  254. // and
  255. // https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L293
  256. // @ts-expect-error
  257. patchedFs.read[util_1.promisify.custom] = async (fd, buffer, ...args) => {
  258. const res = fakeFs.readPromise(fd, buffer, ...args);
  259. return { bytesRead: await res, buffer };
  260. };
  261. // @ts-expect-error
  262. patchedFs.write[util_1.promisify.custom] = async (fd, buffer, ...args) => {
  263. const res = fakeFs.writePromise(fd, buffer, ...args);
  264. return { bytesWritten: await res, buffer };
  265. };
  266. }
  267. }
  268. exports.patchFs = patchFs;
  269. function extendFs(realFs, fakeFs) {
  270. const patchedFs = Object.create(realFs);
  271. patchFs(patchedFs, fakeFs);
  272. return patchedFs;
  273. }
  274. exports.extendFs = extendFs;