@morjs/utils
Version:
mor utils
347 lines • 13.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execCommands = exports.resolveDependency = exports.getRelativePath = exports.isCommonJsModule = exports.makeImportClause = exports.expandExtsWithConditionalExt = exports.generateQrcodeForTerminal = exports.setNPMBinPATH = exports.isLightColor = exports.hexToRgb = exports.validKeysMessage = void 0;
const console_png_1 = __importDefault(require("console-png"));
const path_1 = require("path");
const qr_image_1 = __importDefault(require("qr-image"));
const slash_1 = __importDefault(require("slash"));
const takin_1 = require("takin");
const constants_1 = require("./constants");
/**
* 基于可选值生成描述信息
* @param keys - 可选值
* @returns 可选值为 值1, 值2
*/
function validKeysMessage(keys) {
const values = (Array.isArray(keys) ? keys : Object.keys(keys)).join(', ');
return `可选值为 ${values}`;
}
exports.validKeysMessage = validKeysMessage;
/**
* 将 16 进制的颜色值转换成 rgb 格式
* @param hex - 16 进制的颜色值
*/
function hexToRgb(hex) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, (_, r, g, b) => `${r + r}${g + g}${b + b}`);
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return match
? {
r: parseInt(match[1], 16),
g: parseInt(match[2], 16),
b: parseInt(match[3], 16)
}
: null;
}
exports.hexToRgb = hexToRgb;
/**
* 是否是浅色
* @param r - rgb 色值区域中的 red
* @param g - rgb 色值区域中的 green
* @param b - rgb 色值区域中的 blue
*/
function isLightColor(r, g, b) {
const y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return y >= 128;
}
exports.isLightColor = isLightColor;
/**
* 设置 NPM .bin 路径以复用 npm bin 文件
* @param projectPath 项目路径
* @param env 环境变量
* @returns 添加过 NPM .bin 文件的 env
*/
function setNPMBinPATH(projectPath, env) {
const PATH = Object.keys(env)
.filter((p) => /^path$/i.test(p) && env[p])
.map((p) => env[p].split(path_1.delimiter))
.reduce((set, p) => set.concat(p.filter((p) => !set.includes(p))), [])
.join(path_1.delimiter);
const pathArr = [];
let p = projectPath;
let pp;
do {
pathArr.push((0, path_1.resolve)(p, 'node_modules', '.bin'));
pp = p;
p = (0, path_1.dirname)(p);
} while (p !== pp);
pathArr.push(PATH);
const pathVal = pathArr.join(path_1.delimiter);
for (const key of Object.keys(env)) {
if (/^path$/i.test(key)) {
env[key] = pathVal;
}
}
return env;
}
exports.setNPMBinPATH = setNPMBinPATH;
/**
* 生成二维码字符串
*
* @param input - 用于生成二维码的字符串
* @return 生成可用于 console 的字符串
*/
function generateQrcodeForTerminal(input) {
return new Promise((resolve, reject) => {
(0, console_png_1.default)(qr_image_1.default.imageSync(input, { size: 1, margin: 2 }), function (err, string) {
if (err)
return reject(err);
resolve(string);
});
});
}
exports.generateQrcodeForTerminal = generateQrcodeForTerminal;
/**
* 将普通后缀扩展为 普通后缀和带条件后缀的集合
* 条件后缀优先级高于普通后缀
* @param exts - 后缀列表
* @param conditionalExts - 条件后缀
* @returns 普通后缀和带条件后缀的集合
*/
function expandExtsWithConditionalExt(exts, conditionalExts) {
if (!conditionalExts || !(conditionalExts === null || conditionalExts === void 0 ? void 0 : conditionalExts.length))
return [].concat(exts);
const expandedExts = [];
(0, takin_1.asArray)(conditionalExts).forEach((conditionalExt) => {
exts.forEach((ext) => {
expandedExts.push(conditionalExt + ext);
});
});
return expandedExts.concat(exts);
}
exports.expandExtsWithConditionalExt = expandExtsWithConditionalExt;
/**
* 基于 module 类型 生成引用代码
* 规则:
* 1. 如果没有 importName 则仅 import 或 require
* 2. 如果只有 importName 则当做 default 引用
* 3. 如果 importName 和 importAs 都存在 且 相等 则当做 named import 引用
* @param moduleKind - module 类型, 用于生成 import 或 require 引用代码
* @param importPath - 引用地址
* @param importName - 引用名称
* @param importAs - 引用别名
* @param fileContent - 文件内容,用于辅助判断 commonjs 的情况
* @returns 生成引用代码
*/
function makeImportClause(moduleKind, importPath, importName, importAs, fileContent) {
importPath = (0, slash_1.default)(importPath);
// 判断是否 commonjs
// 优先使用文件内容判断, 避免一个文件里面使用多种 module 类型
if (isCommonJsModule(fileContent, moduleKind)) {
const fragment = `require('${importPath}')`;
if (!importName) {
return `${fragment};\n`;
}
else if (importName && !importAs) {
return `var ${importName} = ${fragment};\n`;
}
else if (importName && importAs) {
return `var ${importAs} = ${fragment}.${importName};\n`;
}
}
else {
if (!importName) {
return `import '${importPath}';\n`;
}
else if (importName && !importAs) {
return `import ${importName} from '${importPath}';\n`;
}
else if (importName && importAs) {
if (importName === importAs) {
return `import { ${importName} } from '${importPath}';\n`;
}
else {
return `import { ${importName} as ${importAs} } from '${importPath}';\n`;
}
}
}
}
exports.makeImportClause = makeImportClause;
/**
* 判断文件是否为 commonjs 模块
* @param fileContent - 文件内容
* @param moduleKind - 文件类型
* @param recheckWhenMatched - 文件类型
* @returns `true` or `false`
*/
async function isCommonJsModule(fileContent, moduleKind, recheckWhenMatched = false, filePath) {
const isCommonJs = (fileContent &&
(fileContent.includes('require(') ||
fileContent.includes('module.exports') ||
fileContent.includes('exports.'))) ||
moduleKind === constants_1.CompileModuleKind.CommonJS;
if (recheckWhenMatched) {
// 超过 500k 暂不处理
if (fileContent && fileContent.length > 512000) {
takin_1.logger.debug(`文件 ${filePath} 超过 500k 暂不进行二次模块类型判断`);
return isCommonJs;
}
// 移除注释之后再判断一次,确保没有判断错误
try {
const content = (await takin_1.esbuild.transform(fileContent, {
loader: 'ts',
target: 'esnext',
legalComments: 'none'
})).code;
return await isCommonJsModule(content, moduleKind, false);
}
catch (error) {
takin_1.logger.debug(`模块类型二次判断失败: ${error.message}`, `文件路径: ${filePath}`, error);
return isCommonJs;
}
}
else {
return isCommonJs;
}
}
exports.isCommonJsModule = isCommonJsModule;
/**
* 返回从 from 到 to 的相对路径
* @param from - 参考路径
* @param to - 需要转换的路径
* @param forcePosix - 是否使用 POSIX 格式路径
* @returns 相对路径
*/
function getRelativePath(from, to, forcePosix = true) {
// 获取到文件的目录,不能直接以文件路径做对比,否则获取到到的结果会多一层级
const fromDirPath = (0, path_1.dirname)(from);
const relativePath = (0, path_1.relative)(fromDirPath, to);
const prefix = relativePath.startsWith('./') || relativePath.startsWith('../')
? relativePath.startsWith('.\\') || relativePath.startsWith('..\\')
? ''
: '.\\'
: './';
if (!forcePosix)
return prefix + relativePath;
return (0, slash_1.default)(prefix + relativePath);
}
exports.getRelativePath = getRelativePath;
/**
* 获取 utils 的依赖地址
* @param depName - 依赖名称
*/
function resolveDependency(depName) {
return require.resolve(depName);
}
exports.resolveDependency = resolveDependency;
/**
* 运行模块脚本并标记脚本运行状态
*/
async function execCommands({ commands = [], tips = '', env = {}, cwd, options = {}, timeout = constants_1.COMMAND_TIMEOUT, callbacks = {}, throwOnError = false, verbose = false }) {
var _a, _b;
const message = tips || '';
const scripts = commands || [];
const commonCommandEnv = env || {};
const commonCommandOptions = options || {};
await ((_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.beforeAll) === null || _a === void 0 ? void 0 : _a.call(callbacks, scripts));
function containsError(msg) {
return String(msg || '').includes('Error');
}
function markError(msg) {
if (!msg)
return '';
return String(msg).replace(/Error/g, takin_1.COLORS.error('Error'));
}
let execFailed = false;
let error;
try {
for await (const command of scripts) {
let cmd = (typeof command === 'string' ? command : command === null || command === void 0 ? void 0 : command.command) || '';
let comandInfo = `执行命令: ${cmd}`;
if (typeof (callbacks === null || callbacks === void 0 ? void 0 : callbacks.beforeEach) === 'function') {
const res = await callbacks.beforeEach(cmd, command);
cmd = res.command;
comandInfo = res.info || `执行命令: ${cmd}`;
}
// 自定义命令选项和环境变量
let commandOptions = {};
let commandEnv = {};
if (typeof command !== 'string') {
commandOptions = (command === null || command === void 0 ? void 0 : command.options) || {};
commandEnv = (command === null || command === void 0 ? void 0 : command.env) || {};
}
// 简单判断下是否为 shell 命令
// 有关 shell 选项带来的执行差异, 参见: https://github.com/sindresorhus/execa#shell
const shell = cmd.includes('||') ||
cmd.includes('&&') ||
cmd.includes('"') ||
cmd.includes("'");
takin_1.logger.info(comandInfo);
const stdioOptions = {};
// 开启详细日志
if (verbose) {
stdioOptions.stdout = 'inherit';
stdioOptions.stderr = 'inherit';
}
else {
stdioOptions.stdin = 'ignore';
}
const execOptions = {
cwd: cwd,
// 追加 node_modules/.bin 路径信息
env: setNPMBinPATH(cwd, {
// 默认自动关闭 husky 避免 git 相关的校验
HUSKY: '0',
...process.env,
// 设置 yarn 缓存文件夹
// 避免通过 yarn 安装 node_modules 时
// yarn 多实例可能带来的缓存冲突问题
// 但会增加一些构建时长
// ...{ YARN_CACHE_FOLDER: path.join(moduleInfo.root, '.yarn') },
...commonCommandEnv,
...commandEnv
}),
timeout,
shell,
...stdioOptions,
// 支持自定义命令选项
...commonCommandOptions,
...commandOptions
};
takin_1.logger.debug('命令详情:', execOptions);
const result = await takin_1.execa.command(cmd, execOptions);
// 执行脚本后置回调
if (typeof (callbacks === null || callbacks === void 0 ? void 0 : callbacks.afterEach) === 'function') {
await callbacks.afterEach(result, command);
}
// 打印可能出现的日志, 部分脚本可能会打印错误日志但是不正常退出
let possibleErrorMsg = '';
if (containsError(result.stdout)) {
possibleErrorMsg = markError(result.stdout);
}
if (containsError(result.stderr)) {
possibleErrorMsg = possibleErrorMsg
? [possibleErrorMsg, markError(result.stderr)].join('\n')
: markError(result.stderr);
}
if (possibleErrorMsg) {
takin_1.logger.warnOnce(`${comandInfo}, 日志包含异常信息, 但未正确退出, 请检查: \n${possibleErrorMsg}`);
execFailed = true;
}
}
await ((_b = callbacks === null || callbacks === void 0 ? void 0 : callbacks.afterAll) === null || _b === void 0 ? void 0 : _b.call(callbacks, scripts));
}
catch (err) {
error = new Error(`${message}执行失败, 原因: ${err.message}`);
if (err.name)
error.name = err.name;
error.stack = err.stack;
execFailed = true;
}
// 提示修复手段
if (execFailed) {
let errorTips = '';
if (typeof (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === 'function') {
errorTips = callbacks.onError(error) || errorTips;
}
if (throwOnError && error)
throw error;
if (errorTips)
takin_1.logger.warnOnce(errorTips);
}
}
exports.execCommands = execCommands;
//# sourceMappingURL=utils.js.map