zb-utils
Version:
ZombieBox utils
237 lines (210 loc) • 5.84 kB
JavaScript
const path = require('path');
const fs = require('fs');
const lodash = require('lodash');
const fsUtils = require('fs-util');
/**
* @param {string} filepath
* @return {boolean}
*/
const isDirectory = (filepath) => fs.existsSync(filepath) && (fs.lstatSync(filepath).isDirectory());
/**
* @param {string} filepath
* @return {boolean}
*/
const isFile = (filepath) => fs.existsSync(filepath) && (fs.lstatSync(filepath).isFile());
/**
* @param {string} filepath
* @return {boolean}
*/
const isSymbolicLink = (filepath) => fs.existsSync(filepath) && (fs.lstatSync(filepath).isSymbolicLink());
/**
* @param {string} src
* @param {Array<string>=} opt_locations
* @return {string}
* @private
*/
const findTemplate = (src, opt_locations = []) => {
const isAbsolutePath = src === path.join(path.sep, src);
if (isAbsolutePath) {
if (fs.existsSync(src)) {
return src;
}
} else {
for (let i = 0; i < opt_locations.length; i++) {
const templatePath = path.join(opt_locations[i], src);
if (fs.existsSync(templatePath)) {
return templatePath;
}
}
}
throw new Error('Can\'t find template: "' + src + '" in [' + opt_locations.join(', ') + ']');
};
/**
* @param {string} src
* @param {Object} data
* @param {{locations: Array<string>}=} opt_options
* @return {string}
*/
const renderTemplate = (src, data, opt_options = {}) => {
data._ = lodash;
return lodash.template(fs.readFileSync(findTemplate(src, opt_options.locations), 'utf-8'))(data);
};
/**
* @param {string} src
* @param {string} dst
* @param {Object} data
* @param {Object=} opt_options
*/
const renderTemplateDir = (src, dst, data, opt_options = {}) => {
const renderDir = (src, options, dest) => {
if (fs.existsSync(dest)) {
if (!isDirectory(dest)) {
throw new Error('dest exists and it is not a directory: ' + dest);
}
} else {
fs.mkdirSync(dest);
}
const files = fs.readdirSync(src);
files.forEach((file) => {
const srcPath = path.join(src, file);
const dstPath = path.join(dest, file);
if (isFile(srcPath)) {
const exp = /\.tpl$/;
if (exp.test(srcPath)) {
fs.writeFileSync(
dstPath.replace(exp, ''),
renderTemplate(srcPath, data, options),
'utf-8'
);
} else {
fsUtils.cp(srcPath, dstPath);
}
} else if (isSymbolicLink(srcPath)) {
const linkPath = fs.readlinkSync(srcPath);
fs.symlinkSync(linkPath, dstPath);
} else {
renderDir(srcPath, options, dstPath);
}
});
};
const template = findTemplate(src, opt_options.locations);
renderDir(path.join(template, 'root'), opt_options, dst);
// rename
const rename = {};
const renameConfig = require(path.join(template, 'rename.json'));// eslint-disable-line global-require
Object
.keys(renameConfig)
.forEach((p) => {
rename[lodash.template(p)(data)] = lodash.template(renameConfig[p])(data);
});
Object
.keys(rename)
.forEach((p) => fs.renameSync(
path.join(dst, p),
path.join(dst, rename[p])
));
};
/**
* Find ZombieBox application path up from location.
* @param {string} location
* @return {?string}
*/
const findUp = (location) => {
let cwd = path.join(path.resolve(location), 'dummy');
while ('/' !== (cwd = path.join(cwd, '..'))) {
const src = path.join(cwd, 'package.json');
if (fs.existsSync(src)) {
const cfg = require(src);// eslint-disable-line global-require
if (cfg.dependencies && cfg.dependencies.hasOwnProperty('zombiebox')) {
return cwd;
}
}
}
return null;
};
/**
* Recursive read directory
* @param {string} dirPath
* @return {Array<string>}
*/
const ls = (dirPath) => {
const files = fs.readdirSync(dirPath);
let found = [];
const len = files.length;
for (let i = 0; i < len; i++) {
const file = files[i];
const filePath = path.join(dirPath, file);
if (isDirectory(filePath)) {
found = found.concat(ls(filePath));
} else if (isFile(filePath) || isSymbolicLink(filePath)) {
found.push(filePath);
}
}
return found;
};
/**
* Merge config objects.
* @param {Object} obj
* @param {...Object} var_args
* @return {Object}
*/
const confMerge = (obj, ...var_args) => {
const hasOwnProperty = Object.prototype.hasOwnProperty;
var_args.forEach((src) => {
for (const prop in src) {
if (hasOwnProperty.call(src, prop)) {
if (!hasOwnProperty.call(obj, prop)) {
obj[prop] = src[prop];
continue;
}
const objValueIsArray = obj[prop] instanceof Array;
const srcValueIsArray = src[prop] instanceof Array;
const objValueIsObject = !objValueIsArray && obj[prop] instanceof Object;
const srcValueIsObject = !srcValueIsArray && src[prop] instanceof Object;
if (srcValueIsArray || objValueIsArray) {
if (!objValueIsArray || !srcValueIsArray) {
throw new Error('Trying to combine an array with a non-array (' + prop + ')');
} else {
// merge arrays
obj[prop] = obj[prop].concat(src[prop])
// unique values
.reverse() // reverse array for save order
.filter((e, i, array) => array.indexOf(e, i + 1) === -1)// unique
.reverse(); // reverse array back
}
} else if (objValueIsObject || srcValueIsObject) {
if (!objValueIsObject || !srcValueIsObject) {
throw new Error('Trying to combine an object with a non-object (' + prop + ')');
} else {
obj[prop] = confMerge(obj[prop], src[prop]);
}
} else {
obj[prop] = src[prop];
}
}
}
});
return obj;
};
/**
* See: https://github.com/arboleya/fs-util/pull/6/files
* @param {string} filepath
*/
const touch = (filepath) => {
fs.writeFileSync(filepath, '');
};
module.exports = {
renderTemplate,
renderTemplateDir,
findUp,
copy: fsUtils.cp,
copyR: fsUtils.cp_r,
touch,
rmRf: fsUtils.rm_rf,
mkdirP: fsUtils.mkdir_p,
find: fsUtils.find,
confMerge,
ls,
lodash,
_: lodash
};