auto-deploy/ftp/lib.js

154 lines
4.7 KiB
JavaScript

const fs = require("fs");
const path = require("path");
const util = require("util");
const Promise = require("bluebird");
const read = require("read");
const readP = util.promisify(read);
const minimatch = require("minimatch");
// P H A S E 0
function checkIncludes(config) {
config.excludes = config.excludes || [];
if (!config.include || !config.include.length) {
return Promise.reject({
code: "NoIncludes",
message: "You need to specify files to upload - e.g. ['*', '**/*']"
});
} else {
return Promise.resolve(config);
}
}
function getPassword(config) {
if (config.password) {
return Promise.resolve(config);
} else {
let options = {
prompt:
"Password for " +
config.user +
"@" +
config.host +
" (ENTER for none): ",
default: "",
silent: true
};
return readP(options).then(res => {
let config2 = Object.assign(config, { password: res });
return config2;
});
}
}
// Analysing local firstory
function canIncludePath(includes, excludes, filePath) {
let go = (acc, item) =>
acc || minimatch(filePath, item, { matchBase: true });
let canInclude = includes.reduce(go, false);
// Now check whether the file should in fact be specifically excluded
if (canInclude) {
// if any excludes match return false
if (excludes) {
let go2 = (acc, item) =>
acc && !minimatch(filePath, item, { matchBase: true });
canInclude = excludes.reduce(go2, true);
}
}
// console.log("canIncludePath", include, filePath, res);
return canInclude;
}
// A method for parsing the source location and storing the information into a suitably formated object
function parseLocal(includes, excludes, localRootDir, relDir) {
// reducer
let handleItem = function(acc, item) {
const currItem = path.join(fullDir, item);
const newRelDir = path.relative(localRootDir, currItem);
if (fs.lstatSync(currItem).isDirectory()) {
// currItem is a directory. Recurse and attach to accumulator
let tmp = parseLocal(includes, excludes, localRootDir, newRelDir);
for (let key in tmp) {
if (tmp[key].length == 0) {
delete tmp[key];
}
}
return Object.assign(acc, tmp);
} else {
// currItem is a file
// acc[relDir] is always created at previous iteration
if (canIncludePath(includes, excludes, newRelDir)) {
// console.log("including", currItem);
acc[relDir].push(item);
return acc;
}
}
return acc;
};
const fullDir = path.join(localRootDir, relDir);
// Check if `startDir` is a valid location
if (!fs.existsSync(fullDir)) {
throw new Error(fullDir + " is not an existing location");
}
// Iterate through the contents of the `fullDir` of the current iteration
const files = fs.readdirSync(fullDir);
// Add empty array, which may get overwritten by subsequent iterations
let acc = {};
acc[relDir] = [];
const res = files.reduce(handleItem, acc);
return res;
}
function countFiles(filemap) {
return Object.values(filemap).reduce((acc, item) => acc.concat(item))
.length;
}
function deleteDir(ftp, dir) {
return ftp.list(dir).then(lst => {
let dirNames = lst
.filter(f => f.type == "d" && f.name != ".." && f.name != ".")
.map(f => path.posix.join(dir, f.name));
let fnames = lst
.filter(f => f.type != "d")
.map(f => path.posix.join(dir, f.name));
// delete sub-directories and then all files
return Promise.mapSeries(dirNames, dirName => {
// deletes everything in sub-directory, and then itself
return deleteDir(ftp, dirName).then(() => ftp.rmdir(dirName));
}).then(() => Promise.mapSeries(fnames, fname => ftp.delete(fname)));
});
}
mkDirExists = (ftp, dir) => {
// Make the directory using recursive expand
return ftp.mkdir(dir, true).catch(err => {
if (err.message.startsWith("EEXIST")) {
return Promise.resolve();
} else {
console.log("[mkDirExists]", err.message);
// console.log(Object.getOwnPropertyNames(err));
return Promise.reject(err);
}
});
};
module.exports = {
checkIncludes: checkIncludes,
getPassword: getPassword,
parseLocal: parseLocal,
canIncludePath: canIncludePath,
countFiles: countFiles,
mkDirExists: mkDirExists,
deleteDir: deleteDir
};