fs.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. 'use strict';
  2. const Promise = require('bluebird');
  3. const fs = require('graceful-fs');
  4. const { dirname, join, extname, basename } = require('path');
  5. const fsPromises = fs.promises;
  6. const chokidar = require('chokidar');
  7. const { escapeRegExp } = require('hexo-util');
  8. const rEOL = /\r\n/g;
  9. function exists(path) {
  10. if (!path) throw new TypeError('path is required!');
  11. const promise = fsPromises.access(path).then(() => true, err => {
  12. if (err.code !== 'ENOENT') throw err;
  13. return false;
  14. });
  15. return Promise.resolve(promise);
  16. }
  17. function existsSync(path) {
  18. if (!path) throw new TypeError('path is required!');
  19. try {
  20. fs.accessSync(path);
  21. } catch (err) {
  22. if (err.code !== 'ENOENT') throw err;
  23. return false;
  24. }
  25. return true;
  26. }
  27. function mkdirs(path) {
  28. if (!path) throw new TypeError('path is required!');
  29. return Promise.resolve(fsPromises.mkdir(path, { recursive: true }));
  30. }
  31. function mkdirsSync(path) {
  32. if (!path) throw new TypeError('path is required!');
  33. fs.mkdirSync(path, { recursive: true });
  34. }
  35. function checkParent(path) {
  36. return Promise.resolve(fsPromises.mkdir(dirname(path), { recursive: true }));
  37. }
  38. function writeFile(path, data, options = {}) {
  39. if (!path) throw new TypeError('path is required!');
  40. if (!data) data = '';
  41. return checkParent(path).then(() => fsPromises.writeFile(path, data, options));
  42. }
  43. function writeFileSync(path, data, options) {
  44. if (!path) throw new TypeError('path is required!');
  45. fs.mkdirSync(dirname(path), { recursive: true });
  46. fs.writeFileSync(path, data, options);
  47. }
  48. function appendFile(path, data, options = {}) {
  49. if (!path) throw new TypeError('path is required!');
  50. return checkParent(path).then(() => fsPromises.appendFile(path, data, options));
  51. }
  52. function appendFileSync(path, data, options) {
  53. if (!path) throw new TypeError('path is required!');
  54. fs.mkdirSync(dirname(path), { recursive: true });
  55. fs.appendFileSync(path, data, options);
  56. }
  57. function copyFile(src, dest, flags) {
  58. if (!src) throw new TypeError('src is required!');
  59. if (!dest) throw new TypeError('dest is required!');
  60. return checkParent(dest).then(() => fsPromises.copyFile(src, dest, flags));
  61. }
  62. function trueFn() {
  63. return true;
  64. }
  65. function ignoreHiddenFiles(ignore) {
  66. if (!ignore) return trueFn;
  67. return ({ name }) => !name.startsWith('.');
  68. }
  69. function ignoreFilesRegex(regex) {
  70. if (!regex) return trueFn;
  71. return ({ name }) => !regex.test(name);
  72. }
  73. function ignoreExcludeFiles(arr, parent) {
  74. if (!arr || !arr.length) return trueFn;
  75. const set = new Set(arr);
  76. return ({ name }) => !set.has(join(parent, name));
  77. }
  78. async function _readAndFilterDir(path, options) {
  79. const { ignoreHidden = true, ignorePattern } = options;
  80. return (await fsPromises.readdir(path, { ...options, withFileTypes: true }))
  81. .filter(ignoreHiddenFiles(ignoreHidden))
  82. .filter(ignoreFilesRegex(ignorePattern));
  83. }
  84. function _readAndFilterDirSync(path, options) {
  85. const { ignoreHidden = true, ignorePattern } = options;
  86. return fs.readdirSync(path, { ...options, withFileTypes: true })
  87. .filter(ignoreHiddenFiles(ignoreHidden))
  88. .filter(ignoreFilesRegex(ignorePattern));
  89. }
  90. function _copyDirWalker(src, dest, results, parent, options) {
  91. return Promise.map(_readAndFilterDir(src, options), item => {
  92. const childSrc = join(src, item.name);
  93. const childDest = join(dest, item.name);
  94. const currentPath = join(parent, item.name);
  95. if (item.isDirectory()) {
  96. return _copyDirWalker(childSrc, childDest, results, currentPath, options);
  97. }
  98. results.push(currentPath);
  99. return copyFile(childSrc, childDest);
  100. });
  101. }
  102. function copyDir(src, dest, options = {}) {
  103. if (!src) throw new TypeError('src is required!');
  104. if (!dest) throw new TypeError('dest is required!');
  105. const results = [];
  106. return checkParent(dest).then(() => _copyDirWalker(src, dest, results, '', options)).return(results);
  107. }
  108. async function _listDirWalker(path, results, parent, options) {
  109. const promises = [];
  110. for (const item of await _readAndFilterDir(path, options)) {
  111. const currentPath = join(parent, item.name);
  112. if (item.isDirectory()) {
  113. promises.push(_listDirWalker(join(path, item.name), results, currentPath, options));
  114. } else {
  115. results.push(currentPath);
  116. }
  117. }
  118. await Promise.all(promises);
  119. }
  120. function listDir(path, options = {}) {
  121. if (!path) throw new TypeError('path is required!');
  122. const results = [];
  123. return Promise.resolve(_listDirWalker(path, results, '', options)).return(results);
  124. }
  125. function _listDirSyncWalker(path, results, parent, options) {
  126. for (const item of _readAndFilterDirSync(path, options)) {
  127. const currentPath = join(parent, item.name);
  128. if (item.isDirectory()) {
  129. _listDirSyncWalker(join(path, item.name), results, currentPath, options);
  130. } else {
  131. results.push(currentPath);
  132. }
  133. }
  134. }
  135. function listDirSync(path, options = {}) {
  136. if (!path) throw new TypeError('path is required!');
  137. const results = [];
  138. _listDirSyncWalker(path, results, '', options);
  139. return results;
  140. }
  141. function escapeEOL(str) {
  142. return str.replace(rEOL, '\n');
  143. }
  144. function escapeBOM(str) {
  145. return str.charCodeAt(0) === 0xFEFF ? str.substring(1) : str;
  146. }
  147. function escapeFileContent(content) {
  148. return escapeBOM(escapeEOL(content));
  149. }
  150. async function _readFile(path, options) {
  151. if (!Object.prototype.hasOwnProperty.call(options, 'encoding')) options.encoding = 'utf8';
  152. const content = await fsPromises.readFile(path, options);
  153. if (options.escape == null || options.escape) {
  154. return escapeFileContent(content);
  155. }
  156. return content;
  157. }
  158. function readFile(path, options = {}) {
  159. if (!path) throw new TypeError('path is required!');
  160. return Promise.resolve(_readFile(path, options));
  161. }
  162. function readFileSync(path, options = {}) {
  163. if (!path) throw new TypeError('path is required!');
  164. if (!Object.prototype.hasOwnProperty.call(options, 'encoding')) options.encoding = 'utf8';
  165. const content = fs.readFileSync(path, options);
  166. if (options.escape == null || options.escape) {
  167. return escapeFileContent(content);
  168. }
  169. return content;
  170. }
  171. async function _emptyDir(path, parent, options) {
  172. const entrys = (await _readAndFilterDir(path, options))
  173. .filter(ignoreExcludeFiles(options.exclude, parent));
  174. const results = [];
  175. await Promise.map(entrys, item => {
  176. const fullPath = join(path, item.name);
  177. const currentPath = join(parent, item.name);
  178. if (item.isDirectory()) {
  179. return _emptyDir(fullPath, currentPath, options).then(files => {
  180. if (!files.length) {
  181. return fsPromises.rmdir(fullPath);
  182. }
  183. results.push(...files);
  184. });
  185. }
  186. results.push(currentPath);
  187. return fsPromises.unlink(fullPath);
  188. });
  189. return results;
  190. }
  191. function emptyDir(path, options = {}) {
  192. if (!path) throw new TypeError('path is required!');
  193. return Promise.resolve(_emptyDir(path, '', options));
  194. }
  195. function _emptyDirSync(path, options, parent) {
  196. const entrys = _readAndFilterDirSync(path, options)
  197. .filter(ignoreExcludeFiles(options.exclude, parent));
  198. const results = [];
  199. for (const item of entrys) {
  200. const childPath = join(path, item.name);
  201. const currentPath = join(parent, item.name);
  202. if (item.isDirectory()) {
  203. const removed = _emptyDirSync(childPath, options, currentPath);
  204. if (!fs.readdirSync(childPath).length) {
  205. rmdirSync(childPath);
  206. }
  207. results.push(...removed);
  208. } else {
  209. fs.unlinkSync(childPath);
  210. results.push(currentPath);
  211. }
  212. }
  213. return results;
  214. }
  215. function emptyDirSync(path, options = {}) {
  216. if (!path) throw new TypeError('path is required!');
  217. return _emptyDirSync(path, options, '');
  218. }
  219. async function _rmdir(path) {
  220. const files = fsPromises.readdir(path, { withFileTypes: true });
  221. await Promise.map(files, item => {
  222. const childPath = join(path, item.name);
  223. return item.isDirectory() ? _rmdir(childPath) : fsPromises.unlink(childPath);
  224. });
  225. return fsPromises.rmdir(path);
  226. }
  227. function rmdir(path) {
  228. if (!path) throw new TypeError('path is required!');
  229. return Promise.resolve(_rmdir(path));
  230. }
  231. function _rmdirSync(path) {
  232. const files = fs.readdirSync(path, { withFileTypes: true });
  233. for (const item of files) {
  234. const childPath = join(path, item.name);
  235. if (item.isDirectory()) {
  236. _rmdirSync(childPath);
  237. } else {
  238. fs.unlinkSync(childPath);
  239. }
  240. }
  241. fs.rmdirSync(path);
  242. }
  243. function rmdirSync(path) {
  244. if (!path) throw new TypeError('path is required!');
  245. _rmdirSync(path);
  246. }
  247. function watch(path, options = {}) {
  248. if (!path) throw new TypeError('path is required!');
  249. const watcher = chokidar.watch(path, options);
  250. return new Promise((resolve, reject) => {
  251. watcher.on('ready', resolve);
  252. watcher.on('error', reject);
  253. }).thenReturn(watcher);
  254. }
  255. function _findUnusedPath(path, files) {
  256. const ext = extname(path);
  257. const base = basename(path, ext);
  258. const regex = new RegExp(`^${escapeRegExp(base)}(?:-(\\d+))?${escapeRegExp(ext)}$`);
  259. let num = -1;
  260. for (let i = 0, len = files.length; i < len; i++) {
  261. const item = files[i];
  262. const match = item.match(regex);
  263. if (match == null) continue;
  264. const matchNum = match[1] ? parseInt(match[1], 10) : 0;
  265. if (matchNum > num) {
  266. num = matchNum;
  267. }
  268. }
  269. return join(dirname(path), `${base}-${num + 1}${ext}`);
  270. }
  271. async function _ensurePath(path) {
  272. if (!await exists(path)) return path;
  273. const files = await fsPromises.readdir(dirname(path));
  274. return _findUnusedPath(path, files);
  275. }
  276. function ensurePath(path) {
  277. if (!path) throw new TypeError('path is required!');
  278. return Promise.resolve(_ensurePath(path));
  279. }
  280. function ensurePathSync(path) {
  281. if (!path) throw new TypeError('path is required!');
  282. if (!fs.existsSync(path)) return path;
  283. const files = fs.readdirSync(dirname(path));
  284. return _findUnusedPath(path, files);
  285. }
  286. function ensureWriteStream(path, options = {}) {
  287. if (!path) throw new TypeError('path is required!');
  288. return checkParent(path).then(() => fs.createWriteStream(path, options));
  289. }
  290. function ensureWriteStreamSync(path, options) {
  291. if (!path) throw new TypeError('path is required!');
  292. fs.mkdirSync(dirname(path), { recursive: true });
  293. return fs.createWriteStream(path, options);
  294. }
  295. // access
  296. ['F_OK', 'R_OK', 'W_OK', 'X_OK'].forEach(key => {
  297. Object.defineProperty(exports, key, {
  298. enumerable: true,
  299. value: fs.constants[key],
  300. writable: false
  301. });
  302. });
  303. exports.access = Promise.promisify(fs.access);
  304. exports.accessSync = fs.accessSync;
  305. // appendFile
  306. exports.appendFile = appendFile;
  307. exports.appendFileSync = appendFileSync;
  308. // chmod
  309. exports.chmod = Promise.promisify(fs.chmod);
  310. exports.chmodSync = fs.chmodSync;
  311. exports.fchmod = Promise.promisify(fs.fchmod);
  312. exports.fchmodSync = fs.fchmodSync;
  313. exports.lchmod = Promise.promisify(fs.lchmod);
  314. exports.lchmodSync = fs.lchmodSync;
  315. // chown
  316. exports.chown = Promise.promisify(fs.chown);
  317. exports.chownSync = fs.chownSync;
  318. exports.fchown = Promise.promisify(fs.fchown);
  319. exports.fchownSync = fs.fchownSync;
  320. exports.lchown = Promise.promisify(fs.lchown);
  321. exports.lchownSync = fs.lchownSync;
  322. // close
  323. exports.close = Promise.promisify(fs.close);
  324. exports.closeSync = fs.closeSync;
  325. // copy
  326. exports.copyDir = copyDir;
  327. exports.copyFile = copyFile;
  328. // createStream
  329. exports.createReadStream = fs.createReadStream;
  330. exports.createWriteStream = fs.createWriteStream;
  331. // emptyDir
  332. exports.emptyDir = emptyDir;
  333. exports.emptyDirSync = emptyDirSync;
  334. // ensurePath
  335. exports.ensurePath = ensurePath;
  336. exports.ensurePathSync = ensurePathSync;
  337. // ensureWriteStream
  338. exports.ensureWriteStream = ensureWriteStream;
  339. exports.ensureWriteStreamSync = ensureWriteStreamSync;
  340. // exists
  341. exports.exists = exists;
  342. exports.existsSync = existsSync;
  343. // fsync
  344. exports.fsync = Promise.promisify(fs.fsync);
  345. exports.fsyncSync = fs.fsyncSync;
  346. // link
  347. exports.link = Promise.promisify(fs.link);
  348. exports.linkSync = fs.linkSync;
  349. // listDir
  350. exports.listDir = listDir;
  351. exports.listDirSync = listDirSync;
  352. // mkdir
  353. exports.mkdir = Promise.promisify(fs.mkdir);
  354. exports.mkdirSync = fs.mkdirSync;
  355. // mkdirs
  356. exports.mkdirs = mkdirs;
  357. exports.mkdirsSync = mkdirsSync;
  358. // open
  359. exports.open = Promise.promisify(fs.open);
  360. exports.openSync = fs.openSync;
  361. // symlink
  362. exports.symlink = Promise.promisify(fs.symlink);
  363. exports.symlinkSync = fs.symlinkSync;
  364. // read
  365. exports.read = Promise.promisify(fs.read);
  366. exports.readSync = fs.readSync;
  367. // readdir
  368. exports.readdir = Promise.promisify(fs.readdir);
  369. exports.readdirSync = fs.readdirSync;
  370. // readFile
  371. exports.readFile = readFile;
  372. exports.readFileSync = readFileSync;
  373. // readlink
  374. exports.readlink = Promise.promisify(fs.readlink);
  375. exports.readlinkSync = fs.readlinkSync;
  376. // realpath
  377. exports.realpath = Promise.promisify(fs.realpath);
  378. exports.realpathSync = fs.realpathSync;
  379. // rename
  380. exports.rename = Promise.promisify(fs.rename);
  381. exports.renameSync = fs.renameSync;
  382. // rmdir
  383. exports.rmdir = rmdir;
  384. exports.rmdirSync = rmdirSync;
  385. // stat
  386. exports.stat = Promise.promisify(fs.stat);
  387. exports.statSync = fs.statSync;
  388. exports.fstat = Promise.promisify(fs.fstat);
  389. exports.fstatSync = fs.fstatSync;
  390. exports.lstat = Promise.promisify(fs.lstat);
  391. exports.lstatSync = fs.lstatSync;
  392. // truncate
  393. exports.truncate = Promise.promisify(fs.truncate);
  394. exports.truncateSync = fs.truncateSync;
  395. exports.ftruncate = Promise.promisify(fs.ftruncate);
  396. exports.ftruncateSync = fs.ftruncateSync;
  397. // unlink
  398. exports.unlink = Promise.promisify(fs.unlink);
  399. exports.unlinkSync = fs.unlinkSync;
  400. // utimes
  401. exports.utimes = Promise.promisify(fs.utimes);
  402. exports.utimesSync = fs.utimesSync;
  403. exports.futimes = Promise.promisify(fs.futimes);
  404. exports.futimesSync = fs.futimesSync;
  405. // watch
  406. exports.watch = watch;
  407. exports.watchFile = fs.watchFile;
  408. exports.unwatchFile = fs.unwatchFile;
  409. // write
  410. exports.write = Promise.promisify(fs.write);
  411. exports.writeSync = fs.writeSync;
  412. // writeFile
  413. exports.writeFile = writeFile;
  414. exports.writeFileSync = writeFileSync;
  415. // Static classes
  416. exports.Stats = fs.Stats;
  417. exports.ReadStream = fs.ReadStream;
  418. exports.WriteStream = fs.WriteStream;
  419. exports.FileReadStream = fs.FileReadStream;
  420. exports.FileWriteStream = fs.FileWriteStream;
  421. // util
  422. exports.escapeBOM = escapeBOM;
  423. exports.escapeEOL = escapeEOL;