builder-isv
Version:
ISV 模块本地预览与云构建器
444 lines (387 loc) • 10 kB
JavaScript
/**
* @author 龙喜<xiaolong.lxl@alibaba-inc.com>
* @description utils
*/
;
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const config = require('../config');
const debug = require('debug');
const request = require('request');
const chalk = require('chalk');
const upxClient = require('isv-module-info').upxClient(config.upxEnv);
const cp = require('child_process');
const os = require('os');
/**
* 创建一个可快速存储的 JSON 数据
* @method __save 保存 JSON 到本地
*/
class JSONStore {
/**
* 构造方法
* @param {String | Object} target JSON 文件路径或者对象,如果是对象,其键值只能为 JSONStore 实例
* @param {Boolean} createIfNotExists 不存在就新建
*/
static create(target, createIfNotExists) {
if (typeof target === 'string') {
let json;
try {
json = JSON.parse(fs.readFileSync(target, 'utf-8'));
} catch (e) {
if (createIfNotExists) {
json = {};
// 标记不存在
defineConst(json, '__exists', false);
} else {
throw e;
}
}
/**
* 保存到本地
*/
defineConst(json, '__save', function() {
// https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir-sync.md
require('fs-extra').ensureDirSync(path.dirname(target));
fs.writeFileSync(target, JSON.stringify(json, null, 2), 'utf-8');
});
return json;
} else if (typeof target === 'object') {
/**
* 递归调用 __save 方法
*/
defineConst(target, '__save', function() {
for (let k in target) {
if (target.hasOwnProperty(k) && target[k] instanceof JSONStore) {
target[k].__save();
}
}
});
return target;
} else {
throw {
message: '不支持的参数'
}
}
}
}
/**
* 定义一个 const 属性
* @param obj
* @param key
* @param value
*/
function defineConst(obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: false,
configurable: false,
writable: false,
value: value
});
}
/**
* 输出标准日志格式到终端
*
* Colors
* black
* red
* green
* yellow
* blue (on Windows the bright version is used as normal blue is illegible)
* magenta
* cyan
* white
* gray
*
* Background colors
* bgBlack
* bgRed
* bgGreen
* bgYellow
* bgBlue
* bgMagenta
* bgCyan
* bgWhite
*
* @param prefix
* @param color
* @param {Array | String} message
*/
function output(prefix, color, message) {
let args = [
`${chalk[color || 'green'](prefix || 'info')}`,
`${chalk.magenta('[kit:light:builder]')}`
];
if (message instanceof Array) {
args = args.concat(message);
} else {
args.append(message);
}
console.log.apply(console, args);
}
function info() {
output('info', 'green', Array.prototype.slice.call(arguments, 0));
}
function warn() {
output('warn', 'yellow', Array.prototype.slice.call(arguments, 0));
}
function error() {
output('ERR!', 'red', Array.prototype.slice.call(arguments, 0));
}
function verbose() {
if (config.defOptions.verbose) {
output('verb', 'blue', Array.prototype.slice.call(arguments, 0));
}
}
/**
* 从 UPX 获取版本信息
*/
function* getVersionFromUpx(dependencies) {
if (_.isEmpty(dependencies)) {
// 直接返回空白依赖,不需要查询 upx
return {};
}
let result = yield upxClient.request('npmService.getNpmVersionBySemVersion', [dependencies]);
verbose('从 UPX 拿到的依赖表:', result);
if (!result) {
throw new Error('从 UPX 检索依赖的数据格式有误,解析 JSON 出错');
}
// 组装成 version 格式
var verJSON = {};
_.forOwn(result, function(value, key) {
verJSON[key] = value && value.version;
});
return verJSON;
}
/**
* 深度克隆(只处理了常用内置对象)
* @param origin
* @returns {*}
*/
function deepClone(origin) {
var target;
if (typeof origin === 'string' || typeof origin === 'number' || typeof origin === 'boolean' || typeof origin === 'undefined' || origin === null || origin instanceof Function || origin instanceof Date) {
return origin;
} else if (origin instanceof Array) {
target = [];
for (var i = 0; i < origin.length; ++i) {
target.push(deepClone(origin[i]));
}
} else {
target = {};
for (var key in origin) {
if (origin.hasOwnProperty(key)) {
target[key] = deepClone(origin[key]);
}
}
}
return target;
}
/**
* 节流阀
* @param fn
* @param interval
* @param context
* @param accurate
* @returns {proxy}
*/
function throttle(fn, interval, context, accurate) {
let lastTime;
let timeout;
let lastResult;
if (accurate) {
return function proxy() {
clearTimeout(timeout);
const currentTime = new Date().getTime();
const args = arguments;
timeout = setTimeout(function delay() {
fn.apply(context || null, Array.prototype.slice.call(args));
lastTime = currentTime;
}, interval - (currentTime - (lastTime || currentTime - interval)));
};
} else {
return function proxy() {
const currentTime = new Date().getTime();
if (!lastTime || currentTime - lastTime >= interval) {
let result = fn.apply(context, Array.prototype.slice.call(arguments));
lastTime = currentTime;
return lastResult = result;
} else {
return lastResult;
}
};
}
}
function exec(cmd, option) {
return new Promise((resolve, reject)=> {
cp.exec(cmd, option || {}, (err, stdout, stderr)=> {
if (err) {
if (!option._ignoreError) {
verbose(err);
}
reject(err, stdout, stderr);
} else {
resolve(stdout || stderr);
}
});
});
}
/**
* spawn a child process(support win32)
* @param cmd
* @param args
* @param option
* @param onData when new data flushed
* @returns {Promise}
*/
function spawn(cmd, args, option, onData) {
return new Promise((resolve, reject)=> {
let spawnProcess;
const buffer = [];
if (os.platform() === 'win32') {
args.unshift(cmd);
spawnProcess = cp.spawn('cmd', ['/s', '/c'].concat(args), option);
} else {
spawnProcess = cp.spawn(cmd, args, option);
}
spawnProcess.stdout.on('data', processData);
spawnProcess.stderr.on('data', processData);
spawnProcess.on('close', (code) => {
if (code !== 0) {
reject(new Error(Buffer.concat(buffer).toString('utf-8')));
} else {
resolve(Buffer.concat(buffer).toString('utf-8'));
}
});
spawnProcess.on('error', (error) => {
reject(error);
});
function processData(data) {
buffer.push(data);
data = data.toString('utf-8');
process.stdout.write(data);
onData && onData(data, spawnProcess, resolve, reject);
}
});
}
/**
* shell 同步执行
* @param modulePath
* @param cmd
* @returns {*}
*/
function execs(modulePath, cmd) {
return exec(cmd, {cwd: modulePath});
}
/**
* 异步执行输出
* @param modulePath
* @param cmd
* @param args
* @param option
* @param onData when new data flushed
* @param force ensure any confirm, stdin must be pipe
* @returns {*}
*/
function spawns(modulePath, cmd, args, option, onData, force) {
return spawn(
cmd,
args,
Object.assign({
cwd: modulePath,
stdio: [process.stdin, 'pipe', 'pipe']
}, option),
force ? (data, childProcess, resolve, reject) => {
if (data.indexOf('(Y/n)') !== -1) {
if (childProcess) {
this.def.log.info('builder 已自动确认 confirm 操作');
// ANSI escape (enter)
childProcess.stdin.write('\x0D');
// 貌似这个也管用... 双保险,shell 只会确认一次换行
childProcess.stdin.write('\n');
} else {
verbose('stdin 非 pipe 模式,无法自动安装');
}
}
onData && onData.apply(this, arguments);
} : onData
);
}
/**
* 使用 tnpm 安装
* tnpm ii 就是 i http://gitlab.alibaba-inc.com/node/tnpm/blob/4.x/origin_npm.js#L52
* tnpm update 就是 remove & i https://github.com/cnpm/npminstall/blob/master/bin/update.js#L31
* @param cwd
* @returns {*}
*/
function tnpmInstall(cwd) {
info('正在安装依赖...');
return spawns(cwd, os.platform() === 'win32' ? 'tnpm.cmd' : 'tnpm', ['update', '--production']);
}
function readFileOrEmpty(path) {
try {
verbose('读取 ' + path + '文件内容');
return fs.readFileSync(path, 'utf8');
} catch (e) {
verbose('读取 ' + path + '文件内容失败');
return '';
}
}
function writeFileOrNone(path, data) {
fs.writeFile(path, data, 'utf8', function(err) {
if (err) {
verbose('写入数据到' + path + '失败');
verbose('想要写入的数据:' + data);
return;
}
verbose('已将数据写入到:' + path);
verbose('文件内容:' + data);
});
}
function writeFileOrNoneSync(path, data) {
try {
fs.writeFileSync(path, data, 'utf8');
verbose('文件' + path + '写入成功');
} catch (e) {
verbose('文件写入失败');
}
}
function parseOrFalse(str) {
let json = {};
try {
if (str.length) {
json = JSON.parse(str);
verbose('将字符串解析成 JSON 成功');
verbose('获得的 JSON 的内容:' + json);
return json;
} else {
return false;
}
} catch (err) {
verbose('将字符串解析成 JSON 失败:', err);
verbose('原字符串内容:' + str);
return false;
}
}
module.exports = {
JSONStore,
defineConst,
log: {
info: info,
warn: warn,
error: error,
verbose: verbose
},
getVersionFromUpx,
exec,
execs,
spawn,
spawns,
throttle,
deepClone,
tnpmInstall,
readFileOrEmpty,
writeFileOrNone,
writeFileOrNoneSync,
parseOrFalse
};