const path = require('path'); const Git = require('./git'); const async = require('async'); const fs = require('fs-extra'); /** * Generate a list of unique directory paths given a list of file paths. * @param {Array.} files List of file paths. * @return {Array.} List of directory paths. */ const uniqueDirs = (exports.uniqueDirs = function(files) { const dirs = {}; files.forEach(filepath => { const parts = path.dirname(filepath).split(path.sep); let partial = parts[0] || '/'; dirs[partial] = true; for (let i = 1, ii = parts.length; i < ii; ++i) { partial = path.join(partial, parts[i]); dirs[partial] = true; } }); return Object.keys(dirs); }); /** * Sort function for paths. Sorter paths come first. Paths of equal length are * sorted alphanumerically in path segment order. * @param {string} a First path. * @param {string} b Second path. * @return {number} Comparison. */ const byShortPath = (exports.byShortPath = (a, b) => { const aParts = a.split(path.sep); const bParts = b.split(path.sep); const aLength = aParts.length; const bLength = bParts.length; let cmp = 0; if (aLength < bLength) { cmp = -1; } else if (aLength > bLength) { cmp = 1; } else { let aPart, bPart; for (let i = 0; i < aLength; ++i) { aPart = aParts[i]; bPart = bParts[i]; if (aPart < bPart) { cmp = -1; break; } else if (aPart > bPart) { cmp = 1; break; } } } return cmp; }); /** * Generate a list of directories to create given a list of file paths. * @param {Array.} files List of file paths. * @return {Array.} List of directory paths ordered by path length. */ const dirsToCreate = (exports.dirsToCreate = function(files) { return uniqueDirs(files).sort(byShortPath); }); /** * Copy a file. * @param {Object} obj Object with src and dest properties. * @param {function(Error)} callback Callback */ const copyFile = (exports.copyFile = function(obj, callback) { let called = false; function done(err) { if (!called) { called = true; callback(err); } } const read = fs.createReadStream(obj.src); read.on('error', err => { done(err); }); const write = fs.createWriteStream(obj.dest); write.on('error', err => { done(err); }); write.on('close', () => { done(); }); read.pipe(write); }); /** * Make directory, ignoring errors if directory already exists. * @param {string} path Directory path. * @param {function(Error)} callback Callback. */ function makeDir(path, callback) { fs.mkdir(path, err => { if (err) { // check if directory exists fs.stat(path, (err2, stat) => { if (err2 || !stat.isDirectory()) { callback(err); } else { callback(); } }); } else { callback(); } }); } /** * Copy a list of files. * @param {Array.} files Files to copy. * @param {string} base Base directory. * @param {string} dest Destination directory. * @return {Promise} A promise. */ exports.copy = function(files, base, dest) { return new Promise((resolve, reject) => { const pairs = []; const destFiles = []; files.forEach(file => { const src = path.resolve(base, file); const relative = path.relative(base, src); const target = path.join(dest, relative); pairs.push({ src: src, dest: target }); destFiles.push(target); }); async.eachSeries(dirsToCreate(destFiles), makeDir, err => { if (err) { return reject(err); } async.each(pairs, copyFile, err => { if (err) { return reject(err); } else { return resolve(); } }); }); }); }; exports.getUser = function(cwd) { return Promise.all([ new Git(cwd).exec('config', 'user.name'), new Git(cwd).exec('config', 'user.email') ]) .then(results => { return {name: results[0].output.trim(), email: results[1].output.trim()}; }) .catch(err => { // git config exits with 1 if name or email is not set return null; }); };