@serpent/common-cli
Version:
通用的 cli 相关的函数
1,117 lines (1,020 loc) • 36.1 kB
JavaScript
import { c as __assign, e as __rest, _ as __awaiter, a as __generator, b as __spreadArray } from './tslib.es6-636c4f06.mjs';
import { a as isWin } from './supportColor-2e661db8.mjs';
import { g as getDefaultExportFromCjs } from './_commonjsHelpers-7d1333e8.mjs';
import { c as clogExports, i as isPlainObject$1 } from './format-e8a6577a.mjs';
import { t as table$1, a as table$2 } from './table-75b962e9.mjs';
import require$$1 from 'os';
import { logger, FS_LOG_LEVELS } from './logger.mjs';
import { f as fs } from './index-c12dbf5e.mjs';
import { g as getBoolEnv, l as loadScript } from './helper-1331d234.mjs';
import 'crypto';
function regexpTrim(str, regexp, callback) {
if (regexp.test(str)) {
callback(RegExp.$1, RegExp.$2, RegExp.$3);
return str.replace(regexp, '');
}
return str;
}
function spiltTrim2array(str, split) {
if (split === void 0) { split = '|'; }
return str
.split(split)
.map(function (s) { return s.trim(); })
.filter(function (s) { return !!s; });
}
function opt(type, opts) {
var options;
if (!opts) {
options = {};
}
else if (typeof opts === 'string') {
options = parseStringOpt2ObjectObj(opts);
}
else {
options = opts;
}
return { type: type, options: options };
}
function env(type, opts) {
return opt(type, opts);
}
/**
* 解析这种结构: `[groupName] !<aliasA | aliasB> 选项描述 {{ defaultValue }}`
*/
function parseStringOpt2ObjectObj(strOpt) {
var options = {};
var groupReg = /^\s*\[([^\]]+)\]/;
var aliasReg = /^\s*(!?)<([^\]]+)>/;
var valueReg = /\{\{(.*?)\}\}$/;
strOpt = regexpTrim(strOpt, groupReg, function (group) {
group = group.trim();
if (group)
options.group = group;
});
strOpt = regexpTrim(strOpt, aliasReg, function (prefix, alias) {
options.hideInHelp = prefix === '!';
options.alias = spiltTrim2array(alias);
});
strOpt = regexpTrim(strOpt, valueReg, function (value) {
options.defaultValue = JSON.parse(value.trim());
});
options.desc = strOpt.trim();
return options;
}
/**
* @module libs/lang/assign
* @createdAt 2016-06-30
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
/**
*
* 原生的 Object.assign 的一个 polyfill
*
* Node v4 以前基本上都不支持 Object.assign 这个方法,此方法是 sindresorhus 写
* 的 object-assign 的一个简化版,源版本还对原生的 Object.assign 做了 bug detect
* ( Detect buggy property enumeration order in older V8 versions ),我这个
* 版本相对简单点
*
* @param {Object} target 要赋值到的对象
* @param {...Object} source 源对象,可以有多个
* @throws {TypeError} 如果第一个参数是 null 或者 undefined
* @return {Object}
*
* @example
* assign({}, {a: '1', b: 2}, null, true, {a: 1})
*
* @see [object-assign@4.1.0]{@link https://github.com/sindresorhus/object-assign/tree/v4.1.0}
* @author Zhongle Qiu
* @since 2.0.0
*/
var assign$1 = function(target, source) {
if (target == null) throw new TypeError('Object.assign cannot be called with null or undefined')
var i, j, k, from, symbols;
var to = Object(target);
for (i = 1; i < arguments.length; i++) {
from = Object(arguments[i]);
for (k in from) {
if (hasOwnProperty.call(from, k)) to[k] = from[k];
}
/* istanbul ignore else */
if (Object.getOwnPropertySymbols) {
symbols = Object.getOwnPropertySymbols(from);
for (j = 0; j < symbols.length; j++) {
if (propIsEnumerable.call(from, symbols[j])) to[symbols[j]] = from[symbols[j]];
}
}
}
return to
};
/**
* @module libs/sys/cli
* @createdAt 2016-07-15
*
* @copyright Copyright (c) 2016 Zhonglei Qiu
* @license Licensed under the MIT license.
*/
var format = clogExports.format;
var table = table$1;
var assign = assign$1;
var isPlainObject = isPlainObject$1;
var reOptionType = /^(bool|str|num|arr|count|bstr|bnum)[a-z]*$/;
var reOption = /^\s*(!?)<(bool|str|num|arr|count|bstr|bnum)[a-z]*>\s*([\s\S]*?)(?:\{\{(.*)\}\})?$/;
var undef = void 0;
var DEFAULT_GROUP_NAME = '__default__:)';
var typeConfig = {
bool: {
label: 'boolean',
needArgs: 0,
boolean: true // 表示也可以不接参数,或者后面接 true/false 参数
},
str: {
label: 'string',
needArgs: 1
},
bstr: {
label: 'boolean/string',
needArgs: 1,
boolean: true
},
num: {
label: 'number',
needArgs: 1
},
bnum: {
label: 'boolean/number',
needArgs: 1,
boolean: true
},
arr: {
label: 'array',
needArgs: 1
},
count: {
label: 'count',
needArgs: 0
}
};
/**
* @class
* @param {Object} conf cli 程序的配置项
* @param {String|Boolean} conf.help String: 帮助描述 Boolean: 是否启用系统默认的帮助选项
* @param {String|Boolean|(() => String} conf.version String: 版本号(默认为 0.0.0) Boolean: 是否启用系统默认的版本选项
* @param {String|(() => String} conf.usage 简短的描述
* @param {String|Array<String>|(() => String} conf.desc 命令的一些介绍
* @param {String|Array<String>|(() => String} conf.example 使用命令的一些样例
* @param {String|(() => String} conf.epilog 放在 help 结尾的一段话,一般用来声明版权,或者显示查看更多的链接
* @param {(() => void} conf.bootstrap 初始化操作,在命令后面添加 ---bootstrap 才会触发此函数执行
*
* @param {Boolean} conf.strict 遇到无法解析的 option 是否要报错
* @param {Boolean} conf.showHelpOnError 解析参数失败时不显示帮助信息
* @param {Boolean} conf.stopParseOnFirstNoOption 在遇到第一个非 option 参数时就停止解析(很适用于运行子程序)
*
* @example
* cli({
* usage: 'cli [options] <foo>'
* version: '1.0.0'
* })
* .options({
* 'e | escape': '<bool> escape input string {{ defaultValue }}'
* })
* .parse(function (res) {
* if (res.escape) {
* // ...
* }
* })
*
* @author Zhonglei Qiu
* @since 2.0.0
* @see optimist, minimist, yargs, nomnom, nopt, commander
*/
function Cli(conf) {
if (!(this instanceof Cli)) return new Cli(conf)
/**
* 配置信息
* @type {Object}
*/
this.conf = conf || {};
conf = this.conf;
/**
* 在遇到第一个非 option 参数时就停止解析(很适用于子程序)
* @type {Boolean}
*/
this.stopParseOnFirstNoOption = !!conf.stopParseOnFirstNoOption;
/**
* 出错时显示帮助信息
* @type {Boolean}
*/
this.showHelpOnError = !!conf.showHelpOnError;
/**
* 严格模式,遇到无法解析的 option 是就报错
* @type {Boolean}
*/
this.strict = !!conf.strict;
/**
* 解析完后剩下的给程序的参数
* @type {Array}
*/
this._ = [];
/**
* 所有 commands 的配置
* @type {Object}
*/
this.mapCommands = {};
this.mapOptions = {};
this.mapEnv = {};
/**
* 所有的命令分组名称
* @type {Array}
*/
this.commandGroups = [];
this.commandGroupsMap = {};
this.optionsGroups = [];
this.optionsGroupsMap = {};
this.envGroups = [];
this.envGroupsMap = {};
if (conf.usage) this.usage = conf.usage;
if (conf.desc) this.desc = [].concat(conf.desc);
if (conf.example) this.example = [].concat(conf.example);
if (conf.epilog) this.epilog = conf.epilog;
if (conf.bootstrap) this.bootstrap = conf.bootstrap;
}
/**
* 设置 Cli 程序的子命令
*
* @param {Object} opts 子命令配置
*
* opts 是 key-value 对象,支持下面几种类型的配置
* - str1:str2... => function
* - str1:str2... => {group: '', desc: '', cmd: function}
*
* @return {Cli}
*
* @example
*
* cli.commands({
* 'build:*': function (res) {
* // 匹配所有 build: 开头的子命令
* },
* 'r | run': function (res) {
* // handle
* },
* second: {
* group: 'category',
* desc: 'this is sub-command with description',
* cmd: function (res) {
* // handle
* }
* }
* })
*
*/
Cli.prototype.commands = function(opts) {
init.call(this, true, opts);
return this
};
/**
* 设置 Cli 程序支持的选项
*
* @param {String} [group] 选项可以分组,此值可以指定分组的名称,在选项非常多的情况下,
* 使用 --help 时可以看到效果
*
* @param {Object} opts 此分组下的所有选项
*
* opts 是 key-value 对象,支持下面几种类型的配置
* - str1:str2... => <type> desc
* - str1:str2... => {type: '', desc: '', defaultValue: ''}
*
* 说明:
* 1. str1 是一个 option,":" 后面的 str2 等等表示 str1 的别名
* 2. desc 表示 option 的描述
* 3. type 表示 option 的类型,支持的类型有
* * boolean bool
* * string str 不能为空
* * number num 不能为空
* * array arr 可以为空
* * count
* * boolean/number bnum
* * boolean/number bstr
*
* @return {Cli}
*
* @example
*
* cli.options({
* 'e | escape': '<boolean> enable escape',
* 'l | linefeed': {
* type: 'boolean',
* desc: 'append a system linefeed at end'
* }
* })
*
*/
Cli.prototype.options = function(group, opts, isEnv) {
if (typeof group !== 'string') {
opts = group;
group = null;
}
if (!opts) return
init.call(this, false, opts, group, isEnv);
return this
};
Cli.prototype.env = function(group, opts) {
return this.options(group, opts, true)
};
function init(isCommand, opts, customGroup, isEnv) {
Object.keys(opts).forEach(function(origKey) {
var key, type, value, cmd, desc, alias, defaultValue, target, hideInHelp;
var group = customGroup || DEFAULT_GROUP_NAME;
alias = origKey.trim().split(/\s*\|\s*/);
key = alias[0];
value = opts[origKey];
var groups = isCommand ? this.commandGroups : isEnv ? this.envGroups : this.optionsGroups;
var groupsMap = isCommand ? this.commandGroupsMap : isEnv ? this.envGroupsMap : this.optionsGroupsMap;
var map = isCommand ? this.mapCommands : isEnv ? this.mapEnv : this.mapOptions;
if (isCommand) {
if (typeof value === 'function') {
cmd = value;
desc = '';
} else if (isPlainObject(value)) {
if (value.group) group = value.group;
hideInHelp = value.hideInHelp;
cmd = value.cmd;
if (typeof cmd !== 'function') {
throw new Error('Command "' + origKey + '" should have a handle function.')
}
desc = value.desc || '';
if (value.conf || value.options || value.groups) {
/* istanbul ignore next */
var conf = value.conf || {};
/* istanbul ignore else */
if (!conf.desc) conf.desc = desc;
var subCli = new Cli(conf);
subCli.options(value.options);
/* istanbul ignore else */
if (value.groups) {
Object.keys(value.groups).forEach(function(k) {
subCli.options(k, value.groups[k]);
});
}
cmd = function(res, cli) {
subCli.parse(res._, function(subres) {
value.cmd.call(subCli, subres, subCli);
});
};
}
} else {
throw new Error('Command "' + origKey + '" is invalid')
}
target = { key: key, alias: alias, cmd: cmd, type: 'command', group: group, desc: desc, hideInHelp: hideInHelp };
} else {
if (typeof value === 'string') {
if (reOption.test(value)) {
hideInHelp = RegExp.$1 === '!';
type = RegExp.$2;
desc = RegExp.$3;
if (RegExp.$4) {
defaultValue = RegExp.$4;
try {
defaultValue = JSON.parse(defaultValue.trim());
} catch (e) {
desc += '{{' + defaultValue + '}}';
defaultValue = undefined;
}
}
} else {
throw new Error('Option "' + origKey + '" config "' + value + '" is invalid.')
}
} else if (isPlainObject(value)) {
type = value.type;
desc = value.desc || '';
hideInHelp = value.hideInHelp;
defaultValue = value.defaultValue;
if (value.group) group = value.group;
if (reOptionType.test(type)) {
type = RegExp.$1;
} else {
throw new Error('Option "' + origKey + '" type is invalid.')
}
} else {
throw new Error('Option "' + origKey + '" is invalid.')
}
target = { key: key, alias: alias, defaultValue: defaultValue, type: type, group: group, desc: desc, hideInHelp: hideInHelp };
}
if (groups.indexOf(group) < 0) {
if (group === DEFAULT_GROUP_NAME) {
groups.unshift(group);
} else {
groups.push(group);
}
groupsMap[group] = [];
}
groupsMap[group].push(target);
alias.forEach(function(k) {
if (k in map) throw new Error((isCommand ? 'Command' : isEnv ? 'Env' : 'Option') + ' key "' + k + '" is duplicated.')
map[k] = target;
});
}, this);
}
// 确保将 -h, -v 放在 default group 的最后面
function parseInit() {
var conf = this.conf;
var opts = {};
if (conf.help !== false) {
opts['help | h'] = '<bool> ' + (typeof conf.help === 'string' ? conf.help : 'Show help');
}
if (conf.version !== false) {
this.version = conf.version;
opts['version | v'] = '<bool> ' + 'Show version';
}
init.call(this, false, opts);
}
function parse(args) {
var _ = this._;
var i, arg, rawArg, stopped;
var ck = consumeKey.bind(this);
var cv = consumeVal.bind(this);
for (i = 0; i < args.length; i++) {
rawArg = arg = args[i];
if (stopped) {
_.push(arg);
} else if (_.length === 0 && this.getCommanderByKey(arg)) { // 运行子命令剩下的参数给子命令去解析
stopped = true;
_.push(arg);
} else if (arg === '--') {
stopped = true;
} else {
var equalValue = null;
var equalIndex = arg.indexOf('=');
if (equalIndex >= 0) {
equalValue = arg.substr(equalIndex + 1);
arg = arg.substr(0, equalIndex);
}
if (arg.slice(0, 2) === '--') { // 长参数
if (arg.length === 3) { // --a 类的参数不是 option,需要当作 value
cv(rawArg);
} else {
ck(arg.slice(2), undefined, equalValue);
if (equalValue != null) cv(equalValue, true);
}
} else if (arg[0] === '-' && arg.length === 2) { // 单个短参数
ck(arg[1], undefined, equalValue);
if (equalValue != null) cv(equalValue, true);
} else if (arg[0] === '-' && arg.length >= 3) { // 多个短参数,或单个数字短参数
// 这里的参数被打散处理了,所以 consume 前要先 check 整体参数是否 valid
if (/^-\w(\d+)$/.test(arg)) {
if (checkOpt.call(this, arg[1], rawArg)) {
// 已经赋值,不应该再赋值
if (equalValue != null) throw new Error('Error: can not parse arg "' + rawArg + '"')
ck(arg[1]);
cv(arg.slice(2));
}
} else {
arg = arg.slice(1).split('');
if (checkOpt.call(this, arg, rawArg)) {
arg.forEach(function(k, i, ref) {
if (ref.length - 1 === i) {
ck(k); // 最后一个 option 可以接任意个参数
if (equalValue != null) cv(equalValue, true);
} else {
ck(k, 'noNeedArgs'); // 后面不能接参数
}
});
}
}
} else {
// 处理非 option
cv(rawArg);
}
}
if (_.length && this.stopParseOnFirstNoOption) stopped = true;
}
// 对最后一个 consumeTarget 判断其参数是否符合
var ct = this.consumeTarget;
if (ct && !ct.boolean && ct.needArgs > ct.currentArgs && ct.needArgs !== Infinity) {
throw new Error('Error: ' + ct.type + ' option ' + this.formatOptionKey(ct.consumedKey) + ' need argument.')
}
}
function parseEnv(mapEnv) {
Object.keys(mapEnv).forEach(function(key) {
// { key: key, alias: alias, defaultValue: defaultValue, type: type, group: group, desc: desc }
var target = mapEnv[key];
if (process.env.hasOwnProperty(key)) {
var value = process.env[target.key];
switch (target.type) {
case 'str':
target.value = value;
break
case 'bool':
target.value = !(!value || value === 'false' || value === 'no' || value === '0');
break
case 'num':
target.value = parseFloat(value);
break
case 'bnum':
if (value === 'false') target.value = false;
else if (value === 'true') target.value = true;
else target.value = parseFloat(value);
break
case 'bstr':
if (value === 'false') target.value = false;
else if (value === 'true') target.value = true;
else target.value = value;
break
default:
throw new Error('Not supported env type "' + target.type + '"')
}
}
});
}
/**
* 解析传入的参数
*
* - 解析到 "--" 就停止解析
* - array 类型的参数支持重复设置,其它类型的参数会被覆盖,array 形式的 option 如果不加参数会返回空数组
* - 支持短标签加数字的快捷方式,如 "-n100",前提是 "-n" 需要是 number 类型
* - 其它:https://github.com/yargs/yargs#parsing-tricks
*
* @param {Array<String>} [args] 参数
* @param {Function} handle parse 完成后,如果没有被内部 handle,则会调用此 handle
* @return {Cli}
*
* @example
*
* cli.parse(process.argv.slice(2), function (res) {
* if (res.foo) {
* // do something
* }
* })
*
*/
Cli.prototype.parse = function(args, handle) {
if (!Array.isArray(args)) {
handle = args;
args = process.argv.slice(2);
}
var tripe = '';
if (args[0] && args[0].startsWith('---') && !args[0].startsWith('----')) {
tripe = args.shift().slice(3);
}
parseInit.call(this);
try {
parse.call(this, args);
} catch (e) {
// completion 模式不会运行命令,所以参数解析失败关系不大
if (tripe !== 'completion') {
/* istanbul ignore else */
if (/^Error: /.test(e.message)) {
this.error(e.message);
return this
} else {
throw e
}
}
}
parseEnv(this.mapEnv);
var res = this.res = {
userDefinedOptions: {}, // 记录所有的使用了用户定义的字段
userDefinedEnv: {},
env: {}
};
res.userDefined = res.userDefinedOptions; // 兼容老版本
var run = function(opts, defined, assignTarget) {
Object.keys(opts).forEach(function(k) {
if ('value' in opts[k]) {
defined[k] = true;
}
var value = 'value' in opts[k] ? opts[k].value : opts[k].defaultValue;
if (value !== undefined) {
assignTarget[k] = value;
}
});
};
run(this.mapOptions, res.userDefinedOptions, res);
run(this.mapEnv, res.userDefinedEnv, res.env);
res.rawArgs = args;
var _ = this._;
if (res.help && this.conf.help !== false) {
this.help();
} else if (res.version && this.conf.version !== false) {
console.log(this.version ? strOrFunToString(this.version) : '0.0.0');
} else {
var commander = this.getCommanderByKey(_[0]);
var tripeFunc = tripe && this.conf[tripe];
// 如果有子命令,需要在子命令中获取补全
// 没有子命令,或者当前要补全的参数刚好只有一个时
if (tripe === 'completion' && (!commander || _.length === 1)) {
var cmdKeys = Object.keys(this.mapCommands);
var optKeys = Object.keys(this.mapOptions).map(function(k) { return k.length > 1 ? '--' + k : '-' + k });
var allKeys = cmdKeys.concat(optKeys);
var compKeys;
var compArg = args[args.length - 1]; // 最后一个要补全的参数
var prevArg = args[args.length - 2];
if (!(compArg && compArg.startsWith('-')) && prevArg && prevArg.startsWith('-')) {
var prevKey = prevArg.replace(/^-{1,2}/, '');
var target = this.mapOptions[prevKey];
if (target && target.needArgs > 0) {
compKeys = []; // 后面是补全参数的值,不需要自动补全
}
}
if (!compKeys) {
compKeys = !compArg ? cmdKeys : allKeys.filter(function(k) { return k.startsWith(compArg) });
}
if (typeof tripeFunc === 'function') {
compKeys = tripeFunc(compKeys, res);
}
if (Array.isArray(compKeys)) {
console.log(compKeys.join('\n'));
}
return
}
if (commander) {
res.$command = _[0];
res._ = _.slice(1);
if (tripe) res._.unshift('---' + tripe);
commander.cmd.call(this, res, this);
} else if (typeof tripeFunc === 'function') {
tripeFunc.call(res);
} else if (typeof handle === 'function') {
res._ = _;
handle.call(this, res, this);
}
}
return this
};
Cli.prototype.getCommanderByKey = function(key) {
var commanderMap = this.mapCommands;
var commander = key && commanderMap[key];
if (key && !commander) {
var newKey = Object.keys(commanderMap).find(function(k) {
if (k.includes('*')) {
return new RegExp('^' + k.replace(/\*/g, '.*') + '$').test(key)
}
return false
});
commander = newKey && commanderMap[newKey];
}
return commander
};
/**
* 格式化 option 选项
* @param {String} key 选项名
* @return {String} 带 "-" 或 "--" 的选项名
*/
Cli.prototype.formatOptionKey = function(key) {
return key.length === 1 ? '-' + key : '--' + key
};
/**
* 得到某个 option 的配置
* @param {String} key 选项名
* @return {Object} 解析过后的配置
*/
Cli.prototype.getOptionConfig = function(key) {
return this.mapOptions[key]
};
/**
* 判断某个 option 是否存在
* @param {String} key 选项名
* @return {Boolean}
*/
Cli.prototype.isOptionKeyValid = function(key) {
return key in this.mapOptions
};
/**
* 输出错误信息
*
* @param {...*} arg 参数格式和 {@link module:libs/sys/clog.format} 的格式是一致的
* @return {Cli}
*/
Cli.prototype.error = function() {
console.log(format('\n%c%s\n', 'red', format.apply(null, arguments)));
if (this.showHelpOnError) this.help();
return this
};
/**
* 根据 Cli 的配置,输出帮助信息
*/
Cli.prototype.help = function(returnStr) {
var buffer = [];
var EOL = require$$1.EOL;
var puts = function() { buffer.push(format.apply(null, arguments)); };
var putsHeader = function(header) { puts('%c%s:\n', 'white.bold', header); };
puts();
if (this.usage) {
puts('%cUsage: %c%s\n', 'white.bold', 'green', strOrFunToString(this.usage));
}
if (this.desc) {
this.desc.forEach(function(row) { puts(' %c%s', 'gray', strOrFunToString(row)); });
puts();
}
if (this.example) {
putsHeader('Example');
this.example.forEach(function(row) { puts(' %c%s', 'red.bg.white', strOrFunToString(row)); });
puts();
}
var putsGroups = function(title, groups, groupsMap) {
if (groups.length) {
var rows = [];
groups.forEach(function(group) {
if (group !== DEFAULT_GROUP_NAME && groupsMap[group].some(function(e) { return !e.hideInHelp })) {
rows.push(['', '', '']);
rows.push([format(' %c%s:', 'green.bold', group), '', '']);
rows.push(['', '', '']);
}
groupsMap[group].forEach(function(entry) {
if (entry.hideInHelp) return
var row = [];
if (title === 'Commands') {
row.push(format(' %c%s ', 'green', entry.alias.join(', ')));
row.push(format('%c%s', 'default', entry.desc));
row.push('');
} else {
var defaultValue = entry.defaultValue !== undef
? format(' %c[ default: %s ]', 'gray', JSON.stringify(entry.defaultValue))
: '';
var alias = title === 'Env' ? entry.alias : entry.alias.map(function(a) { return (a.length === 1 ? '-' : '--') + a });
row.push(format(' %c%s', 'green', alias.join(', ')));
row.push(format(' %c%s ', 'cyan', '<' + typeConfig[entry.type].label + '>'));
row.push(format('%c%s', 'default', entry.desc + defaultValue));
}
rows.push(row);
});
}, this);
/* istanbul ignore else */
if (rows.length) {
putsHeader(title);
puts(table(rows));
puts();
}
}
};
putsGroups('Commands', this.commandGroups, this.commandGroupsMap);
putsGroups('Options', this.optionsGroups, this.optionsGroupsMap);
putsGroups('Env', this.envGroups, this.envGroupsMap);
if (this.epilog) puts(' %s\n', strOrFunToString(this.epilog));
buffer = buffer.join(EOL);
return returnStr ? buffer : /* istanbul ignore next */ console.log(buffer)
};
function checkOpt(opts, rawArg) {
var optsArr = [].concat(opts);
if (optsArr.every(this.isOptionKeyValid.bind(this))) {
return true
} else {
invalidOpt.call(this, rawArg);
}
}
function invalidOpt(rawArg) {
if (this.strict) {
throw new Error('Error: invalid option ' + rawArg + '.')
} else {
consumeVal.call(this, rawArg);
}
}
function consumeKey(key, noNeedArgs, equalValue) {
var conf = this.getOptionConfig(key);
if (!conf) return invalidOpt.call(this, this.formatOptionKey(key))
assign(conf, typeConfig[conf.type]);
conf.currentArgs = 0;
if (noNeedArgs && conf.needArgs > 0) {
throw new Error('Error: ' + conf.type + ' option '
+ this.formatOptionKey(key) + ' need argument.')
}
switch (conf.type) {
case 'bool':
case 'bstr':
case 'bnum':
conf.value = true;
break
case 'count':
if (equalValue != null) throw new Error('Error: "' + key + '" option do not need argument.')
if ('value' in conf) conf.value++;
else conf.value = 1;
break
case 'arr':
if (!conf.value) conf.value = [];
break
}
this.consumeTarget = conf;
conf.consumedKey = key;
}
function consumeVal(val, isEqualValue) {
var lowerVal = val.toLowerCase();
var conf = this.consumeTarget;
var needArgs = conf ? conf.needArgs : 0;
// boolean 相关的参数可以接受 boolean 值
if (!needArgs && conf && conf.boolean && (isEqualValue || (lowerVal === 'true' || lowerVal === 'false'))) {
needArgs = 1;
}
/* istanbul ignore else */
if (!conf || needArgs <= conf.currentArgs) {
this._.push(val);
} else if (needArgs > conf.currentArgs) {
var consumedKey = this.formatOptionKey(conf.consumedKey);
conf.currentArgs++;
switch (conf.type) {
case 'arr':
conf.value.push(val);
break
case 'str':
case 'bstr':
if (conf.type === 'bstr' && lowerVal === 'true') conf.value = true;
else if (conf.type === 'bstr' && lowerVal === 'false') conf.value = false;
else conf.value = val;
break
case 'bool':
// 单独的 boolean 值在 --key=val 的模式下支持 val 配置成 "yes"
conf.value = lowerVal === 'true' || lowerVal === 'yes';
break
case 'num':
case 'bnum':
if (conf.type === 'bnum' && lowerVal === 'true') conf.value = true;
else if (conf.type === 'bnum' && lowerVal === 'false') conf.value = false;
else conf.value = parseNumber(val);
if (isNaN(conf.value)) {
throw new Error('Error: invalid number value "'
+ val + '" for option "'
+ consumedKey + '".')
}
break
}
}
}
function parseNumber(val, ctx) {
if (/\./.test(val)) val = parseFloat(val);
else val = parseInt(val, 10);
return val
}
function strOrFunToString(val) {
return typeof val === 'function' ? val() : val
}
var cli = Cli;
var cli$1 = /*@__PURE__*/getDefaultExportFromCjs(cli);
function createContext() {
return __assign({ logger: logger, getBoolEnv: getBoolEnv, loadScript: loadScript }, fs);
}
var log = logger.fs('serpent:cmder');
function cmd(params, run) {
return function (args, parentConf, parentRes) {
var fsLog = params.fsLog, _a = params.options, options = _a === void 0 ? {} : _a, _b = params.env, env = _b === void 0 ? {} : _b, _c = params.commands, commands = _c === void 0 ? {} : _c, conf = __rest(params, ["fsLog", "options", "env", "commands"]);
var optionsParam = __assign(__assign({}, options), getCustomOpts(fsLog));
var envParam = env;
var cmder = cli$1(__assign(__assign({}, parentConf), conf));
initCmder('options', cmder, optionsParam);
initCmder('env', cmder, envParam);
cmder.commands(Object.entries(commands).reduce(function (subs, _a) {
var strCmd = _a[0], modFn = _a[1];
return __assign(__assign({}, subs), parseStrCmd2ObjCmd(strCmd, modFn));
}, {}));
var ctx = createContext();
cmder.parse(args || process.argv.slice(2), function (res, instance) {
return __awaiter(this, void 0, void 0, function () {
var _, userDefinedOptions, userDefinedEnv, env, rawArgs, options;
return __generator(this, function (_a) {
res.$command, _ = res._, userDefinedOptions = res.userDefinedOptions, userDefinedEnv = res.userDefinedEnv, res.userDefined, env = res.env, rawArgs = res.rawArgs, options = __rest(res, ["$command", "_", "userDefinedOptions", "userDefinedEnv", "userDefined", "env", "rawArgs"]);
silent(function () {
var baseInfo = {
$command: parentRes === null || parentRes === void 0 ? void 0 : parentRes.$command,
args: _,
userDefinedOptions: userDefinedOptions,
userDefinedEnv: userDefinedEnv,
options: options,
rawArgs: rawArgs,
env: env,
};
if (fsLog && options.log) {
var info = logger.fs.open(options.log, { logFile: options.logFile });
logger.warn("\n log level: ".concat(info === null || info === void 0 ? void 0 : info.level));
logger.warn(" log file: ".concat(info === null || info === void 0 ? void 0 : info.logFile, "\n"));
log.verbose('env', envParam);
log.verbose('options', optionsParam);
log.debug('start command', baseInfo);
}
return run(__assign(__assign(__assign(__assign({}, baseInfo), { help: function (returnString) { return instance.help(returnString); }, isWin: isWin, table: table$2 }), ctx), { getRootDir: function (refPath) {
var res = ctx.tryGetProjectRootDir(refPath);
if (!res) {
var ref = refPath ? " in ".concat(refPath) : '';
throw new Error("Can't found project root directory".concat(ref, ", make sure you are under a directory which contains package.json file"));
}
return res;
} }));
}, function (err) {
if (fsLog && options.log) {
if (err)
log.error('run command error', err);
else
log.info('run command successfully');
logger.fs.close();
}
});
return [2 /*return*/];
});
});
});
};
}
/**
* @param strCmd 类似这种结构:`'[groupName] !<aliasA | aliasB> 命令描述': () => require(file) `
* @param modFn 文件路径或文件名称
*/
function parseStrCmd2ObjCmd(strCmd, modFn) {
var _a;
// 提取字符串中的 group
var group;
var groupReg = /^\s*\[([^\]]+)\]/;
if (groupReg.test(strCmd)) {
group = RegExp.$1;
strCmd = strCmd.replace(groupReg, '');
}
// 提取命令名称及别名
var aliasReg = /^\s*(!?)<([^>]+)>/;
var error = function () {
throw new Error("command config string \"".concat(strCmd, "\" did not includes any command name"));
};
if (!aliasReg.test(strCmd))
error();
var hideInHelp = RegExp.$1 === '!';
var keys = spiltTrim2array(RegExp.$2);
if (!keys.length)
error();
var desc = strCmd.replace(aliasReg, '').trim();
var commands = (_a = {},
_a[keys.join(' | ')] = {
desc: desc,
group: group,
hideInHelp: hideInHelp,
cmd: function (res) {
var _this = this;
var run = function (fn) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
return [2 /*return*/, fn(res._, { version: false, desc: desc }, res)];
}); }); }; // 子命令默认不需要 version
if (typeof modFn === 'function') {
silent(function () { return run(modFn); });
}
else {
modFn.then(function (fn) { return silent(function () { return run(fn); }); });
}
},
},
_a);
return commands;
}
function initCmder(fnKey, cmder, params) {
Object.entries(params).forEach(function (_a) {
var _b;
var key = _a[0], opts = _a[1];
var type = opts.type, _c = opts.options, _d = _c.alias, alias = _d === void 0 ? [] : _d, defaultValue = _c.defaultValue, desc = _c.desc, group = _c.group, hideInHelp = _c.hideInHelp;
var obj = (_b = {},
_b[__spreadArray([key], (typeof alias === 'string' ? [alias] : alias), true).join(' | ')] = {
type: type,
desc: desc,
defaultValue: defaultValue,
hideInHelp: hideInHelp,
},
_b);
if (group)
cmder[fnKey](group, obj);
else
cmder[fnKey](obj);
});
}
function silent(exec, done) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, exec()];
case 1:
_a.sent();
if (done)
done();
return [2 /*return*/];
}
});
});
}
function getCustomOpts(fsLog) {
if (!fsLog)
return {};
var obj = {};
if (typeof fsLog === 'string' || Array.isArray(fsLog))
obj = { log: { alias: fsLog } };
else
obj = fsLog;
return {
log: {
type: 'bstr',
options: __assign({ desc: "enable log or set log level, supports: ".concat(FS_LOG_LEVELS.join(', ')) }, obj.log),
},
logFile: {
type: 'string',
options: __assign({ desc: 'custom log file path, default is <temDir>/log.<year-month-date>.log' }, obj.logFile),
},
logNamespace: {
type: 'string',
options: __assign({ desc: 'custom log namespace, if specified, will only write related log message' }, obj.logNamespace),
},
};
}
export { cmd as c, env as e, opt as o };