UNPKG

builder-isv

Version:

ISV 模块本地预览与云构建器

444 lines (387 loc) 10 kB
/** * @author 龙喜<xiaolong.lxl@alibaba-inc.com> * @description utils */ 'use strict'; 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 };