UNPKG

@serpent/common-cli

Version:

通用的 cli 相关的函数

1,117 lines (1,020 loc) 36.1 kB
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 };