cpx.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2016 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. "use strict";
  7. var _getIterator2 = require("babel-runtime/core-js/get-iterator");
  8. var _getIterator3 = _interopRequireDefault(_getIterator2);
  9. var _setImmediate2 = require("babel-runtime/core-js/set-immediate");
  10. var _setImmediate3 = _interopRequireDefault(_setImmediate2);
  11. var _getPrototypeOf = require("babel-runtime/core-js/object/get-prototype-of");
  12. var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
  13. var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
  14. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  15. var _createClass2 = require("babel-runtime/helpers/createClass");
  16. var _createClass3 = _interopRequireDefault(_createClass2);
  17. var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn");
  18. var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
  19. var _inherits2 = require("babel-runtime/helpers/inherits");
  20. var _inherits3 = _interopRequireDefault(_inherits2);
  21. var _symbol = require("babel-runtime/core-js/symbol");
  22. var _symbol2 = _interopRequireDefault(_symbol);
  23. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  24. var _require = require("events");
  25. var EventEmitter = _require.EventEmitter;
  26. var fs = require("fs");
  27. var _require2 = require("path");
  28. var dirname = _require2.dirname;
  29. var resolvePath = _require2.resolve;
  30. var relativePath = _require2.relative;
  31. var joinPath = _require2.join;
  32. var _require3 = require("chokidar");
  33. var createWatcher = _require3.watch;
  34. var _require4 = require("glob");
  35. var Glob = _require4.Glob;
  36. var searchSync = _require4.sync;
  37. var getBasePath = require("glob2base");
  38. var mkdir = require("mkdirp");
  39. var mkdirSync = mkdir.sync;
  40. var _require5 = require("minimatch");
  41. var Minimatch = _require5.Minimatch;
  42. var copyFile = require("./copy");
  43. var copyFileSync = require("./copy-sync");
  44. var Queue = require("./queue");
  45. var BASE_DIR = (0, _symbol2.default)("baseDir");
  46. var DEREFERENCE = (0, _symbol2.default)("dereference");
  47. var INCLUDE_EMPTY_DIRS = (0, _symbol2.default)("include-empty-dirs");
  48. var INITIAL_COPY = (0, _symbol2.default)("initialCopy");
  49. var OUT_DIR = (0, _symbol2.default)("outDir");
  50. var PRESERVE = (0, _symbol2.default)("preserve");
  51. var SOURCE = (0, _symbol2.default)("source");
  52. var TRANSFORM = (0, _symbol2.default)("transform");
  53. var UPDATE = (0, _symbol2.default)("update");
  54. var QUEUE = (0, _symbol2.default)("queue");
  55. var WATCHER = (0, _symbol2.default)("watcher");
  56. /**
  57. * Converts a file path to use glob.
  58. * Glob doesn't support the delimiter of Windows.
  59. *
  60. * @param {string} path - A path to convert.
  61. * @returns {string} The normalized path.
  62. */
  63. function normalizePath(path) {
  64. if (path == null) {
  65. return null;
  66. }
  67. var normalizedPath = relativePath(process.cwd(), resolvePath(path));
  68. normalizedPath = normalizedPath.replace(/\\/g, "/");
  69. if (/\/$/.test(normalizedPath)) {
  70. normalizedPath = normalizedPath.slice(0, -1);
  71. }
  72. return normalizedPath || ".";
  73. }
  74. /**
  75. * Applys a given action for each file that matches with a given pattern.
  76. *
  77. * @param {Cpx} cpx - An instance.
  78. * @param {string} pattern - A pattern to find files.
  79. * @param {function} action - A predicate function to apply.
  80. * @returns {void}
  81. */
  82. function doAllSimply(cpx, pattern, action) {
  83. new Glob(pattern, { nodir: !cpx.includeEmptyDirs, silent: true }).on("match", action.bind(cpx));
  84. }
  85. /**
  86. * Applys a given action for each file that matches with a given pattern.
  87. * Then calls a given callback function after done.
  88. *
  89. * @param {Cpx} cpx - An instance.
  90. * @param {string} pattern - A pattern to find files.
  91. * @param {function} action - A predicate function to apply.
  92. * @param {function} cb - A callback function.
  93. * @returns {void}
  94. */
  95. function doAll(cpx, pattern, action, cb) {
  96. if (cb == null) {
  97. doAllSimply(cpx, pattern, action);
  98. return;
  99. }
  100. var count = 0;
  101. var done = false;
  102. var lastError = null;
  103. /**
  104. * Calls the callback function if done.
  105. * @returns {void}
  106. */
  107. function cbIfEnd() {
  108. if (done && count === 0) {
  109. cb(lastError);
  110. }
  111. }
  112. new Glob(pattern, {
  113. nodir: !cpx.includeEmptyDirs,
  114. silent: true,
  115. follow: cpx.dereference
  116. }).on("match", function (path) {
  117. if (lastError != null) {
  118. return;
  119. }
  120. count += 1;
  121. action.call(cpx, path, function (err) {
  122. count -= 1;
  123. lastError = lastError || err;
  124. cbIfEnd();
  125. });
  126. }).on("end", function () {
  127. done = true;
  128. cbIfEnd();
  129. }).on("error", function (err) {
  130. lastError = lastError || err;
  131. });
  132. }
  133. module.exports = function (_EventEmitter) {
  134. (0, _inherits3.default)(Cpx, _EventEmitter);
  135. /**
  136. * @param {string} source - A blob for copy files.
  137. * @param {string} outDir - A file path for the destination directory.
  138. * @param {object} options - An options object.
  139. */
  140. function Cpx(source, outDir, options) {
  141. (0, _classCallCheck3.default)(this, Cpx);
  142. options = options || {}; // eslint-disable-line no-param-reassign
  143. var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Cpx).call(this));
  144. _this[SOURCE] = normalizePath(source);
  145. _this[OUT_DIR] = normalizePath(outDir);
  146. _this[DEREFERENCE] = Boolean(options.dereference);
  147. _this[INCLUDE_EMPTY_DIRS] = Boolean(options.includeEmptyDirs);
  148. _this[INITIAL_COPY] = options.initialCopy === undefined || Boolean(options.initialCopy);
  149. _this[PRESERVE] = Boolean(options.preserve);
  150. _this[TRANSFORM] = [].concat(options.transform).filter(Boolean);
  151. _this[UPDATE] = Boolean(options.update);
  152. _this[QUEUE] = new Queue();
  153. _this[BASE_DIR] = null;
  154. _this[WATCHER] = null;
  155. return _this;
  156. }
  157. //==========================================================================
  158. // Commons
  159. //--------------------------------------------------------------------------
  160. /**
  161. * The source file glob to copy.
  162. * @type {string}
  163. */
  164. (0, _createClass3.default)(Cpx, [{
  165. key: "src2dst",
  166. /**
  167. * Convert a glob from source to destination.
  168. *
  169. * @param {string} path - A path to convert.
  170. * @returns {string} The converted path.
  171. */
  172. value: function src2dst(path) {
  173. if (this.base === ".") {
  174. return joinPath(this.outDir, path);
  175. }
  176. return path.replace(this.base, this.outDir);
  177. }
  178. /**
  179. * Copy a file.
  180. *
  181. * @param {string} srcPath - A file path to copy.
  182. * @param {function} [cb = null] - A callback function.
  183. * @returns {void}
  184. */
  185. }, {
  186. key: "enqueueCopy",
  187. value: function enqueueCopy(srcPath) {
  188. var _this2 = this;
  189. var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
  190. var dstPath = this.src2dst(srcPath);
  191. if (dstPath === srcPath) {
  192. if (cb != null) {
  193. (0, _setImmediate3.default)(cb, null);
  194. return;
  195. }
  196. }
  197. this[QUEUE].push(function (next) {
  198. mkdir(dirname(dstPath), next);
  199. });
  200. this[QUEUE].push(function (next) {
  201. copyFile(srcPath, dstPath, _this2, function (err) {
  202. if (err == null) {
  203. _this2.emit("copy", { srcPath: srcPath, dstPath: dstPath });
  204. }
  205. next();
  206. if (cb != null) {
  207. cb(err || null);
  208. }
  209. });
  210. });
  211. }
  212. /**
  213. * Remove a file.
  214. *
  215. * @param {string} path - A file path to remove.
  216. * @param {function} [cb = null] - A callback function.
  217. * @returns {void}
  218. */
  219. }, {
  220. key: "enqueueRemove",
  221. value: function enqueueRemove(path) {
  222. var _this3 = this;
  223. var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
  224. var lastError = null;
  225. var stat = null;
  226. this[QUEUE].push(function (next) {
  227. fs.stat(path, function (err, result) {
  228. lastError = err;
  229. stat = result;
  230. next();
  231. });
  232. });
  233. this[QUEUE].push(function (next) {
  234. if (stat && stat.isDirectory()) {
  235. fs.rmdir(path, function (err) {
  236. if (err == null) {
  237. _this3.emit("remove", { path: path });
  238. }
  239. lastError = err;
  240. next();
  241. });
  242. } else {
  243. fs.unlink(path, function (err) {
  244. if (err == null) {
  245. _this3.emit("remove", { path: path });
  246. }
  247. lastError = err;
  248. next();
  249. });
  250. }
  251. });
  252. this[QUEUE].push(function (next) {
  253. fs.rmdir(dirname(path), function () {
  254. next();
  255. if (cb != null) {
  256. cb(lastError);
  257. }
  258. });
  259. });
  260. }
  261. //==========================================================================
  262. // Clean Methods
  263. //--------------------------------------------------------------------------
  264. /**
  265. * Remove all files that matches `this.source` like pattern in `this.dest`
  266. * directory.
  267. * @param {function} [cb = null] - A callback function.
  268. * @returns {void}
  269. */
  270. }, {
  271. key: "clean",
  272. value: function clean() {
  273. var cb = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
  274. var dest = this.src2dst(this.source);
  275. if (dest === this.source) {
  276. if (cb != null) {
  277. (0, _setImmediate3.default)(cb, null);
  278. }
  279. return;
  280. }
  281. doAll(this, dest, this.enqueueRemove, cb);
  282. }
  283. /**
  284. * Remove all files that matches `this.source` like pattern in `this.dest`
  285. * directory.
  286. * @returns {void}
  287. * @thrpws {Error} IO error.
  288. */
  289. }, {
  290. key: "cleanSync",
  291. value: function cleanSync() {
  292. var dest = this.src2dst(this.source);
  293. if (dest === this.source) {
  294. return;
  295. }
  296. var _iteratorNormalCompletion = true;
  297. var _didIteratorError = false;
  298. var _iteratorError = undefined;
  299. try {
  300. for (var _iterator = (0, _getIterator3.default)(searchSync(dest, {
  301. nodir: !this.includeEmptyDirs,
  302. silent: true
  303. })), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  304. var path = _step.value;
  305. try {
  306. var stat = fs.statSync(path);
  307. if (stat.isDirectory()) {
  308. fs.rmdirSync(path);
  309. } else {
  310. fs.unlinkSync(path);
  311. }
  312. } catch (err) {
  313. if (err.code !== "ENOENT") {
  314. throw err;
  315. }
  316. }
  317. try {
  318. fs.rmdirSync(dirname(path));
  319. } catch (err) {
  320. if (err.code !== "ENOTEMPTY") {
  321. throw err;
  322. }
  323. }
  324. this.emit("remove", { path: path });
  325. }
  326. } catch (err) {
  327. _didIteratorError = true;
  328. _iteratorError = err;
  329. } finally {
  330. try {
  331. if (!_iteratorNormalCompletion && _iterator.return) {
  332. _iterator.return();
  333. }
  334. } finally {
  335. if (_didIteratorError) {
  336. throw _iteratorError;
  337. }
  338. }
  339. }
  340. }
  341. //============================================================================
  342. // Copy Methods
  343. //----------------------------------------------------------------------------
  344. /**
  345. * Copy all files that matches `this.source` pattern to `this.outDir`.
  346. *
  347. * @param {function} [cb = null] - A callback function.
  348. * @returns {void}
  349. */
  350. }, {
  351. key: "copy",
  352. value: function copy() {
  353. var cb = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
  354. doAll(this, this.source, this.enqueueCopy, cb);
  355. }
  356. /**
  357. * Copy all files that matches `this.source` pattern to `this.outDir`.
  358. *
  359. * @returns {void}
  360. * @thrpws {Error} IO error.
  361. */
  362. }, {
  363. key: "copySync",
  364. value: function copySync() {
  365. var _this4 = this;
  366. if (this.transformFactories.length > 0) {
  367. throw new Error("Synchronous copy can't use the transform option.");
  368. }
  369. var srcPaths = searchSync(this.source, {
  370. nodir: !this.includeEmptyDirs,
  371. silent: true,
  372. follow: this.dereference
  373. });
  374. srcPaths.forEach(function (srcPath) {
  375. var dstPath = _this4.src2dst(srcPath);
  376. if (dstPath === srcPath) {
  377. return;
  378. }
  379. mkdirSync(dirname(dstPath));
  380. copyFileSync(srcPath, dstPath, _this4);
  381. _this4.emit("copy", { srcPath: srcPath, dstPath: dstPath });
  382. });
  383. }
  384. //============================================================================
  385. // Watch Methods
  386. //----------------------------------------------------------------------------
  387. /**
  388. * Copy all files that matches `this.source` pattern to `this.outDir`.
  389. * And watch changes in `this.base`, and copy only the file every time.
  390. *
  391. * @returns {void}
  392. * @throws {Error} This had been watching already.
  393. */
  394. }, {
  395. key: "watch",
  396. value: function watch() {
  397. var _this5 = this;
  398. if (this[WATCHER] != null) {
  399. throw new Error("InvalidStateError");
  400. }
  401. var m = new Minimatch(this.source);
  402. var firstCopyCount = 0;
  403. var ready = false;
  404. var fireReadyIfReady = function fireReadyIfReady() {
  405. if (ready && firstCopyCount === 0) {
  406. _this5.emit("watch-ready");
  407. }
  408. };
  409. var onAdded = function onAdded(path) {
  410. var normalizedPath = normalizePath(path);
  411. if (m.match(normalizedPath)) {
  412. if (ready) {
  413. _this5.enqueueCopy(normalizedPath);
  414. } else if (_this5.initialCopy) {
  415. firstCopyCount += 1;
  416. _this5.enqueueCopy(normalizedPath, function () {
  417. firstCopyCount -= 1;
  418. fireReadyIfReady();
  419. });
  420. }
  421. }
  422. };
  423. var onRemoved = function onRemoved(path) {
  424. var normalizedPath = normalizePath(path);
  425. if (m.match(normalizedPath)) {
  426. var dstPath = _this5.src2dst(normalizedPath);
  427. if (dstPath !== normalizedPath) {
  428. _this5.enqueueRemove(dstPath);
  429. }
  430. }
  431. };
  432. this[WATCHER] = createWatcher(this.base, {
  433. cwd: process.cwd(),
  434. persistent: true,
  435. followSymlinks: this.dereference
  436. });
  437. this[WATCHER].on("add", onAdded).on("addDir", onAdded).on("unlink", onRemoved).on("unlinkDir", onRemoved).on("change", function (path) {
  438. var normalizedPath = normalizePath(path);
  439. if (m.match(normalizedPath)) {
  440. _this5.enqueueCopy(normalizedPath);
  441. }
  442. }).on("ready", function () {
  443. ready = true;
  444. fireReadyIfReady();
  445. }).on("error", function (err) {
  446. _this5.emit("watch-error", err);
  447. });
  448. }
  449. /**
  450. * Stop watching.
  451. *
  452. * @returns {void}
  453. */
  454. }, {
  455. key: "unwatch",
  456. value: function unwatch() {
  457. if (this[WATCHER] != null) {
  458. this[WATCHER].close();
  459. this[WATCHER] = null;
  460. }
  461. }
  462. /**
  463. * Stop watching.
  464. *
  465. * @returns {void}
  466. */
  467. }, {
  468. key: "close",
  469. value: function close() {
  470. this.unwatch();
  471. }
  472. }, {
  473. key: "source",
  474. get: function get() {
  475. return this[SOURCE];
  476. }
  477. /**
  478. * The destination directory to copy.
  479. * @type {string}
  480. */
  481. }, {
  482. key: "outDir",
  483. get: function get() {
  484. return this[OUT_DIR];
  485. }
  486. /**
  487. * The flag to follow symbolic links.
  488. * @type {boolean}
  489. */
  490. }, {
  491. key: "dereference",
  492. get: function get() {
  493. return this[DEREFERENCE];
  494. }
  495. /**
  496. * The flag to copy empty directories which is matched with the glob.
  497. * @type {boolean}
  498. */
  499. }, {
  500. key: "includeEmptyDirs",
  501. get: function get() {
  502. return this[INCLUDE_EMPTY_DIRS];
  503. }
  504. /**
  505. * The flag to copy files at the initial time of watch.
  506. * @type {boolean}
  507. */
  508. }, {
  509. key: "initialCopy",
  510. get: function get() {
  511. return this[INITIAL_COPY];
  512. }
  513. /**
  514. * The flag to copy file attributes.
  515. * @type {boolean}
  516. */
  517. }, {
  518. key: "preserve",
  519. get: function get() {
  520. return this[PRESERVE];
  521. }
  522. /**
  523. * The factories of transform streams.
  524. * @type {function[]}
  525. */
  526. }, {
  527. key: "transformFactories",
  528. get: function get() {
  529. return this[TRANSFORM];
  530. }
  531. /**
  532. * The flag to disallow overwriting.
  533. * @type {boolean}
  534. */
  535. }, {
  536. key: "update",
  537. get: function get() {
  538. return this[UPDATE];
  539. }
  540. /**
  541. * The base directory of `this.source`.
  542. * @type {string}
  543. */
  544. }, {
  545. key: "base",
  546. get: function get() {
  547. if (this[BASE_DIR] == null) {
  548. this[BASE_DIR] = normalizePath(getBasePath(new Glob(this.source)));
  549. }
  550. return this[BASE_DIR];
  551. }
  552. }]);
  553. return Cpx;
  554. }(EventEmitter);