fs.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. 'use strict';
  2. var Promise = require('bluebird');
  3. var fs = require('graceful-fs');
  4. var pathFn = require('path');
  5. var chokidar = require('chokidar');
  6. var escapeRegExp = require('escape-string-regexp');
  7. var dirname = pathFn.dirname;
  8. var join = pathFn.join;
  9. var rEOL = new RegExp('\\r\\n', 'g');
  10. var statAsync = Promise.promisify(fs.stat);
  11. var readdirAsync = Promise.promisify(fs.readdir);
  12. var unlinkAsync = Promise.promisify(fs.unlink);
  13. var mkdirAsync = Promise.promisify(fs.mkdir);
  14. var writeFileAsync = Promise.promisify(fs.writeFile);
  15. var appendFileAsync = Promise.promisify(fs.appendFile);
  16. var rmdirAsync = Promise.promisify(fs.rmdir);
  17. var readFileAsync = Promise.promisify(fs.readFile);
  18. var createReadStream = fs.createReadStream;
  19. var createWriteStream = fs.createWriteStream;
  20. var accessSync = fs.accessSync;
  21. var accessAsync, exists, existsSync;
  22. if (fs.access) {
  23. accessAsync = Promise.promisify(fs.access);
  24. exists = _existsNew;
  25. existsSync = _existsSyncNew;
  26. } else {
  27. exists = _existsOld;
  28. existsSync = fs.existsSync;
  29. }
  30. function _existsOld(path, callback) {
  31. if (!path) throw new TypeError('path is required!');
  32. return new Promise(function(resolve, reject) {
  33. fs.exists(path, function(exist) {
  34. resolve(exist);
  35. if (typeof callback === 'function') callback(exist);
  36. });
  37. });
  38. }
  39. function _existsNew(path, callback) {
  40. if (!path) throw new TypeError('path is required!');
  41. return accessAsync(path).then(function() {
  42. return true;
  43. }, function() {
  44. return false;
  45. }).then(function(exist) {
  46. if (typeof callback === 'function') callback(exist);
  47. return exist;
  48. });
  49. }
  50. function _existsSyncNew(path) {
  51. if (!path) throw new TypeError('path is required!');
  52. try {
  53. fs.accessSync(path);
  54. } catch (err) {
  55. return false;
  56. }
  57. return true;
  58. }
  59. function mkdirs(path, callback) {
  60. if (!path) throw new TypeError('path is required!');
  61. var parent = dirname(path);
  62. return exists(parent).then(function(exist) {
  63. if (!exist) return mkdirs(parent);
  64. }).then(function() {
  65. return mkdirAsync(path);
  66. }).catch(function(err) {
  67. if (err.cause.code !== 'EEXIST') throw err;
  68. }).asCallback(callback);
  69. }
  70. function mkdirsSync(path) {
  71. if (!path) throw new TypeError('path is required!');
  72. var parent = dirname(path);
  73. var exist = fs.existsSync(parent);
  74. if (!exist) mkdirsSync(parent);
  75. fs.mkdirSync(path);
  76. }
  77. function checkParent(path) {
  78. if (!path) throw new TypeError('path is required!');
  79. var parent = dirname(path);
  80. return exists(parent).then(function(exist) {
  81. if (!exist) return mkdirs(parent);
  82. });
  83. }
  84. function checkParentSync(path) {
  85. if (!path) throw new TypeError('path is required!');
  86. var parent = dirname(path);
  87. var exist = fs.existsSync(parent);
  88. if (exist) return;
  89. try {
  90. mkdirsSync(parent);
  91. } catch (err) {
  92. if (err.code !== 'EEXIST') throw err;
  93. }
  94. }
  95. function writeFile(path, data, options, callback) {
  96. if (!path) throw new TypeError('path is required!');
  97. if (!callback && typeof options === 'function') {
  98. callback = options;
  99. options = {};
  100. }
  101. return checkParent(path).then(function() {
  102. return writeFileAsync(path, data, options);
  103. }).asCallback(callback);
  104. }
  105. function writeFileSync(path, data, options) {
  106. if (!path) throw new TypeError('path is required!');
  107. checkParentSync(path);
  108. fs.writeFileSync(path, data, options);
  109. }
  110. function appendFile(path, data, options, callback) {
  111. if (!path) throw new TypeError('path is required!');
  112. if (!callback && typeof options === 'function') {
  113. callback = options;
  114. options = {};
  115. }
  116. return checkParent(path).then(function() {
  117. return appendFileAsync(path, data, options);
  118. }).asCallback(callback);
  119. }
  120. function appendFileSync(path, data, options) {
  121. if (!path) throw new TypeError('path is required!');
  122. checkParentSync(path);
  123. fs.appendFileSync(path, data, options);
  124. }
  125. const _copyFile = fs.copyFile ? Promise.promisify(fs.copyFile) : function(src, dest) {
  126. return new Promise(function(resolve, reject) {
  127. var rs = createReadStream(src);
  128. var ws = createWriteStream(dest);
  129. rs.pipe(ws).on('error', reject);
  130. ws.on('close', resolve).on('error', reject);
  131. });
  132. };
  133. function copyFile(src, dest, flags, callback) {
  134. if (!src) throw new TypeError('src is required!');
  135. if (!dest) throw new TypeError('dest is required!');
  136. if (typeof flags === 'function') {
  137. callback = flags;
  138. }
  139. return checkParent(dest).then(function() {
  140. return _copyFile(src, dest, flags);
  141. }).asCallback(callback);
  142. }
  143. function trueFn() {
  144. return true;
  145. }
  146. function ignoreHiddenFiles(ignore) {
  147. if (!ignore) return trueFn;
  148. return function(item) {
  149. return item[0] !== '.';
  150. };
  151. }
  152. function ignoreFilesRegex(regex) {
  153. if (!regex) return trueFn;
  154. return function(item) {
  155. return !regex.test(item);
  156. };
  157. }
  158. function ignoreExcludeFiles(arr, parent) {
  159. if (!arr || !arr.length) return trueFn;
  160. // Build a map to make it faster.
  161. var map = {};
  162. for (var i = 0, len = arr.length; i < len; i++) {
  163. map[arr[i]] = true;
  164. }
  165. return function(item) {
  166. var path = join(parent, item.path);
  167. return !map[path];
  168. };
  169. }
  170. function reduceFiles(result, item) {
  171. if (Array.isArray(item)) {
  172. return result.concat(item);
  173. }
  174. result.push(item);
  175. return result;
  176. }
  177. function _readAndFilterDir(path, options) {
  178. return readdirAsync(path)
  179. .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden))
  180. .filter(ignoreFilesRegex(options.ignorePattern))
  181. .map(function(item) {
  182. var fullPath = join(path, item);
  183. return statAsync(fullPath).then(function(stats) {
  184. return {
  185. isDirectory: stats.isDirectory(),
  186. path: item,
  187. fullPath: fullPath
  188. };
  189. });
  190. });
  191. }
  192. function _readAndFilterDirSync(path, options) {
  193. return fs.readdirSync(path)
  194. .filter(ignoreHiddenFiles(options.ignoreHidden == null ? true : options.ignoreHidden))
  195. .filter(ignoreFilesRegex(options.ignorePattern))
  196. .map(function(item) {
  197. var fullPath = join(path, item);
  198. var stats = fs.statSync(fullPath);
  199. return {
  200. isDirectory: stats.isDirectory(),
  201. path: item,
  202. fullPath: fullPath
  203. };
  204. });
  205. }
  206. function _copyDir(src, dest, options, parent) {
  207. options = options || {};
  208. parent = parent || '';
  209. return checkParent(dest).then(function() {
  210. return _readAndFilterDir(src, options);
  211. })
  212. .map(function(item) {
  213. var childSrc = item.fullPath;
  214. var childDest = join(dest, item.path);
  215. if (item.isDirectory) {
  216. return _copyDir(childSrc, childDest, options, join(parent, item.path));
  217. }
  218. return copyFile(childSrc, childDest, options)
  219. .thenReturn(join(parent, item.path));
  220. }).reduce(reduceFiles, []);
  221. }
  222. function copyDir(src, dest, options, callback) {
  223. if (!src) throw new TypeError('src is required!');
  224. if (!dest) throw new TypeError('dest is required!');
  225. if (!callback && typeof options === 'function') {
  226. callback = options;
  227. options = {};
  228. }
  229. return _copyDir(src, dest, options).asCallback(callback);
  230. }
  231. function _listDir(path, options, parent) {
  232. options = options || {};
  233. parent = parent || '';
  234. return _readAndFilterDir(path, options).map(function(item) {
  235. if (item.isDirectory) {
  236. return _listDir(item.fullPath, options, join(parent, item.path));
  237. }
  238. return join(parent, item.path);
  239. }).reduce(reduceFiles, []);
  240. }
  241. function listDir(path, options, callback) {
  242. if (!path) throw new TypeError('path is required!');
  243. if (!callback && typeof options === 'function') {
  244. callback = options;
  245. options = {};
  246. }
  247. return _listDir(path, options).asCallback(callback);
  248. }
  249. function listDirSync(path, options, parent) {
  250. if (!path) throw new TypeError('path is required!');
  251. options = options || {};
  252. parent = parent || '';
  253. return _readAndFilterDirSync(path, options).map(function(item) {
  254. if (item.isDirectory) {
  255. return listDirSync(item.fullPath, options, join(parent, item.path));
  256. }
  257. return join(parent, item.path);
  258. }).reduce(reduceFiles, []);
  259. }
  260. function escapeEOL(str) {
  261. return str.replace(rEOL, '\n');
  262. }
  263. function escapeBOM(str) {
  264. return str.charCodeAt(0) === 0xFEFF ? str.substring(1) : str;
  265. }
  266. function escapeFileContent(content) {
  267. return escapeBOM(escapeEOL(content));
  268. }
  269. function readFile(path, options, callback) {
  270. if (!path) throw new TypeError('path is required!');
  271. if (!callback && typeof options === 'function') {
  272. callback = options;
  273. options = {};
  274. }
  275. options = options || {};
  276. if (!options.hasOwnProperty('encoding')) options.encoding = 'utf8';
  277. return readFileAsync(path, options).then(function(content) {
  278. if (options.escape == null || options.escape) {
  279. return escapeFileContent(content);
  280. }
  281. return content;
  282. }).asCallback(callback);
  283. }
  284. function readFileSync(path, options) {
  285. if (!path) throw new TypeError('path is required!');
  286. options = options || {};
  287. if (!options.hasOwnProperty('encoding')) options.encoding = 'utf8';
  288. var content = fs.readFileSync(path, options);
  289. if (options.escape == null || options.escape) {
  290. return escapeFileContent(content);
  291. }
  292. return content;
  293. }
  294. function _emptyDir(path, options, parent) {
  295. options = options || {};
  296. parent = parent || '';
  297. return _readAndFilterDir(path, options)
  298. .filter(ignoreExcludeFiles(options.exclude, parent))
  299. .map(function(item) {
  300. var fullPath = item.fullPath;
  301. if (item.isDirectory) {
  302. return _emptyDir(fullPath, options, join(parent, item.path)).then(function(removed) {
  303. return readdirAsync(fullPath).then(function(files) {
  304. if (!files.length) return rmdirAsync(fullPath);
  305. }).thenReturn(removed);
  306. });
  307. }
  308. return unlinkAsync(fullPath).thenReturn(join(parent, item.path));
  309. }).reduce(reduceFiles, []);
  310. }
  311. function emptyDir(path, options, callback) {
  312. if (!path) throw new TypeError('path is required!');
  313. if (!callback && typeof options === 'function') {
  314. callback = options;
  315. options = {};
  316. }
  317. return _emptyDir(path, options).asCallback(callback);
  318. }
  319. function emptyDirSync(path, options, parent) {
  320. if (!path) throw new TypeError('path is required!');
  321. options = options || {};
  322. parent = parent || '';
  323. return _readAndFilterDirSync(path, options)
  324. .filter(ignoreExcludeFiles(options.exclude, parent))
  325. .map(function(item) {
  326. var childPath = item.fullPath;
  327. if (item.isDirectory) {
  328. var removed = emptyDirSync(childPath, options, join(parent, item.path));
  329. if (!fs.readdirSync(childPath).length) {
  330. rmdirSync(childPath);
  331. }
  332. return removed;
  333. }
  334. fs.unlinkSync(childPath);
  335. return join(parent, item.path);
  336. }).reduce(reduceFiles, []);
  337. }
  338. function rmdir(path, callback) {
  339. if (!path) throw new TypeError('path is required!');
  340. return readdirAsync(path).map(function(item) {
  341. var childPath = join(path, item);
  342. return statAsync(childPath).then(function(stats) {
  343. if (stats.isDirectory()) {
  344. return rmdir(childPath);
  345. }
  346. return unlinkAsync(childPath);
  347. });
  348. }).then(function() {
  349. return rmdirAsync(path);
  350. }).asCallback(callback);
  351. }
  352. function rmdirSync(path) {
  353. if (!path) throw new TypeError('path is required!');
  354. var files = fs.readdirSync(path);
  355. var childPath, stats;
  356. for (var i = 0, len = files.length; i < len; i++) {
  357. childPath = join(path, files[i]);
  358. stats = fs.statSync(childPath);
  359. if (stats.isDirectory()) {
  360. rmdirSync(childPath);
  361. } else {
  362. fs.unlinkSync(childPath);
  363. }
  364. }
  365. fs.rmdirSync(path);
  366. }
  367. function watch(path, options, callback) {
  368. if (!path) throw new TypeError('path is required!');
  369. if (!callback && typeof options === 'function') {
  370. callback = options;
  371. options = {};
  372. }
  373. return new Promise(function(resolve, reject) {
  374. var watcher = chokidar.watch(path, options);
  375. watcher.on('ready', function() {
  376. resolve(watcher);
  377. });
  378. watcher.on('error', reject);
  379. }).asCallback(callback);
  380. }
  381. function _findUnusedPath(path, files) {
  382. var extname = pathFn.extname(path);
  383. var basename = pathFn.basename(path, extname);
  384. var regex = new RegExp('^' + escapeRegExp(basename) + '(?:-(\\d+))?' + escapeRegExp(extname) + '$');
  385. var item = '';
  386. var num = -1;
  387. var match, matchNum;
  388. for (var i = 0, len = files.length; i < len; i++) {
  389. item = files[i];
  390. if (!regex.test(item)) continue;
  391. match = item.match(regex);
  392. matchNum = match[1] ? parseInt(match[1], 10) : 0;
  393. if (matchNum > num) {
  394. num = matchNum;
  395. }
  396. }
  397. return join(dirname(path), basename + '-' + (num + 1) + extname);
  398. }
  399. function ensurePath(path, callback) {
  400. if (!path) throw new TypeError('path is required!');
  401. return exists(path).then(function(exist) {
  402. if (!exist) return path;
  403. return readdirAsync(dirname(path)).then(function(files) {
  404. return _findUnusedPath(path, files);
  405. });
  406. }).asCallback(callback);
  407. }
  408. function ensurePathSync(path) {
  409. if (!path) throw new TypeError('path is required!');
  410. if (!fs.existsSync(path)) return path;
  411. var files = fs.readdirSync(dirname(path));
  412. return _findUnusedPath(path, files);
  413. }
  414. function ensureWriteStream(path, options, callback) {
  415. if (!path) throw new TypeError('path is required!');
  416. if (!callback && typeof options === 'function') {
  417. callback = options;
  418. options = {};
  419. }
  420. return checkParent(path).then(function() {
  421. return fs.createWriteStream(path, options);
  422. }).asCallback(callback);
  423. }
  424. function ensureWriteStreamSync(path, options) {
  425. if (!path) throw new TypeError('path is required!');
  426. checkParentSync(path);
  427. return fs.createWriteStream(path, options);
  428. }
  429. // access
  430. if (fs.access) {
  431. ['F_OK', 'R_OK', 'W_OK', 'X_OK'].forEach(function(key) {
  432. Object.defineProperty(exports, key, {
  433. enumerable: true,
  434. value: (fs.constants || fs)[key],
  435. writable: false
  436. });
  437. });
  438. exports.access = accessAsync;
  439. exports.accessSync = accessSync;
  440. }
  441. // appendFile
  442. exports.appendFile = appendFile;
  443. exports.appendFileSync = appendFileSync;
  444. // chmod
  445. exports.chmod = Promise.promisify(fs.chmod);
  446. exports.chmodSync = fs.chmodSync;
  447. exports.fchmod = Promise.promisify(fs.fchmod);
  448. exports.fchmodSync = fs.fchmodSync;
  449. exports.lchmod = Promise.promisify(fs.lchmod);
  450. exports.lchmodSync = fs.lchmodSync;
  451. // chown
  452. exports.chown = Promise.promisify(fs.chown);
  453. exports.chownSync = fs.chownSync;
  454. exports.fchown = Promise.promisify(fs.fchown);
  455. exports.fchownSync = fs.fchownSync;
  456. exports.lchown = Promise.promisify(fs.lchown);
  457. exports.lchownSync = fs.lchownSync;
  458. // close
  459. exports.close = Promise.promisify(fs.close);
  460. exports.closeSync = fs.closeSync;
  461. // copy
  462. exports.copyDir = copyDir;
  463. exports.copyFile = copyFile;
  464. // createStream
  465. exports.createReadStream = createReadStream;
  466. exports.createWriteStream = createWriteStream;
  467. // emptyDir
  468. exports.emptyDir = emptyDir;
  469. exports.emptyDirSync = emptyDirSync;
  470. // ensurePath
  471. exports.ensurePath = ensurePath;
  472. exports.ensurePathSync = ensurePathSync;
  473. // ensureWriteStream
  474. exports.ensureWriteStream = ensureWriteStream;
  475. exports.ensureWriteStreamSync = ensureWriteStreamSync;
  476. // exists
  477. exports.exists = exists;
  478. exports.existsSync = existsSync;
  479. // fsync
  480. exports.fsync = Promise.promisify(fs.fsync);
  481. exports.fsyncSync = fs.fsyncSync;
  482. // link
  483. exports.link = Promise.promisify(fs.link);
  484. exports.linkSync = fs.linkSync;
  485. // listDir
  486. exports.listDir = listDir;
  487. exports.listDirSync = listDirSync;
  488. // mkdir
  489. exports.mkdir = mkdirAsync;
  490. exports.mkdirSync = fs.mkdirSync;
  491. // mkdirs
  492. exports.mkdirs = mkdirs;
  493. exports.mkdirsSync = mkdirsSync;
  494. // open
  495. exports.open = Promise.promisify(fs.open);
  496. exports.openSync = fs.openSync;
  497. // symlink
  498. exports.symlink = Promise.promisify(fs.symlink);
  499. exports.symlinkSync = fs.symlinkSync;
  500. // read
  501. exports.read = Promise.promisify(fs.read);
  502. exports.readSync = fs.readSync;
  503. // readdir
  504. exports.readdir = readdirAsync;
  505. exports.readdirSync = fs.readdirSync;
  506. // readFile
  507. exports.readFile = readFile;
  508. exports.readFileSync = readFileSync;
  509. // readlink
  510. exports.readlink = Promise.promisify(fs.readlink);
  511. exports.readlinkSync = fs.readlinkSync;
  512. // realpath
  513. exports.realpath = Promise.promisify(fs.realpath);
  514. exports.realpathSync = fs.realpathSync;
  515. // rename
  516. exports.rename = Promise.promisify(fs.rename);
  517. exports.renameSync = fs.renameSync;
  518. // rmdir
  519. exports.rmdir = rmdir;
  520. exports.rmdirSync = rmdirSync;
  521. // stat
  522. exports.stat = statAsync;
  523. exports.statSync = fs.statSync;
  524. exports.fstat = Promise.promisify(fs.fstat);
  525. exports.fstatSync = fs.fstatSync;
  526. exports.lstat = Promise.promisify(fs.lstat);
  527. exports.lstatSync = fs.lstatSync;
  528. // truncate
  529. exports.truncate = Promise.promisify(fs.truncate);
  530. exports.truncateSync = fs.truncateSync;
  531. exports.ftruncate = Promise.promisify(fs.ftruncate);
  532. exports.ftruncateSync = fs.ftruncateSync;
  533. // unlink
  534. exports.unlink = unlinkAsync;
  535. exports.unlinkSync = fs.unlinkSync;
  536. // utimes
  537. exports.utimes = Promise.promisify(fs.utimes);
  538. exports.utimesSync = fs.utimesSync;
  539. exports.futimes = Promise.promisify(fs.futimes);
  540. exports.futimesSync = fs.futimesSync;
  541. // watch
  542. exports.watch = watch;
  543. exports.watchFile = fs.watchFile;
  544. exports.unwatchFile = fs.unwatchFile;
  545. // write
  546. exports.write = Promise.promisify(fs.write);
  547. exports.writeSync = fs.writeSync;
  548. // writeFile
  549. exports.writeFile = writeFile;
  550. exports.writeFileSync = writeFileSync;
  551. // Static classes
  552. exports.Stats = fs.Stats;
  553. exports.ReadStream = fs.ReadStream;
  554. exports.WriteStream = fs.WriteStream;
  555. exports.FileReadStream = fs.FileReadStream;
  556. exports.FileWriteStream = fs.FileWriteStream;
  557. // util
  558. exports.escapeBOM = escapeBOM;
  559. exports.escapeEOL = escapeEOL;