sync-glob
Version:
Synchronize files and folders locally by glob patterns, watch option included.
283 lines (220 loc) • 8.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* globals process */
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _globAll = require('glob-all');
var _globAll2 = _interopRequireDefault(_globAll);
var _chokidar = require('chokidar');
var _chokidar2 = _interopRequireDefault(_chokidar);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _resolveTarget = require('./lib/resolve-target');
var _resolveTarget2 = _interopRequireDefault(_resolveTarget);
var _sourcesBases = require('./lib/sources-bases');
var _sourcesBases2 = _interopRequireDefault(_sourcesBases);
var _isGlob = require('./lib/is-glob');
var _isGlob2 = _interopRequireDefault(_isGlob);
var _trimQuotes = require('./lib/trim-quotes');
var _trimQuotes2 = _interopRequireDefault(_trimQuotes);
var _fs3 = require('./lib/fs');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_bluebird2.default.config({ cancellation: true });
var defaults = {
watch: false,
delete: true,
depth: Infinity
};
/**
* Synchronise files, directories and/or glob patterns, optionally watching for changes.
*
* @param {string|Array.<string>} sources - A list of files, directories and/or glob patterns.
* @param {string} target - The destination directory.
* @param {Object} [options] - An optional configuration object.
* @param {bool} [options.watch=false] - Enable or disable watch mode.
* @param {bool} [options.delete=true] - Whether to delete the `target`'s content initially.
* @param {bool} [options.depth=Infinity] - Chokidars `depth` (If set, limits how many levels of subdirectories will be traversed).
* @param {string} [options.transform=false] - A module path resolved by node's `require`.
* @param {NotifyCallback} [notify] - An optional notification callback.
* @returns {CloseFunc} - Returns a close function which cancels active promises and watch mode.
*/
// eslint-disable-next-line consistent-return
var syncGlob = function syncGlob(sources, target) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var notify = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {};
if (!Array.isArray(sources)) {
// eslint-disable-next-line no-param-reassign
sources = [sources];
}
// eslint-disable-next-line no-param-reassign
sources = sources.map(_trimQuotes2.default);
if (typeof options === 'function') {
// eslint-disable-next-line no-param-reassign
notify = options;
// eslint-disable-next-line no-param-reassign
options = {};
}
// eslint-disable-next-line no-param-reassign
options = _extends({}, defaults, options);
var notifyError = function notifyError(err) {
notify('error', err);
};
var bases = (0, _sourcesBases2.default)(sources);
var resolveTargetFromBases = (0, _resolveTarget2.default)(bases);
var _options = options,
depth = _options.depth,
watch = _options.watch;
var _options2 = options,
transform = _options2.transform;
if (typeof depth !== 'number' || isNaN(depth)) {
notifyError('Expected valid number for option "depth"');
return false;
}
if (transform) {
var transformPath = transform;
try {
require.resolve(transformPath);
} catch (err) {
transformPath = _path2.default.join(process.cwd(), transformPath);
try {
require.resolve(transformPath);
} catch (err2) {
notifyError(err2);
}
}
// eslint-disable-next-line
transform = require(transformPath);
}
// Initial mirror
var mirrorInit = [(0, _bluebird.promisify)(_globAll2.default)(sources.map(function (source) {
return (0, _isGlob2.default)(source) === -1 && _fs2.default.statSync(source).isDirectory() ? source + '/**' : source;
})).then(function (files) {
return files.map(function (file) {
return _path2.default.normalize(file);
});
})];
if (options.delete) {
mirrorInit.push((0, _fs3.remove)(target).then(function () {
notify('remove', [target]);
}).catch(notifyError));
} else {
notify('no-delete', target);
}
var mirrorPromiseAll = _bluebird2.default.all(mirrorInit).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 1),
files = _ref2[0];
return _bluebird2.default.all(files.map(function (source) {
var resolvedTarget = resolveTargetFromBases(source, target);
return (0, _fs3.stat)(source).then(function (stats) {
var result = void 0;
if (stats.isFile()) {
result = (0, _fs3.copyFile)(source, resolvedTarget, transform);
} else if (stats.isDirectory()) {
result = (0, _fs3.copyDir)(source, resolvedTarget);
}
if (result) {
result = result.then(function () {
notify('copy', [source, resolvedTarget]);
});
}
return result;
}).catch(notifyError);
}));
}).then(function () {
notify('mirror', [sources, target]);
}).catch(notifyError).finally(function () {
mirrorPromiseAll = null;
});
var watcher = void 0;
var activePromises = [];
var close = function close() {
if (watcher) {
watcher.close();
watcher = null;
}
if (mirrorPromiseAll) {
mirrorPromiseAll.cancel();
mirrorPromiseAll = null;
}
if (activePromises) {
activePromises.forEach(function (promise) {
promise.cancel();
});
activePromises = null;
}
};
// Watcher to keep in sync from that
if (watch) {
watcher = _chokidar2.default.watch(sources, {
persistent: true,
depth: depth,
ignoreInitial: true,
awaitWriteFinish: true
});
watcher.on('ready', notify.bind(undefined, 'watch', sources)).on('all', function (event, source) {
var resolvedTarget = resolveTargetFromBases(source, target);
var promise = void 0;
switch (event) {
case 'add':
case 'change':
promise = (0, _fs3.copyFile)(source, resolvedTarget, transform);
break;
case 'addDir':
promise = (0, _fs3.copyDir)(source, resolvedTarget);
break;
case 'unlink':
case 'unlinkDir':
promise = (0, _fs3.remove)(resolvedTarget);
break;
default:
return;
}
activePromises.push(promise.then(function () {
var eventMap = {
add: 'copy',
addDir: 'copy',
change: 'copy',
unlink: 'remove',
unlinkDir: 'remove'
};
notify(eventMap[event] || event, [source, resolvedTarget]);
}).catch(notifyError).finally(function () {
if (activePromises) {
var index = activePromises.indexOf(promise);
if (index !== -1) {
activePromises.slice(index, 1);
}
}
promise = null;
}));
}).on('error', notifyError);
process.on('SIGINT', close);
process.on('SIGQUIT', close);
process.on('SIGTERM', close);
}
return close;
};
exports.default = syncGlob;
/**
* This callback notifies you about various steps, like:
* - **copy:** File or directory has been copied to `target`.
* - **remove:** File or directory has been removed from `target`.
* - **no-delete:** No initial deletion of `target`s contents.
* - **mirror:** Initial copy of all `sources` to `target` done.
* - **watch:** Watch mode has started.
* - **error:** Any error which may occurred during program execution.
*
* @callback NotifyCallback
* @param {string} type - The type of notification.
* @param {...any} args - Event specific variadic arguments.
*/
/**
* A cleanup function which cancels all active promises and closes watch mode if enabled.
*
* @typedef {function} CloseFunc
*/