ftp-deploy
Version:
Ftp a folder from your local disk to an ftp destination
179 lines (157 loc) • 5.65 kB
JavaScript
const fs = require("fs");
const path = require("upath");
const util = require("util");
const read = require("read");
const { minimatch } = require("minimatch");
// P H A S E 0
function checkIncludes(config) {
config.exclude = config.exclude || [];
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 read(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 mapSeries(dirNames, (dirName) => {
// deletes everything in sub-directory, and then itself
return deleteDir(ftp, dirName).then(() => ftp.rmdir(dirName));
}).then(() => 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);
}
});
};
/**
* Applies the mapper function to each item in the array in series.
* @param {Array} array - The array to map over.
* @param {Function} mapper - The mapping function.
* @returns {Promise<Array>} A promise that resolves with the new array.
*/
function mapSeries(array, mapper) {
// Initialize a resolved Promise
let result = Promise.resolve();
// Empty array to store the results
const output = [];
array.forEach((item, index) => {
// Chain a new Promise to the result
result = result.then(() => {
// Apply the mapper function to the current item
return mapper(item, index, array).then((res) => {
output.push(res);
});
});
});
// Return a Promise that resolves with the output array
return result.then(() => output);
}
module.exports = {
checkIncludes: checkIncludes,
getPassword: getPassword,
parseLocal: parseLocal,
canIncludePath: canIncludePath,
countFiles: countFiles,
mkDirExists: mkDirExists,
deleteDir: deleteDir,
mapSeries: mapSeries,
};