/** * @author Toru Nagashima * @copyright 2016 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ "use strict"; var _getIterator2 = require("babel-runtime/core-js/get-iterator"); var _getIterator3 = _interopRequireDefault(_getIterator2); var _setImmediate2 = require("babel-runtime/core-js/set-immediate"); var _setImmediate3 = _interopRequireDefault(_setImmediate2); var _getPrototypeOf = require("babel-runtime/core-js/object/get-prototype-of"); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require("babel-runtime/helpers/createClass"); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require("babel-runtime/helpers/possibleConstructorReturn"); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require("babel-runtime/helpers/inherits"); var _inherits3 = _interopRequireDefault(_inherits2); var _symbol = require("babel-runtime/core-js/symbol"); var _symbol2 = _interopRequireDefault(_symbol); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var _require = require("events"); var EventEmitter = _require.EventEmitter; var fs = require("fs"); var _require2 = require("path"); var dirname = _require2.dirname; var resolvePath = _require2.resolve; var relativePath = _require2.relative; var joinPath = _require2.join; var _require3 = require("chokidar"); var createWatcher = _require3.watch; var _require4 = require("glob"); var Glob = _require4.Glob; var searchSync = _require4.sync; var getBasePath = require("glob2base"); var mkdir = require("mkdirp"); var mkdirSync = mkdir.sync; var _require5 = require("minimatch"); var Minimatch = _require5.Minimatch; var copyFile = require("./copy"); var copyFileSync = require("./copy-sync"); var Queue = require("./queue"); var BASE_DIR = (0, _symbol2.default)("baseDir"); var DEREFERENCE = (0, _symbol2.default)("dereference"); var INCLUDE_EMPTY_DIRS = (0, _symbol2.default)("include-empty-dirs"); var INITIAL_COPY = (0, _symbol2.default)("initialCopy"); var OUT_DIR = (0, _symbol2.default)("outDir"); var PRESERVE = (0, _symbol2.default)("preserve"); var SOURCE = (0, _symbol2.default)("source"); var TRANSFORM = (0, _symbol2.default)("transform"); var UPDATE = (0, _symbol2.default)("update"); var QUEUE = (0, _symbol2.default)("queue"); var WATCHER = (0, _symbol2.default)("watcher"); /** * Converts a file path to use glob. * Glob doesn't support the delimiter of Windows. * * @param {string} path - A path to convert. * @returns {string} The normalized path. */ function normalizePath(path) { if (path == null) { return null; } var normalizedPath = relativePath(process.cwd(), resolvePath(path)); normalizedPath = normalizedPath.replace(/\\/g, "/"); if (/\/$/.test(normalizedPath)) { normalizedPath = normalizedPath.slice(0, -1); } return normalizedPath || "."; } /** * Applys a given action for each file that matches with a given pattern. * * @param {Cpx} cpx - An instance. * @param {string} pattern - A pattern to find files. * @param {function} action - A predicate function to apply. * @returns {void} */ function doAllSimply(cpx, pattern, action) { new Glob(pattern, { nodir: !cpx.includeEmptyDirs, silent: true }).on("match", action.bind(cpx)); } /** * Applys a given action for each file that matches with a given pattern. * Then calls a given callback function after done. * * @param {Cpx} cpx - An instance. * @param {string} pattern - A pattern to find files. * @param {function} action - A predicate function to apply. * @param {function} cb - A callback function. * @returns {void} */ function doAll(cpx, pattern, action, cb) { if (cb == null) { doAllSimply(cpx, pattern, action); return; } var count = 0; var done = false; var lastError = null; /** * Calls the callback function if done. * @returns {void} */ function cbIfEnd() { if (done && count === 0) { cb(lastError); } } new Glob(pattern, { nodir: !cpx.includeEmptyDirs, silent: true, follow: cpx.dereference }).on("match", function (path) { if (lastError != null) { return; } count += 1; action.call(cpx, path, function (err) { count -= 1; lastError = lastError || err; cbIfEnd(); }); }).on("end", function () { done = true; cbIfEnd(); }).on("error", function (err) { lastError = lastError || err; }); } module.exports = function (_EventEmitter) { (0, _inherits3.default)(Cpx, _EventEmitter); /** * @param {string} source - A blob for copy files. * @param {string} outDir - A file path for the destination directory. * @param {object} options - An options object. */ function Cpx(source, outDir, options) { (0, _classCallCheck3.default)(this, Cpx); options = options || {}; // eslint-disable-line no-param-reassign var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Cpx).call(this)); _this[SOURCE] = normalizePath(source); _this[OUT_DIR] = normalizePath(outDir); _this[DEREFERENCE] = Boolean(options.dereference); _this[INCLUDE_EMPTY_DIRS] = Boolean(options.includeEmptyDirs); _this[INITIAL_COPY] = options.initialCopy === undefined || Boolean(options.initialCopy); _this[PRESERVE] = Boolean(options.preserve); _this[TRANSFORM] = [].concat(options.transform).filter(Boolean); _this[UPDATE] = Boolean(options.update); _this[QUEUE] = new Queue(); _this[BASE_DIR] = null; _this[WATCHER] = null; return _this; } //========================================================================== // Commons //-------------------------------------------------------------------------- /** * The source file glob to copy. * @type {string} */ (0, _createClass3.default)(Cpx, [{ key: "src2dst", /** * Convert a glob from source to destination. * * @param {string} path - A path to convert. * @returns {string} The converted path. */ value: function src2dst(path) { if (this.base === ".") { return joinPath(this.outDir, path); } return path.replace(this.base, this.outDir); } /** * Copy a file. * * @param {string} srcPath - A file path to copy. * @param {function} [cb = null] - A callback function. * @returns {void} */ }, { key: "enqueueCopy", value: function enqueueCopy(srcPath) { var _this2 = this; var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; var dstPath = this.src2dst(srcPath); if (dstPath === srcPath) { if (cb != null) { (0, _setImmediate3.default)(cb, null); return; } } this[QUEUE].push(function (next) { mkdir(dirname(dstPath), next); }); this[QUEUE].push(function (next) { copyFile(srcPath, dstPath, _this2, function (err) { if (err == null) { _this2.emit("copy", { srcPath: srcPath, dstPath: dstPath }); } next(); if (cb != null) { cb(err || null); } }); }); } /** * Remove a file. * * @param {string} path - A file path to remove. * @param {function} [cb = null] - A callback function. * @returns {void} */ }, { key: "enqueueRemove", value: function enqueueRemove(path) { var _this3 = this; var cb = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; var lastError = null; var stat = null; this[QUEUE].push(function (next) { fs.stat(path, function (err, result) { lastError = err; stat = result; next(); }); }); this[QUEUE].push(function (next) { if (stat && stat.isDirectory()) { fs.rmdir(path, function (err) { if (err == null) { _this3.emit("remove", { path: path }); } lastError = err; next(); }); } else { fs.unlink(path, function (err) { if (err == null) { _this3.emit("remove", { path: path }); } lastError = err; next(); }); } }); this[QUEUE].push(function (next) { fs.rmdir(dirname(path), function () { next(); if (cb != null) { cb(lastError); } }); }); } //========================================================================== // Clean Methods //-------------------------------------------------------------------------- /** * Remove all files that matches `this.source` like pattern in `this.dest` * directory. * @param {function} [cb = null] - A callback function. * @returns {void} */ }, { key: "clean", value: function clean() { var cb = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; var dest = this.src2dst(this.source); if (dest === this.source) { if (cb != null) { (0, _setImmediate3.default)(cb, null); } return; } doAll(this, dest, this.enqueueRemove, cb); } /** * Remove all files that matches `this.source` like pattern in `this.dest` * directory. * @returns {void} * @thrpws {Error} IO error. */ }, { key: "cleanSync", value: function cleanSync() { var dest = this.src2dst(this.source); if (dest === this.source) { return; } var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = (0, _getIterator3.default)(searchSync(dest, { nodir: !this.includeEmptyDirs, silent: true })), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var path = _step.value; try { var stat = fs.statSync(path); if (stat.isDirectory()) { fs.rmdirSync(path); } else { fs.unlinkSync(path); } } catch (err) { if (err.code !== "ENOENT") { throw err; } } try { fs.rmdirSync(dirname(path)); } catch (err) { if (err.code !== "ENOTEMPTY") { throw err; } } this.emit("remove", { path: path }); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } //============================================================================ // Copy Methods //---------------------------------------------------------------------------- /** * Copy all files that matches `this.source` pattern to `this.outDir`. * * @param {function} [cb = null] - A callback function. * @returns {void} */ }, { key: "copy", value: function copy() { var cb = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0]; doAll(this, this.source, this.enqueueCopy, cb); } /** * Copy all files that matches `this.source` pattern to `this.outDir`. * * @returns {void} * @thrpws {Error} IO error. */ }, { key: "copySync", value: function copySync() { var _this4 = this; if (this.transformFactories.length > 0) { throw new Error("Synchronous copy can't use the transform option."); } var srcPaths = searchSync(this.source, { nodir: !this.includeEmptyDirs, silent: true, follow: this.dereference }); srcPaths.forEach(function (srcPath) { var dstPath = _this4.src2dst(srcPath); if (dstPath === srcPath) { return; } mkdirSync(dirname(dstPath)); copyFileSync(srcPath, dstPath, _this4); _this4.emit("copy", { srcPath: srcPath, dstPath: dstPath }); }); } //============================================================================ // Watch Methods //---------------------------------------------------------------------------- /** * Copy all files that matches `this.source` pattern to `this.outDir`. * And watch changes in `this.base`, and copy only the file every time. * * @returns {void} * @throws {Error} This had been watching already. */ }, { key: "watch", value: function watch() { var _this5 = this; if (this[WATCHER] != null) { throw new Error("InvalidStateError"); } var m = new Minimatch(this.source); var firstCopyCount = 0; var ready = false; var fireReadyIfReady = function fireReadyIfReady() { if (ready && firstCopyCount === 0) { _this5.emit("watch-ready"); } }; var onAdded = function onAdded(path) { var normalizedPath = normalizePath(path); if (m.match(normalizedPath)) { if (ready) { _this5.enqueueCopy(normalizedPath); } else if (_this5.initialCopy) { firstCopyCount += 1; _this5.enqueueCopy(normalizedPath, function () { firstCopyCount -= 1; fireReadyIfReady(); }); } } }; var onRemoved = function onRemoved(path) { var normalizedPath = normalizePath(path); if (m.match(normalizedPath)) { var dstPath = _this5.src2dst(normalizedPath); if (dstPath !== normalizedPath) { _this5.enqueueRemove(dstPath); } } }; this[WATCHER] = createWatcher(this.base, { cwd: process.cwd(), persistent: true, followSymlinks: this.dereference }); this[WATCHER].on("add", onAdded).on("addDir", onAdded).on("unlink", onRemoved).on("unlinkDir", onRemoved).on("change", function (path) { var normalizedPath = normalizePath(path); if (m.match(normalizedPath)) { _this5.enqueueCopy(normalizedPath); } }).on("ready", function () { ready = true; fireReadyIfReady(); }).on("error", function (err) { _this5.emit("watch-error", err); }); } /** * Stop watching. * * @returns {void} */ }, { key: "unwatch", value: function unwatch() { if (this[WATCHER] != null) { this[WATCHER].close(); this[WATCHER] = null; } } /** * Stop watching. * * @returns {void} */ }, { key: "close", value: function close() { this.unwatch(); } }, { key: "source", get: function get() { return this[SOURCE]; } /** * The destination directory to copy. * @type {string} */ }, { key: "outDir", get: function get() { return this[OUT_DIR]; } /** * The flag to follow symbolic links. * @type {boolean} */ }, { key: "dereference", get: function get() { return this[DEREFERENCE]; } /** * The flag to copy empty directories which is matched with the glob. * @type {boolean} */ }, { key: "includeEmptyDirs", get: function get() { return this[INCLUDE_EMPTY_DIRS]; } /** * The flag to copy files at the initial time of watch. * @type {boolean} */ }, { key: "initialCopy", get: function get() { return this[INITIAL_COPY]; } /** * The flag to copy file attributes. * @type {boolean} */ }, { key: "preserve", get: function get() { return this[PRESERVE]; } /** * The factories of transform streams. * @type {function[]} */ }, { key: "transformFactories", get: function get() { return this[TRANSFORM]; } /** * The flag to disallow overwriting. * @type {boolean} */ }, { key: "update", get: function get() { return this[UPDATE]; } /** * The base directory of `this.source`. * @type {string} */ }, { key: "base", get: function get() { if (this[BASE_DIR] == null) { this[BASE_DIR] = normalizePath(getBasePath(new Glob(this.source))); } return this[BASE_DIR]; } }]); return Cpx; }(EventEmitter);