mirror of
https://github.com/chenasraf/massarg.git
synced 2026-05-18 01:39:05 +00:00
chore: remove old files
chore: add gh-pages deploy script
This commit is contained in:
@@ -1,120 +0,0 @@
|
||||
import { CommandDef, ExampleDef, HelpDef, MainDef, Named, OptionDef } from "./types"
|
||||
import { ArrayOr, asArray } from "./utils"
|
||||
|
||||
// prettier-ignore
|
||||
export const colorList = [
|
||||
"reset", "bold", "dim", "italic", "underline", "inverse", "hidden", "strikethrough", "black", "red", "green",
|
||||
"yellow", "blue", "magenta", "cyan", "white", "gray", "bgBlack", "bgRed", "bgGreen", "bgYellow", "bgBlue",
|
||||
"bgMagenta", "bgCyan", "bgWhite",
|
||||
]
|
||||
|
||||
class AssertionError extends Error {}
|
||||
|
||||
function assert(condition: any, message?: string): void {
|
||||
if (!condition) {
|
||||
throw new AssertionError(message)
|
||||
}
|
||||
}
|
||||
|
||||
function nullOr(condition: any, condition2: any): boolean {
|
||||
return [null, undefined].includes(condition) || condition2
|
||||
}
|
||||
|
||||
function assertRequired(obj: any, prefix: string, name: string): void {
|
||||
assert(![undefined, null].includes(obj), `${prefix}: ${name} must be provided`)
|
||||
}
|
||||
|
||||
function assertType(obj: any, prefix: string, name: string, options: { type: string; required?: boolean }): void {
|
||||
if (options.required) {
|
||||
assertRequired(obj, prefix, name)
|
||||
} else {
|
||||
try {
|
||||
assertRequired(obj, prefix, name)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
assert(typeof obj === options.type, `${prefix}: ${name} must be ${options.type}`)
|
||||
}
|
||||
|
||||
function assertNumber(
|
||||
obj: number | null | undefined,
|
||||
prefix: string,
|
||||
name: string,
|
||||
options: { min?: number; max?: number; required?: boolean }
|
||||
): void {
|
||||
assertType(obj, prefix, name, { required: options.required, type: "number" })
|
||||
|
||||
if (!options.required && [null, undefined].includes(obj as any)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof options.max === "number") {
|
||||
assert(obj! <= options.max, `${prefix}: ${name} must be ≤ ${options.max}`)
|
||||
}
|
||||
if (typeof options.min === "number") {
|
||||
assert(obj! >= options.min, `${prefix}: ${name} must be ≥ ${options.min}`)
|
||||
}
|
||||
}
|
||||
|
||||
function assertColor(color: ArrayOr<string> | undefined, prefix: string, name: string) {
|
||||
assert(
|
||||
nullOr(
|
||||
color,
|
||||
asArray(color).every((c) => colorList.includes(c!))
|
||||
),
|
||||
`${prefix}: ${name} must be string or array of strings. Accepted values: ` + colorList.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
function assertAliases(def: Named, allDefs: Named[], prefix: string) {
|
||||
assert(
|
||||
!def.aliases || def.aliases.every((a) => !allDefs.find((opt) => [opt.name, ...(opt.aliases ?? [])].includes(a))),
|
||||
`${prefix}: Aliases must be unique`
|
||||
)
|
||||
}
|
||||
|
||||
export function assertHelp(help: HelpDef) {
|
||||
assertType(help.binName, "Help", "Binary Name", { type: "string" })
|
||||
assertColor(help.normalColors, "Help", "Normal colors")
|
||||
assertColor(help.bodyColors, "Help", "Body colors")
|
||||
assertColor(help.titleColors, "Help", "Title colors")
|
||||
assertColor(help.subtitleColors, "Help", "Subtitle colors")
|
||||
assertColor(help.highlightColors, "Help", "Highlight colors")
|
||||
assertType(help.footer, "Help", "Footer", { type: "string" })
|
||||
assertType(help.header, "Help", "Header", { type: "string" })
|
||||
assertType(help.optionNameSeparator, "Help", "Option Name Separator", { type: "string" })
|
||||
assertType(help.commandNameSeparator, "Help", "Command Name Separator", { type: "string" })
|
||||
assertNumber(help.printWidth, "Help", "Print Width", { min: 0 })
|
||||
assertType(help.exampleInputPrefix, "Help", "Example Input Prefix", { type: "string" })
|
||||
assertType(help.exampleOutputPrefix, "Help", "Example Output Prefix", { type: "string" })
|
||||
assertType(help.usageExample, "Help", "Usage Example", { type: "string" })
|
||||
assertType(help.useGlobalColumns, "Help", "Use Global Columns", { type: "boolean" })
|
||||
assertType(help.includeDefaults, "Help", "Include Defaults", { type: "boolean" })
|
||||
assertType(help.useColors, "Help", "Use Colors", { type: "boolean" })
|
||||
}
|
||||
|
||||
export function assertCommand(command: CommandDef<any>, allCommands: CommandDef<any>[]): void {
|
||||
assertType(command.name, "Command", "Name", { required: true, type: "string" })
|
||||
assertAliases(command, allCommands, "Command")
|
||||
assertType(command.run, "Command", "Run", { required: true, type: "function" })
|
||||
}
|
||||
|
||||
export function assertOption(option: OptionDef<any, any>, allOptions: OptionDef<any, any>[]): void {
|
||||
assert(option.name, "Option: Name must be provided")
|
||||
assert(typeof option.name === "string", "Option: Name must be string")
|
||||
assertAliases(option, allOptions, "Option")
|
||||
assertType(option.boolean, "Option", "Default Value", { type: "boolean" })
|
||||
assertType(option.parse, "Option", "Parse", { type: "function" })
|
||||
}
|
||||
|
||||
export function assertExample(example: ExampleDef) {
|
||||
assertType(example.input, "Example", "Input", { required: true, type: "string" })
|
||||
assertType(example.output, "Example", "Output", { type: "string" })
|
||||
assertType(example.description, "Example", "Description", { type: "string" })
|
||||
}
|
||||
|
||||
export function assertMain(run: MainDef<any>) {
|
||||
assertType(run, "Main", "Main", { required: true, type: "function" })
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export class RequiredError extends Error {
|
||||
fieldName!: string
|
||||
cmdName!: string
|
||||
|
||||
constructor(fieldName: string, cmdName: string) {
|
||||
super(
|
||||
`Option: \`${fieldName}\` is required${
|
||||
cmdName !== "all" ? ` for command: \`${cmdName}\`` : ""
|
||||
}, but was not defined. Try using: \`--${fieldName} {value}\``
|
||||
)
|
||||
this.fieldName = fieldName
|
||||
this.cmdName = cmdName
|
||||
}
|
||||
|
||||
public static isRequiredError(e: any): e is RequiredError {
|
||||
return e.fieldName && e.cmdName
|
||||
}
|
||||
}
|
||||
555
_old/index.ts
555
_old/index.ts
@@ -1,555 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import chalk from "chalk"
|
||||
import merge from "lodash/merge"
|
||||
import camelCase from "lodash/camelCase"
|
||||
import path from "path"
|
||||
import { OptionsBase, CommandDef, HelpDef, MainDef, OptionDef, ExampleDef } from "./types"
|
||||
import { ArrayOr, asArray, colorCount, COLOR_CODE_LEN, wrap } from "./utils"
|
||||
import { RequiredError } from "./errors"
|
||||
import { assertCommand, assertExample, assertHelp, assertMain, assertOption } from "./assertions"
|
||||
|
||||
export class Massarg<Options> {
|
||||
private _main?: MainDef<Options>
|
||||
private _options: OptionDef<Options, any>[] = []
|
||||
private _commands: CommandDef<Options>[] = []
|
||||
private _runCommand?: CommandDef<Options>
|
||||
private _examples: ExampleDef[] = []
|
||||
private _maxNameLen = 0
|
||||
/**
|
||||
* These are the parsed options passed via args. They will only be available after using `parse()` or `printHelp()`,
|
||||
* or when returned by `parseArgs()`. */
|
||||
public data: Options & OptionsBase = { help: false, extras: [] as string[] } as Options & OptionsBase
|
||||
|
||||
private _help: Required<HelpDef> = {
|
||||
binName: undefined as any,
|
||||
normalColors: "dim",
|
||||
highlightColors: "yellow",
|
||||
titleColors: ["bold", "white"],
|
||||
subtitleColors: ["bold", "dim"],
|
||||
bodyColors: "white",
|
||||
printWidth: 80,
|
||||
header: "",
|
||||
footer: "",
|
||||
commandNameSeparator: " | ",
|
||||
optionNameSeparator: "|",
|
||||
useGlobalColumns: true,
|
||||
usageExample: "[command] [options]",
|
||||
useColors: true,
|
||||
includeDefaults: true,
|
||||
exampleInputPrefix: "$",
|
||||
exampleOutputPrefix: "➜",
|
||||
}
|
||||
private _requiredOptions: Record<"all" | string, Record<string, boolean>> = {}
|
||||
|
||||
constructor() {
|
||||
this.option({
|
||||
name: "help",
|
||||
aliases: ["h"],
|
||||
description: "Display help information",
|
||||
parse: Boolean,
|
||||
})
|
||||
}
|
||||
|
||||
/** Define the main command to run when no commands are passed. */
|
||||
public main(run: MainDef<Options>): Massarg<Options> {
|
||||
assertMain(run)
|
||||
this._main = run
|
||||
return this
|
||||
}
|
||||
|
||||
/** Add option to be parsed */
|
||||
public option<Value>(option: OptionDef<Options, Value>): Massarg<Options> {
|
||||
let defaultValue = option.defaultValue as any
|
||||
|
||||
// detect boolean values
|
||||
option.boolean ??= (option.parse as any) === Boolean || [true, false].includes(defaultValue)
|
||||
// detect array values
|
||||
option.array ??= Array.isArray(defaultValue)
|
||||
// default parser
|
||||
option.parse ??= (option.boolean ? this._isTruthy : (a: any) => a) as any
|
||||
|
||||
assertOption(option, this._options)
|
||||
|
||||
if (option.array && defaultValue === undefined) {
|
||||
defaultValue = []
|
||||
}
|
||||
|
||||
this._options.push(option)
|
||||
this._prepareRequired(option)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Add example line to be added to the help text. */
|
||||
public example(example: ExampleDef): Massarg<Options> {
|
||||
assertExample(example)
|
||||
|
||||
this._examples.push(example as ExampleDef)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Add command to be run */
|
||||
public command(command: CommandDef<Options>): Massarg<Options> {
|
||||
assertCommand(command, this._commands)
|
||||
|
||||
this._commands.push(command)
|
||||
for (const opt of this._commandOptions(command)) {
|
||||
this._prepareRequired(opt)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/** Set options for behavior of the help text print. */
|
||||
public help(help: HelpDef): Massarg<Options> {
|
||||
assertHelp(help)
|
||||
|
||||
this._help = merge(this._help, help)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the help text without being required to pass option.
|
||||
*
|
||||
* @param args If args weren't already parsed, you can add them here
|
||||
*/
|
||||
public printHelp(args?: string[]): void {
|
||||
console.log(this.getHelpString(args).join("\n"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help text as an array of lines. Useful for manipulating the response or querying before displaying
|
||||
* to the user.
|
||||
*/
|
||||
public getHelpString(args?: string[]): string[] {
|
||||
const lines: string[] = []
|
||||
|
||||
if (args?.length) {
|
||||
this.parseArgs(args)
|
||||
}
|
||||
|
||||
const { bodyColors, highlightColors, normalColors, titleColors, binName, usageExample } = this._help
|
||||
|
||||
lines.push(
|
||||
[
|
||||
this.color(titleColors, "Usage:"),
|
||||
this.color(highlightColors, binName ?? path.basename(process.argv[1])),
|
||||
this.color(normalColors, usageExample),
|
||||
].join(" ")
|
||||
)
|
||||
|
||||
lines.push("")
|
||||
|
||||
if (this._help.header) {
|
||||
lines.push(this.color(bodyColors, this._help.header))
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
if (this._commands.length) {
|
||||
lines.push(this.color(titleColors, "Commands:"))
|
||||
lines.push("")
|
||||
lines.push(...this._printCommands())
|
||||
}
|
||||
|
||||
lines.push(...this._printOptions())
|
||||
|
||||
if (this._examples.length) {
|
||||
lines.push(this.color(titleColors, "Examples:"))
|
||||
lines.push("")
|
||||
lines.push(...this._printExamples())
|
||||
}
|
||||
|
||||
if (this._help.footer) {
|
||||
lines.push(this.color(bodyColors, this._help.footer))
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the arguments without running the commands related to them. Useful for testing or querying the data from the
|
||||
* args manually, if it is for some reason not enough to parse it normally through defining commands.
|
||||
* @param args Arguments to parse. Defaults to `process.argv`
|
||||
* @returns Parsed options
|
||||
*/
|
||||
public parseArgs(args = process.argv): Options & OptionsBase {
|
||||
for (const option of this._options) {
|
||||
if (option.defaultValue !== undefined) {
|
||||
this._addOptionToData(option, option.defaultValue)
|
||||
} else if (option.array) {
|
||||
this._pushToArrayData(option)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
const option = this._options.find((o) => `--${o.name}` === arg || o.aliases?.map((a) => `-${a}`).includes(arg))
|
||||
|
||||
if (option) {
|
||||
let tempValue: any
|
||||
const hasNextToken = args.length > i + 1
|
||||
const nextTokenIsValue = hasNextToken && !args[i + 1].startsWith("-")
|
||||
|
||||
if (option.boolean && (!hasNextToken || !nextTokenIsValue)) {
|
||||
// parse boolean args w/o value
|
||||
tempValue = true
|
||||
} else if (!hasNextToken || !nextTokenIsValue) {
|
||||
// non-boolean args with no value
|
||||
throw new TypeError(`Missing value for: ${option.name}`)
|
||||
} else {
|
||||
// any args (incl. boolean) with value
|
||||
tempValue = args[i + 1]
|
||||
args.shift()
|
||||
}
|
||||
const value = option.parse!(tempValue, this.data)
|
||||
this._addOptionToData(option, value)
|
||||
|
||||
// continue
|
||||
}
|
||||
|
||||
const command = this._commands.find((o) => o.name === arg || o.aliases?.includes(arg))
|
||||
|
||||
let justFoundCommand = false
|
||||
|
||||
if (command) {
|
||||
if (!this._runCommand) {
|
||||
this._runCommand = command
|
||||
justFoundCommand = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!option && (!command || (command && !justFoundCommand))) {
|
||||
const defOpts = this._options.filter((o) => o.isDefault)
|
||||
if (defOpts.length) {
|
||||
for (const option of defOpts) {
|
||||
this._addOptionToData(option, option.parse!(arg, this.data))
|
||||
}
|
||||
} else {
|
||||
this.data.extras.push(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.data
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given args, running any relevant commands in the process.
|
||||
*
|
||||
* @param args args to parse. Defaults to `process.argv`
|
||||
*/
|
||||
public parse(args?: string[]): void {
|
||||
this.parseArgs(args)
|
||||
|
||||
if (this.data.help) {
|
||||
this.printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (this._runCommand) {
|
||||
this._ensureRequired(this._runCommand)
|
||||
this._runCommand.run(this.data)
|
||||
} else if (this._main) {
|
||||
this._ensureRequired()
|
||||
this._main(this.data)
|
||||
} else {
|
||||
this._ensureRequired()
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (RequiredError.isRequiredError(e)) {
|
||||
console.error(chalk.red`${e.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
private _prepareRequired(options: OptionDef<Options, any>) {
|
||||
if (options.required) {
|
||||
if (options.commands?.length) {
|
||||
for (const command of this._optionCommands(options)) {
|
||||
this._requiredOptions[command.name] ??= {}
|
||||
this._requiredOptions[command.name][options.name] = true
|
||||
}
|
||||
} else {
|
||||
this._requiredOptions["all"] ??= {}
|
||||
this._requiredOptions["all"][options.name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _printExamples(): string[] {
|
||||
const lines: string[] = []
|
||||
const { normalColors, highlightColors, bodyColors, titleColors } = this._help
|
||||
for (const example of this._examples) {
|
||||
if (example.description) {
|
||||
lines.push(
|
||||
...wrap(this.color(titleColors, example.description), {
|
||||
colorCount: this.colorCount(titleColors),
|
||||
indent: 2,
|
||||
printWidth: this._help.printWidth,
|
||||
})
|
||||
)
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
lines.push(
|
||||
...wrap(
|
||||
[this.color(normalColors, this._help.exampleInputPrefix), this.color(highlightColors, example.input)].join(
|
||||
" "
|
||||
),
|
||||
{
|
||||
colorCount: this.colorCount(highlightColors),
|
||||
firstLineIndent: 2,
|
||||
indent: 3 + this._help.exampleInputPrefix.length,
|
||||
// indent: this.colorCount(normalColors) + 4,
|
||||
printWidth: this._help.printWidth,
|
||||
}
|
||||
)
|
||||
)
|
||||
if (example.output) {
|
||||
lines.push(
|
||||
...wrap(
|
||||
[this.color(normalColors, this._help.exampleOutputPrefix), this.color(bodyColors, example.output)].join(
|
||||
" "
|
||||
),
|
||||
{
|
||||
colorCount: this.colorCount(bodyColors),
|
||||
firstLineIndent: 2,
|
||||
indent: 3 + this._help.exampleOutputPrefix.length,
|
||||
// indent: this.colorCount(normalColors) + 4,
|
||||
printWidth: this._help.printWidth,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
lines.push("")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
private _isTruthy(v: any): boolean {
|
||||
v = String(v).toLowerCase()
|
||||
return ["1", "true", "yes", "y", "on"].includes(v) || !["0", "false", "no", "n", "off"].includes(v)
|
||||
}
|
||||
|
||||
private _ensureRequired(cmd?: CommandDef<Options>) {
|
||||
const cmdName = cmd?.name ?? "all"
|
||||
|
||||
for (const optName in this._requiredOptions[cmdName]) {
|
||||
if (this._requiredOptions[cmdName][optName]) {
|
||||
throw new RequiredError(optName, cmdName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _addOptionToData(option: OptionDef<Options, any>, value: any) {
|
||||
const _d: Record<string, any> = this.data
|
||||
|
||||
const set = (value: any) => {
|
||||
_d[option.name] = value
|
||||
_d[camelCase(option.name)] = value
|
||||
option.aliases?.forEach((a) => (_d[a] = value))
|
||||
}
|
||||
const push = (value: any) => {
|
||||
this._pushToArrayData(option, value)
|
||||
}
|
||||
if (!option.array) {
|
||||
// single value
|
||||
set(value)
|
||||
} else {
|
||||
// multiple values
|
||||
if (Array.isArray(value) && value.length) {
|
||||
for (const el of value) {
|
||||
push(el)
|
||||
}
|
||||
} else if (!Array.isArray(value)) {
|
||||
push(value)
|
||||
}
|
||||
}
|
||||
if (value !== option.defaultValue && value !== undefined) {
|
||||
for (const key in this._requiredOptions) {
|
||||
this._requiredOptions[key][option.name] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _pushToArrayData(option: OptionDef<Options, any>, value?: any) {
|
||||
const _d: Record<string, any> = this.data
|
||||
|
||||
const ccSame = camelCase(option.name) === option.name
|
||||
_d[option.name] ??= []
|
||||
_d[camelCase(option.name)] ??= []
|
||||
option.aliases?.forEach((a) => (_d[a] ??= []))
|
||||
|
||||
if (value !== undefined) {
|
||||
_d[option.name].push(value)
|
||||
if (!ccSame) {
|
||||
_d[camelCase(option.name)].push(value)
|
||||
}
|
||||
option.aliases?.forEach((a) => _d[a].push(value))
|
||||
}
|
||||
}
|
||||
|
||||
private _getWrappedLines(
|
||||
list: Array<{ name: string; description?: string; additionalColorCount?: number }>
|
||||
): string[] {
|
||||
const { normalColors, highlightColors } = this._help
|
||||
const lines: string[] = []
|
||||
|
||||
let maxNameLen = this._help.useGlobalColumns ? this._maxNameLen ?? 0 : 0
|
||||
for (const item of list) {
|
||||
if (item.name.length > maxNameLen) {
|
||||
maxNameLen = item.name.length
|
||||
}
|
||||
}
|
||||
if (this._help.useGlobalColumns) {
|
||||
this._maxNameLen = maxNameLen
|
||||
}
|
||||
|
||||
const ARG_SPACE_LEN = 2
|
||||
const INDENT_LEN = 2
|
||||
const nameFullSize = maxNameLen + ARG_SPACE_LEN + INDENT_LEN
|
||||
|
||||
for (const item of list) {
|
||||
const cmdName = this.color(highlightColors, `${item.name}`).padEnd(
|
||||
nameFullSize + this.colorCount(highlightColors) * COLOR_CODE_LEN,
|
||||
" "
|
||||
)
|
||||
const cmdDesc = this.color(normalColors, item.description ?? "")
|
||||
|
||||
for (const line of wrap(cmdName + cmdDesc, {
|
||||
indent: nameFullSize + INDENT_LEN,
|
||||
colorCount: this.colorCount(
|
||||
normalColors,
|
||||
highlightColors,
|
||||
item.additionalColorCount ? new Array({ length: item.additionalColorCount }) : []
|
||||
),
|
||||
firstLineIndent: INDENT_LEN,
|
||||
printWidth: this._help.printWidth,
|
||||
})) {
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
lines.push("")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
private _printCommands(): string[] {
|
||||
return this._getWrappedLines(
|
||||
this._commands.map((c) => ({ name: this._fullCmdName(c), description: c.description }))
|
||||
)
|
||||
}
|
||||
|
||||
private _printOptions(): string[] {
|
||||
const lines: string[] = []
|
||||
|
||||
const { titleColors, subtitleColors } = this._help
|
||||
|
||||
const commandOpts: string[] = []
|
||||
|
||||
for (const cmd of this._commands) {
|
||||
const opts = this._commandOptions(cmd)
|
||||
if (opts.length) {
|
||||
commandOpts.push(this.color(subtitleColors, `${cmd.name}:`))
|
||||
commandOpts.push("")
|
||||
|
||||
for (const line of this._getWrappedLines(
|
||||
opts.map((c) => ({
|
||||
name: this._fullOptName(c),
|
||||
description: this._optionDescription(c),
|
||||
additionalColorCount: c.defaultValue !== undefined ? 1 : 0,
|
||||
}))
|
||||
)) {
|
||||
commandOpts.push(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(this.color(titleColors, commandOpts.length ? "Command Options:" : "Options:"))
|
||||
lines.push("")
|
||||
|
||||
for (const line of commandOpts) {
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
const globalOpts = this._globalOptions()
|
||||
if (globalOpts.length) {
|
||||
if (commandOpts.length) {
|
||||
lines.push(this.color(titleColors, "Global Options:"))
|
||||
lines.push("")
|
||||
}
|
||||
for (const line of this._getWrappedLines(
|
||||
globalOpts.map((c) => ({ name: this._fullOptName(c), description: this._optionDescription(c) }))
|
||||
)) {
|
||||
lines.push(line)
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
private _optionDescription(c: OptionDef<Options, any>): string | undefined {
|
||||
if (c.defaultValue === undefined || !this._help.includeDefaults) {
|
||||
return c.description
|
||||
}
|
||||
|
||||
return [c.description!, this.color(this._help.bodyColors, `(default: ${c.defaultValue.toString().trim()})`)]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
private _fullCmdName(cmd: CommandDef<Options>) {
|
||||
return [cmd.name, ...(cmd.aliases ?? [])].join(this._help.commandNameSeparator)
|
||||
}
|
||||
|
||||
private _fullOptName(opt: OptionDef<Options, any>) {
|
||||
return [`--${opt.name}`, ...(opt.aliases ?? []).map((a) => `-${a}`)].join(this._help.optionNameSeparator)
|
||||
}
|
||||
|
||||
private _commandOptions(cmd: CommandDef<Options>): OptionDef<Options, any>[] {
|
||||
return this._options.filter(
|
||||
(o) =>
|
||||
(asArray(o.commands).length && asArray(o.commands).includes(cmd.name)) ||
|
||||
cmd.aliases?.some((a) => asArray(o.commands).includes(a))
|
||||
)
|
||||
}
|
||||
|
||||
private _optionCommands(opt: OptionDef<Options, any>): OptionDef<Options, any>[] {
|
||||
return this._commands.filter((c) => {
|
||||
return asArray(opt.commands).some((_c) => {
|
||||
return [c.name, ...(c.aliases ?? [])].includes(_c!)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private _globalOptions(): OptionDef<Options, any>[] {
|
||||
return this._options.filter((o) => !o.commands)
|
||||
}
|
||||
|
||||
private color(color: ArrayOr<keyof typeof chalk>, ...text: any[]): string {
|
||||
if (!this._help.useColors) {
|
||||
return text.join(" ")
|
||||
}
|
||||
let output: string = undefined as any
|
||||
for (const c of asArray(color)) {
|
||||
output = (chalk[c as keyof typeof chalk] as typeof chalk.dim)(...(output ? [output] : text))
|
||||
}
|
||||
return chalk.reset(output)
|
||||
}
|
||||
|
||||
private colorCount(...colors: any[]): number {
|
||||
if (!this._help.useColors) {
|
||||
return 0
|
||||
}
|
||||
return colorCount(...colors)
|
||||
}
|
||||
}
|
||||
|
||||
export function massarg<T>() {
|
||||
return new Massarg<T>()
|
||||
}
|
||||
|
||||
export default massarg
|
||||
@@ -1,60 +0,0 @@
|
||||
import massarg from "."
|
||||
|
||||
massarg()
|
||||
.help({
|
||||
// printWidth: 0,
|
||||
binName: "my-cmd",
|
||||
useGlobalColumns: true,
|
||||
header: "This is the header",
|
||||
footer: "This is the footer",
|
||||
usageExample: "command [options]",
|
||||
})
|
||||
.option({
|
||||
name: "bool",
|
||||
aliases: ["b"],
|
||||
defaultValue: false,
|
||||
commands: ["do", "cc"],
|
||||
description: "This is a boolean arg. Supply it without value or with 1 to set as true, or set value 0 for false",
|
||||
required: true,
|
||||
parse: Boolean,
|
||||
})
|
||||
.option({
|
||||
name: "number",
|
||||
aliases: ["n"],
|
||||
description: "This is a number arg, if you include this option, you must supply it with a value.",
|
||||
defaultValue: 0,
|
||||
commands: "do",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.command({
|
||||
name: "do-something",
|
||||
description: "This command does something.",
|
||||
aliases: ["do", "d"],
|
||||
run: console.log.bind(undefined, "do"),
|
||||
})
|
||||
.command({
|
||||
name: "my-custom-command",
|
||||
description:
|
||||
"This is another command that does something. It's a different one just to see more available. This " +
|
||||
"description is just to fill more lines.",
|
||||
aliases: ["cc", "c"],
|
||||
run: console.log.bind(undefined, "do"),
|
||||
})
|
||||
.main(console.log.bind(undefined, "main"))
|
||||
.example({
|
||||
input: "my-cmd test --one --two",
|
||||
description: "This is how you do it",
|
||||
output: "42",
|
||||
})
|
||||
// .command({
|
||||
// name: "cmd",
|
||||
// description: "Command",
|
||||
// run: () => void 0,
|
||||
// })
|
||||
// .option({
|
||||
// name: "number",
|
||||
// description: "Number value",
|
||||
// commands: "cmd",
|
||||
// parse: (v) => parseInt(v),
|
||||
// })
|
||||
.parse()
|
||||
@@ -1,138 +0,0 @@
|
||||
import capitalize from "lodash/capitalize"
|
||||
import massarg from "../src"
|
||||
import { colorList } from "../src/assertions"
|
||||
import { HelpDef } from "../src/types"
|
||||
|
||||
describe("Assertions", () => {
|
||||
test("should assert main command input", () => {
|
||||
expect(() => massarg().main(undefined as any)).toThrow("Main: Main must be provided")
|
||||
expect(() => massarg().main(1 as any)).toThrow("Main: Main must be function")
|
||||
})
|
||||
|
||||
test("should assert command input", () => {
|
||||
expect(() =>
|
||||
massarg().command({
|
||||
name: 1 as any,
|
||||
run: () => void 0,
|
||||
})
|
||||
).toThrow("Command: Name must be string")
|
||||
|
||||
expect(() =>
|
||||
massarg().command({
|
||||
name: undefined as any,
|
||||
run: () => void 0,
|
||||
})
|
||||
).toThrow("Command: Name must be provided")
|
||||
|
||||
expect(() =>
|
||||
massarg().command({
|
||||
name: "cmd",
|
||||
run: undefined as any,
|
||||
})
|
||||
).toThrow("Command: Run must be provided")
|
||||
|
||||
expect(() =>
|
||||
massarg().command({
|
||||
name: "cmd",
|
||||
run: 1 as any,
|
||||
})
|
||||
).toThrow("Command: Run must be function")
|
||||
|
||||
expect(() =>
|
||||
massarg()
|
||||
.command({
|
||||
name: "cmd",
|
||||
run: () => void 0,
|
||||
})
|
||||
.command({
|
||||
name: "cmd2",
|
||||
aliases: ["cmd"],
|
||||
run: () => void 0,
|
||||
})
|
||||
).toThrow("Command: Aliases must be unique")
|
||||
})
|
||||
|
||||
test("should assert option input", () => {
|
||||
expect(() =>
|
||||
massarg().option({
|
||||
name: 1 as any,
|
||||
})
|
||||
).toThrow("Option: Name must be string")
|
||||
|
||||
expect(() =>
|
||||
massarg().option({
|
||||
name: undefined as any,
|
||||
})
|
||||
).toThrow("Option: Name must be provided")
|
||||
|
||||
expect(() =>
|
||||
massarg().option({
|
||||
name: "opt",
|
||||
parse: 1 as any,
|
||||
})
|
||||
).toThrow("Option: Parse must be function")
|
||||
|
||||
expect(() =>
|
||||
massarg()
|
||||
.option({
|
||||
name: "opt",
|
||||
})
|
||||
.option({
|
||||
name: "opt2",
|
||||
aliases: ["opt"],
|
||||
})
|
||||
).toThrow("Option: Aliases must be unique")
|
||||
})
|
||||
|
||||
describe("help input assertions", () => {
|
||||
const strings: Partial<Record<keyof HelpDef, string>> = {
|
||||
binName: "Binary Name",
|
||||
optionNameSeparator: "Option Name Separator",
|
||||
commandNameSeparator: "Command Name Separator",
|
||||
header: "Header",
|
||||
footer: "Footer",
|
||||
exampleInputPrefix: "Example Input Prefix",
|
||||
exampleOutputPrefix: "Example Output Prefix",
|
||||
usageExample: "Usage Example",
|
||||
}
|
||||
|
||||
const bools: Partial<Record<keyof HelpDef, string>> = {
|
||||
includeDefaults: "Include Defaults",
|
||||
useColors: "Use Colors",
|
||||
useGlobalColumns: "Use Global Columns",
|
||||
}
|
||||
|
||||
for (const k of Object.getOwnPropertyNames(strings)) {
|
||||
test(`should assert string ${k}`, () => {
|
||||
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
|
||||
`Help: ${strings[k as keyof HelpDef]} must be string`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
for (const k of Object.getOwnPropertyNames(bools)) {
|
||||
test(`should assert bool ${k}`, () => {
|
||||
expect(() => massarg().help({ [k as keyof HelpDef]: 1 as any })).toThrow(
|
||||
`Help: ${bools[k as keyof HelpDef]} must be bool`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
test("should assert other help input params", () => {
|
||||
const colors = ["normal", "body", "title", "subtitle", "highlight"]
|
||||
for (const color of colors) {
|
||||
expect(() =>
|
||||
massarg().help({
|
||||
[color + "Colors"]: [1],
|
||||
})
|
||||
).toThrow(
|
||||
`Help: ${capitalize(color)} colors must be string or array of strings. Accepted values: ` +
|
||||
colorList.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
expect(() => massarg().help({ printWidth: -1 })).toThrow("Help: Print Width must be ≥ 0")
|
||||
expect(() => massarg().help({ printWidth: "a" as any })).toThrow("Help: Print Width must be number")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
import massarg from "../src"
|
||||
|
||||
describe("Commands", () => {
|
||||
test("should run command", () => {
|
||||
const cmd = jest.fn()
|
||||
massarg()
|
||||
.command({
|
||||
name: "cmd",
|
||||
aliases: ["c"],
|
||||
run: cmd,
|
||||
})
|
||||
.parse(["cmd"])
|
||||
expect(cmd).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
test("should run command with alias", () => {
|
||||
const cmd = jest.fn()
|
||||
massarg()
|
||||
.command({
|
||||
name: "cmd",
|
||||
aliases: ["c"],
|
||||
run: cmd,
|
||||
})
|
||||
.parse(["c"])
|
||||
expect(cmd).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("should consume the first command, then treat the rest as extras", () => {
|
||||
const cmd = jest.fn()
|
||||
const opt = jest.fn()
|
||||
const def = massarg()
|
||||
.command({
|
||||
name: "cmd",
|
||||
aliases: ["c"],
|
||||
run: cmd,
|
||||
})
|
||||
.command({
|
||||
name: "opt",
|
||||
aliases: ["o"],
|
||||
run: opt,
|
||||
})
|
||||
|
||||
const options = def.parseArgs(["cmd", "opt"])
|
||||
|
||||
def.parse(["cmd", "opt"])
|
||||
|
||||
expect(cmd).toHaveBeenCalledTimes(1)
|
||||
expect(opt).not.toHaveBeenCalled()
|
||||
expect(options.extras).toContain("opt")
|
||||
})
|
||||
|
||||
test("should consume the first command, then pass to default", () => {
|
||||
const cmd1 = jest.fn()
|
||||
const cmd2 = jest.fn()
|
||||
const def = massarg<{ opt: string }>()
|
||||
.command({
|
||||
name: "cmd1",
|
||||
aliases: ["c1"],
|
||||
run: cmd1,
|
||||
})
|
||||
.command({
|
||||
name: "cmd2",
|
||||
aliases: ["c2"],
|
||||
run: cmd2,
|
||||
})
|
||||
.option({
|
||||
name: "opt",
|
||||
isDefault: true,
|
||||
aliases: ["o"],
|
||||
commands: ["c1"],
|
||||
required: true,
|
||||
})
|
||||
|
||||
const options = def.parseArgs(["cmd1", "opt"])
|
||||
|
||||
def.parse(["cmd1", "opt"])
|
||||
|
||||
expect(cmd1).toHaveBeenCalledTimes(1)
|
||||
expect(cmd2).not.toHaveBeenCalled()
|
||||
expect(options.opt).toBe("opt")
|
||||
})
|
||||
|
||||
test("should consume the first command, then pass to default when option name matches command name", () => {
|
||||
const cmd1 = jest.fn()
|
||||
const opt = jest.fn()
|
||||
const def = massarg<{ opt: string }>()
|
||||
.command({
|
||||
name: "cmd1",
|
||||
aliases: ["c1"],
|
||||
run: cmd1,
|
||||
})
|
||||
.command({
|
||||
name: "opt",
|
||||
aliases: ["o"],
|
||||
run: opt,
|
||||
})
|
||||
.option({
|
||||
name: "opt",
|
||||
isDefault: true,
|
||||
aliases: ["o"],
|
||||
commands: ["c1"],
|
||||
required: true,
|
||||
})
|
||||
|
||||
const options = def.parseArgs(["cmd1", "opt"])
|
||||
|
||||
def.parse(["cmd1", "opt"])
|
||||
|
||||
expect(cmd1).toHaveBeenCalledTimes(1)
|
||||
expect(opt).not.toHaveBeenCalled()
|
||||
expect(options.opt).toBe("opt")
|
||||
})
|
||||
})
|
||||
@@ -1,108 +0,0 @@
|
||||
import massarg from "../src"
|
||||
|
||||
describe("Examples", () => {
|
||||
describe("basic parse", () => {
|
||||
test("should parse input only", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.example({
|
||||
input: "my-cmd --number 10",
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
"Examples:" +
|
||||
"\n\n" +
|
||||
" $ my-cmd --number 10" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should parse output", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.example({
|
||||
input: "my-cmd --number 10",
|
||||
output: "Your number is 10 which is an integer",
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
"Examples:" +
|
||||
"\n\n" +
|
||||
" $ my-cmd --number 10" +
|
||||
"\n" +
|
||||
" ➜ Your number is 10 which is an integer" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test("should wrap input properly", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.example({
|
||||
input: "my-cmd --number 10 --another-number 20 --and-yet-another-number 30 --what-about-another 40",
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
"Examples:" +
|
||||
"\n\n" +
|
||||
" $ my-cmd --number 10 --another-number 20 --and-yet-another-number 30" +
|
||||
"\n" +
|
||||
" --what-about-another 40" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should wrap description properly", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.example({
|
||||
input: "my-cmd --number 10",
|
||||
description:
|
||||
"This is a really long test. Very long text indeed, which should eventually span to more than 1 line, so indentation can be tested.",
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
"Examples:" +
|
||||
"\n\n" +
|
||||
" This is a really long test. Very long text indeed, which should eventually" +
|
||||
"\n" +
|
||||
" span to more than 1 line, so indentation can be tested." +
|
||||
"\n\n" +
|
||||
" $ my-cmd --number 10" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
import massarg from "../src"
|
||||
|
||||
describe("Extras", () => {
|
||||
test("should take extra values unparsed", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["--number", "10", "app name"])
|
||||
expect(options).toHaveProperty("number", 10)
|
||||
expect(options).toHaveProperty("extras", ["app name"])
|
||||
})
|
||||
})
|
||||
@@ -1,174 +0,0 @@
|
||||
import massarg from "../src"
|
||||
|
||||
describe("Print Help", () => {
|
||||
test("should print to console", () => {
|
||||
const mock = jest.spyOn(console, "log").mockImplementation(() => void 0)
|
||||
massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.printHelp()
|
||||
expect(mock).toBeCalled()
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
test("should print header & footer", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false, header: "This is a header", footer: "This is a footer" })
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"This is a header" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
" --number Number value" +
|
||||
"\n\n" +
|
||||
"This is a footer" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should print default value", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
defaultValue: 0,
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
" --number Number value (default: 0)" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should print help without command options", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
" --number Number value" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should not throw when passing args", () => {
|
||||
expect(() =>
|
||||
massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString(["--help"])
|
||||
.join("\n")
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
test("should print help correctly with only global options", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.command({
|
||||
name: "cmd",
|
||||
description: "Command",
|
||||
run: () => void 0,
|
||||
})
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Commands:" +
|
||||
"\n\n" +
|
||||
" cmd Command" +
|
||||
"\n\n" +
|
||||
"Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n\n" +
|
||||
" --number Number value" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
|
||||
test("should print help correctly with command options", () => {
|
||||
const helpStr = massarg()
|
||||
.help({ binName: "test", useColors: false })
|
||||
.command({
|
||||
name: "cmd",
|
||||
description: "Command",
|
||||
run: () => void 0,
|
||||
})
|
||||
.option({
|
||||
name: "number",
|
||||
description: "Number value",
|
||||
commands: "cmd",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.getHelpString()
|
||||
.join("\n")
|
||||
|
||||
expect(helpStr).toBe(
|
||||
"Usage: test [command] [options]" +
|
||||
"\n\n" +
|
||||
"Commands:" +
|
||||
"\n\n" +
|
||||
" cmd Command" +
|
||||
"\n\n" +
|
||||
"Command Options:" +
|
||||
"\n\n" +
|
||||
"cmd:" +
|
||||
"\n\n" +
|
||||
" --number Number value" +
|
||||
"\n\n" +
|
||||
"Global Options:" +
|
||||
"\n\n" +
|
||||
" --help|-h Display help information" +
|
||||
"\n"
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,268 +0,0 @@
|
||||
import chalk from "chalk"
|
||||
import massarg from "../src"
|
||||
import { OptionDef } from "../src/types"
|
||||
|
||||
describe("Options", () => {
|
||||
describe("basics", () => {
|
||||
test("should parse properly", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["--number", "10"])
|
||||
expect(options).toHaveProperty("number", 10)
|
||||
})
|
||||
|
||||
test("should read from alias", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
aliases: ["n"],
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["-n", "10"])
|
||||
expect(options).toHaveProperty("number", 10)
|
||||
expect(options).toHaveProperty("n", 10)
|
||||
})
|
||||
})
|
||||
|
||||
describe("parsing", () => {
|
||||
test("should camelCase names", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "is-number",
|
||||
aliases: ["n"],
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["--is-number", "10"])
|
||||
|
||||
expect(options).toHaveProperty("isNumber", 10)
|
||||
expect(options).toHaveProperty("is-number", 10)
|
||||
expect(options).toHaveProperty("n", 10)
|
||||
})
|
||||
|
||||
describe("bool", () => {
|
||||
test("should parse bool in correct forms", () => {
|
||||
const boolOpt = {
|
||||
name: "bool",
|
||||
aliases: ["b"],
|
||||
boolean: true,
|
||||
} as OptionDef<any, any>
|
||||
const defOpt = {
|
||||
name: "default",
|
||||
aliases: ["d"],
|
||||
isDefault: true,
|
||||
}
|
||||
const numOpt = {
|
||||
name: "num",
|
||||
aliases: ["n"],
|
||||
parse: Number,
|
||||
}
|
||||
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool"])).toHaveProperty("bool", true)
|
||||
|
||||
// 1/0
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "1"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).option(defOpt).parseArgs(["--bool", "1", "def"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "0"])).toHaveProperty("bool", false)
|
||||
|
||||
// on/off
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "on"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).option(defOpt).parseArgs(["--bool", "on", "def"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "off"])).toHaveProperty("bool", false)
|
||||
|
||||
// true/false
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "true"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).option(defOpt).parseArgs(["--bool", "true", "def"])).toHaveProperty(
|
||||
"bool",
|
||||
true
|
||||
)
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "false"])).toHaveProperty("bool", false)
|
||||
|
||||
// yes/no
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "yes"])).toHaveProperty("bool", true)
|
||||
expect(massarg().option(boolOpt).option(defOpt).parseArgs(["--bool", "yes", "def"])).toHaveProperty(
|
||||
"bool",
|
||||
true
|
||||
)
|
||||
expect(massarg().option(boolOpt).parseArgs(["--bool", "no"])).toHaveProperty("bool", false)
|
||||
|
||||
// [none]
|
||||
expect(
|
||||
massarg().option(boolOpt).option(defOpt).option(numOpt).parseArgs(["--bool", "someName"])
|
||||
).toHaveProperty("bool", true)
|
||||
|
||||
expect(
|
||||
massarg().option(boolOpt).option(defOpt).option(numOpt).parseArgs(["--bool", "--num", "1", "someName"])
|
||||
).toHaveProperty("bool", true)
|
||||
|
||||
// [none] - negation
|
||||
expect(
|
||||
massarg().option(boolOpt).option(defOpt).option(numOpt).parseArgs(["--no-bool", "--num", "1", "someName"])
|
||||
).toHaveProperty("bool", true)
|
||||
|
||||
expect(
|
||||
massarg().option(boolOpt).option(defOpt).option(numOpt).parseArgs(["-!b", "--num", "1", "someName"])
|
||||
).toHaveProperty("bool", true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("array", () => {
|
||||
test("should parse array in correct forms", () => {
|
||||
const opts = {
|
||||
name: "array",
|
||||
array: true,
|
||||
} as OptionDef<any, any>
|
||||
|
||||
const arr0el = massarg().option(opts).parseArgs([])
|
||||
expect(arr0el).toHaveProperty("array", [])
|
||||
|
||||
const arr1el = massarg().option(opts).parseArgs(["--array", "something"])
|
||||
expect(arr1el).toHaveProperty("array", ["something"])
|
||||
|
||||
const arr2el = massarg().option(opts).parseArgs(["--array", "something", "--array", "another"])
|
||||
expect(arr2el).toHaveProperty("array", ["something", "another"])
|
||||
})
|
||||
})
|
||||
|
||||
test("should expect value when not bool", () => {
|
||||
expect(() =>
|
||||
massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["--number"])
|
||||
).toThrow("Missing value for: number")
|
||||
})
|
||||
|
||||
describe("isDefault", () => {
|
||||
test("should parse default with one default", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
isDefault: true,
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.option({
|
||||
name: "bool",
|
||||
isDefault: false,
|
||||
boolean: true,
|
||||
})
|
||||
.parseArgs(["1"])
|
||||
|
||||
expect(options).toHaveProperty("number", 1)
|
||||
expect(options).not.toHaveProperty("bool", true)
|
||||
})
|
||||
|
||||
test("should parse default with multiple defaults", () => {
|
||||
const options = massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
isDefault: true,
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.option({
|
||||
name: "bool",
|
||||
isDefault: true,
|
||||
boolean: true,
|
||||
})
|
||||
.parseArgs(["1"])
|
||||
|
||||
expect(options).toHaveProperty("number", 1)
|
||||
expect(options).toHaveProperty("bool", true)
|
||||
})
|
||||
|
||||
test("should parse default with command", () => {
|
||||
const options = massarg()
|
||||
.command({
|
||||
name: "cmd",
|
||||
run: () => void 0,
|
||||
})
|
||||
.option({
|
||||
name: "number",
|
||||
isDefault: true,
|
||||
parse: (v) => parseInt(v),
|
||||
})
|
||||
.parseArgs(["cmd", "1"])
|
||||
|
||||
expect(options).toHaveProperty("number", 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("required", () => {
|
||||
const mockProcessExit = jest.spyOn(process, "exit").mockImplementation((code) => {
|
||||
throw new Error(`Process.exit(${code})`) // Forces the code to throw instead of exit
|
||||
})
|
||||
beforeEach(() => {
|
||||
mockProcessExit.mockClear()
|
||||
})
|
||||
|
||||
test("should throw on missing required value", () => {
|
||||
const mockConsoleError = jest.spyOn(console, "error").mockImplementation(() => void 0)
|
||||
const mockConsoleLog = jest.spyOn(console, "log").mockImplementation(() => void 0)
|
||||
|
||||
expect(() =>
|
||||
massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
required: true,
|
||||
})
|
||||
.parse(["--not-number", "abcdefg"])
|
||||
).toThrow("Process.exit(1)")
|
||||
expect(mockConsoleError).toBeCalledWith(
|
||||
chalk.red`Option: \`number\` is required, but was not defined. Try using: \`--number \{value\}\``
|
||||
)
|
||||
mockConsoleError.mockRestore()
|
||||
mockConsoleLog.mockRestore()
|
||||
})
|
||||
|
||||
test("should not throw on existing required value for command", () => {
|
||||
const mockConsoleError = jest.spyOn(console, "error").mockImplementation(() => void 0)
|
||||
const mockConsoleLog = jest.spyOn(console, "log").mockImplementation(() => void 0)
|
||||
expect(() =>
|
||||
massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
commands: "cmd",
|
||||
required: true,
|
||||
})
|
||||
.command({
|
||||
name: "cmd",
|
||||
run: () => void 0,
|
||||
})
|
||||
.parse(["cmd", "--number", "10"])
|
||||
).not.toThrow("Option: `number` is required, but was not defined. Try using: `--number {value}`")
|
||||
mockConsoleError.mockRestore()
|
||||
mockConsoleLog.mockRestore()
|
||||
})
|
||||
|
||||
test("should throw on missing required value for command", () => {
|
||||
const mockConsoleError = jest.spyOn(console, "error").mockImplementation(() => void 0)
|
||||
const mockConsoleLog = jest.spyOn(console, "log").mockImplementation(() => void 0)
|
||||
expect(() =>
|
||||
massarg()
|
||||
.option({
|
||||
name: "number",
|
||||
parse: (v) => parseInt(v),
|
||||
commands: "cmd",
|
||||
required: true,
|
||||
})
|
||||
.command({
|
||||
name: "cmd",
|
||||
run: () => void 0,
|
||||
})
|
||||
.parse(["cmd"])
|
||||
).toThrow("Process.exit(1)")
|
||||
expect(mockConsoleError).toBeCalledWith(
|
||||
chalk.red`Option: \`number\` is required for command: \`cmd\`, but was not defined. Try using: \`--number \{value\}\``
|
||||
)
|
||||
mockConsoleError.mockRestore()
|
||||
mockConsoleLog.mockRestore()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,21 +0,0 @@
|
||||
import chalk from "chalk"
|
||||
import { color, colorCount } from "../src/utils"
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("Color", () => {
|
||||
test("should reset color around text", () => {
|
||||
expect(color("green", "test")).toBe(chalk.reset`${chalk.green`test`}`)
|
||||
})
|
||||
test("should work with arrays", () => {
|
||||
expect(color(["green", "bgGray"], "test")).toBe(chalk.reset`${chalk.bgGray`${chalk.green`test`}`}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Color Count", () => {
|
||||
test("should parse correct color counts", () => {
|
||||
expect(colorCount(["dim", "yellow"])).toBe(2)
|
||||
expect(colorCount(["dim", "yellow"], "green")).toBe(3)
|
||||
expect(colorCount(["dim", "yellow"], "green", ["red"])).toBe(4)
|
||||
})
|
||||
})
|
||||
})
|
||||
261
_old/types.ts
261
_old/types.ts
@@ -1,261 +0,0 @@
|
||||
import { ArrayOr } from "./utils"
|
||||
import chalk from "chalk"
|
||||
|
||||
export type OptionsBase = {
|
||||
/** When `true`, will output help text and exit the process without error */
|
||||
help: boolean
|
||||
|
||||
/** This contains arguments that were not taken by any command or option. */
|
||||
extras: string[]
|
||||
}
|
||||
|
||||
export type MainDef<Options> = (options: Options & OptionsBase) => void
|
||||
|
||||
export interface Named {
|
||||
name: string
|
||||
aliases?: string[]
|
||||
}
|
||||
|
||||
export interface OptionDef<Options, Value> extends Named {
|
||||
/** Option name. When using in CLI, you will use `--name`, e.g. `--my-option`. */
|
||||
name: string
|
||||
|
||||
/**
|
||||
* When `true`, any args placed without name will be applied to this option. When more than one arg is supplied
|
||||
* this way, only the last given will be used (unless the option is an array type).
|
||||
* When more than one option has this turned on, they will all be given these values. Use carefully. */
|
||||
isDefault?: boolean
|
||||
|
||||
/**
|
||||
* In addition to primary name, you may also define aliases. Aliases when used in CLI should only be prefixed with
|
||||
* 1 hypen, as such: `-o`
|
||||
*/
|
||||
aliases?: string[]
|
||||
|
||||
/** Description of option to display in help text. */
|
||||
description?: string
|
||||
|
||||
/** Default value to use when none is supplied. */
|
||||
defaultValue?: Value
|
||||
|
||||
/**
|
||||
* A boolean field may be supplied without value, which will cause it to parse as `true`.
|
||||
* Additional parsing may still be done using the `parse` option.
|
||||
*/
|
||||
boolean?: boolean
|
||||
|
||||
/**
|
||||
* An array field will collect any inputs to it into a list. Each item in the list will be parsed with `parse` before
|
||||
* being added.
|
||||
*/
|
||||
array?: boolean
|
||||
|
||||
/**
|
||||
* A required option will throw an error if it's not passed as input. If you attach this option to a specific command
|
||||
* (or multiple commands), it will only be required when using that command. If not, it will be required for any
|
||||
* command.
|
||||
*/
|
||||
required?: boolean
|
||||
|
||||
/**
|
||||
* Commands this option is relevant for. You may use either name or alias of command, but in the help text, only the
|
||||
* name will be shown as the section title.
|
||||
*
|
||||
* If you supply none, this option will be a global option by default.
|
||||
*
|
||||
* **Please note** this does not affect parsing: every option will be available when the program is run. This is only
|
||||
* for organizing it properly in the help text. But as long as you don't use that option in a command that doesn't
|
||||
* need o use it, nothing should work differently.
|
||||
*/
|
||||
commands?: ArrayOr<string>
|
||||
|
||||
/**
|
||||
* Use this function to decide what to do with the arg that was passed. When ommitted, the alue will be returned
|
||||
* as-is, which is always a string. You may cast or convert your input here so that it will be available already
|
||||
* parsed when it's needed.
|
||||
*
|
||||
* @param value The string arg that was passed
|
||||
* @param options Any already-parsed options in the current context. The order is not guaranteed, so some args will
|
||||
* not necessarily be parsed before this one.
|
||||
*/
|
||||
parse?(value: string, options: Options & OptionsBase): Value
|
||||
}
|
||||
|
||||
export interface CommandDef<T> extends Named {
|
||||
/**
|
||||
* Command name. When using in CLI, you will use `name` without any prefixes, unlike options.
|
||||
* Also, the first command that was parsed will run, and the others will be skipped.
|
||||
*/
|
||||
name: string
|
||||
|
||||
/** In addition to primary name, you may also define aliases. */
|
||||
aliases?: string[]
|
||||
|
||||
/** Description of command to display in help text. */
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* This is the function that runs this command.
|
||||
*
|
||||
* @param options All the parsed options (or defaults) that were passed in the CLI will be available here.
|
||||
*/
|
||||
run(options: T & OptionsBase): void
|
||||
}
|
||||
|
||||
export interface HelpDef {
|
||||
/**
|
||||
* Desired width to accommodate to when outputting help to the shell.
|
||||
* Descriptions that are longer than this value will be wrapped into the next line.
|
||||
*
|
||||
* Default: `80`. Use `0` to disable wrapping. */
|
||||
printWidth?: number
|
||||
|
||||
/**
|
||||
* The name of your application binary. Massarg attempts to infer this using the args, but you may override this
|
||||
* if you don't like the inferred value.
|
||||
*/
|
||||
binName?: string
|
||||
|
||||
/**
|
||||
* A single color or array of colors to use on normal text (descriptions, usage example, etc.)
|
||||
*
|
||||
* The colors are passed to `chalk`, so you can use any color `chalk` supports, including foreground and
|
||||
* background colors.
|
||||
*
|
||||
* **Please note** that combining colors may break wrapping, so please test your help output before releasing.
|
||||
*
|
||||
* Defaults to `"dim"`
|
||||
*/
|
||||
normalColors?: ArrayOr<keyof typeof chalk>
|
||||
|
||||
/**
|
||||
* A single color or array of colors to use on highlighted text (command names, option names, binary name, etc)
|
||||
*
|
||||
* The colors are passed to `chalk`, so you can use any color `chalk` supports, including foreground and
|
||||
* background colors.
|
||||
*
|
||||
* **Please note** that combining colors may break wrapping, so please test your help output before releasing.
|
||||
*
|
||||
* Defaults to `"yellow"`
|
||||
*/
|
||||
highlightColors?: ArrayOr<keyof typeof chalk>
|
||||
|
||||
/**
|
||||
* A single color or array of colors to use on title text ("Options", "Usage", etc)
|
||||
*
|
||||
* The colors are passed to `chalk`, so you can use any color `chalk` supports, including foreground and
|
||||
* background colors.
|
||||
*
|
||||
* **Please note** that combining colors may break wrapping, so please test your help output before releasing.
|
||||
*
|
||||
* Defaults to `"white"`
|
||||
*/
|
||||
titleColors?: ArrayOr<keyof typeof chalk>
|
||||
|
||||
/**
|
||||
* A single color or array of colors to use on subtitle text (e.g. command titles for non-gloal options)
|
||||
*
|
||||
* The colors are passed to `chalk`, so you can use any color `chalk` supports, including foreground and
|
||||
* background colors.
|
||||
*
|
||||
* **Please note** that combining colors may break wrapping, so please test your help output before releasing.
|
||||
*
|
||||
* Defaults to `["bold", "dim"]`
|
||||
*/
|
||||
subtitleColors?: ArrayOr<keyof typeof chalk>
|
||||
|
||||
/**
|
||||
* A single color or array of colors to use on body text (e.g. header and footer)
|
||||
*
|
||||
* The colors are passed to `chalk`, so you can use any color `chalk` supports, including foreground and
|
||||
* background colors.
|
||||
*
|
||||
* **Please note** that combining colors may break wrapping, so please test your help output before releasing.
|
||||
*
|
||||
* Defaults to `["white"]`
|
||||
*/
|
||||
bodyColors?: ArrayOr<keyof typeof chalk>
|
||||
|
||||
/**
|
||||
* Additional content to display below the usage line, and above the rest.
|
||||
*/
|
||||
header?: string
|
||||
|
||||
/**
|
||||
* Additional content to display below the commands and options, at the very bottom.
|
||||
*/
|
||||
footer?: string
|
||||
|
||||
/**
|
||||
* Separator for command name & its aliases.
|
||||
*
|
||||
* Defaults to `" | "`, e.g. for command `"my-cmd"` with aliases `["m", "c"]`, the ourput will be:
|
||||
*
|
||||
* ```
|
||||
* my-cmd | m | c
|
||||
* ```
|
||||
*/
|
||||
commandNameSeparator?: string
|
||||
|
||||
/**
|
||||
* Separator for option name & its aliases.
|
||||
*
|
||||
* Defaults to `"|"`, e.g. for option `"my-bool"` with aliases `["m", "b"]`, the ourput will be:
|
||||
*
|
||||
* ```
|
||||
* --my-bool|m|b
|
||||
* ```
|
||||
*/
|
||||
optionNameSeparator?: string
|
||||
|
||||
/**
|
||||
* When `true`, all the command and option names' and descritions' columns will align with each other.
|
||||
* When `false`, they will all be aligned only within their own category. This can save you white-space when
|
||||
* you have some long-named options or commands that cause all others to have too much.
|
||||
*
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
useGlobalColumns?: boolean
|
||||
|
||||
/**
|
||||
* Text to be shown next to the binary name, on the Usage line.
|
||||
*
|
||||
* Defaults to `"[command] [options]"`, which outputs:
|
||||
*
|
||||
* ```
|
||||
* my-bin [comman] [options]
|
||||
* ```
|
||||
*/
|
||||
usageExample?: string
|
||||
|
||||
/**
|
||||
* When disabled, all colors in the output will be disabled.
|
||||
*/
|
||||
useColors?: boolean
|
||||
|
||||
/**
|
||||
* When disabled, the default values will not be appended to the help text of each option.
|
||||
*/
|
||||
includeDefaults?: boolean
|
||||
|
||||
/**
|
||||
* The prefix at the start of the line when presenting examples, at the input (top) line.
|
||||
*/
|
||||
exampleInputPrefix?: string
|
||||
|
||||
/**
|
||||
* The prefix at the start of the line when presenting examples, at the output (bottom) line.
|
||||
*/
|
||||
exampleOutputPrefix?: string
|
||||
}
|
||||
|
||||
export interface ExampleDef {
|
||||
/** The input line - to show examples of parameters, commands, etc */
|
||||
input: string
|
||||
|
||||
/** The output line - to show the output of whatever `input` is regarding. */
|
||||
output?: string
|
||||
|
||||
/** An optional description which will be used as a title. */
|
||||
description?: string
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import chalk from "chalk"
|
||||
// import chunk from "lodash/chunk"
|
||||
import repeat from "lodash/repeat"
|
||||
import merge from "lodash/merge"
|
||||
|
||||
export function color(color: ArrayOr<keyof typeof chalk>, ...text: any[]): string {
|
||||
let output: string = undefined as any
|
||||
for (const c of asArray(color)) {
|
||||
output = (chalk[c as keyof typeof chalk] as typeof chalk.dim)(...(output ? [output] : text))
|
||||
}
|
||||
return chalk.reset(output)
|
||||
}
|
||||
|
||||
export function colorCount(...colors: any[]): number {
|
||||
return asArray(colors).reduce((all, colorSet) => all + asArray(colorSet).length, 0)
|
||||
}
|
||||
|
||||
export interface WrapOptions {
|
||||
indent?: number
|
||||
firstLineIndent?: number
|
||||
printWidth?: number
|
||||
colorCount?: number
|
||||
}
|
||||
|
||||
export function wrap(text: string, options?: WrapOptions): string[] {
|
||||
const _opts = merge(
|
||||
{
|
||||
printWidth: 100,
|
||||
indent: 0,
|
||||
colorCount: 0,
|
||||
} as WrapOptions,
|
||||
options
|
||||
) as Required<WrapOptions>
|
||||
|
||||
const indentSize = _opts.indent ?? 0
|
||||
const firstIndentSize = _opts.firstLineIndent ?? indentSize
|
||||
const maxLineLength = _opts.printWidth - firstIndentSize + COLOR_CODE_LEN * _opts.colorCount
|
||||
|
||||
function indent(i: number, l: string): string {
|
||||
return repeat(" ", i === 0 ? firstIndentSize : indentSize) + l
|
||||
}
|
||||
|
||||
if (!_opts.printWidth || maxLineLength <= 0) {
|
||||
return text.split("\n").map((l, i) => indent(i, l))
|
||||
}
|
||||
|
||||
let lines = chunk(text, maxLineLength).map((l, i) => indent(i, l))
|
||||
|
||||
lines = [
|
||||
lines[0],
|
||||
...chunk(
|
||||
lines
|
||||
.slice(1)
|
||||
.map((l) => l.trim())
|
||||
.join(" ")
|
||||
.trim(),
|
||||
maxLineLength - indentSize - COLOR_CODE_LEN
|
||||
).map((l, i) => indent(i + 1, l)),
|
||||
].filter((l) => l.trim().length)
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
export const COLOR_CODE_LEN = color("yellow", " ").length - 1
|
||||
|
||||
function chunk(text: string, len: number): string[] {
|
||||
const arr = text.split(" ")
|
||||
const result = []
|
||||
let subStr = arr[0]
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
let word = arr[i]
|
||||
if (subStr.length + word.length + 1 <= len) {
|
||||
subStr = subStr + " " + word
|
||||
} else {
|
||||
result.push(subStr)
|
||||
subStr = word
|
||||
}
|
||||
}
|
||||
if (subStr.length) {
|
||||
result.push(subStr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export type ArrayOr<T> = T | T[]
|
||||
export function asArray<T>(obj: T | T[]): T[] {
|
||||
return Array.isArray(obj) ? obj ?? [] : obj ? [obj] : []
|
||||
}
|
||||
@@ -19,7 +19,8 @@
|
||||
"dev": "tsc --watch",
|
||||
"cmd": "ts-node src/sample.ts",
|
||||
"test": "jest",
|
||||
"docgen": "typedoc --out docs src/**/*.ts --exclude src/sample.ts --plugin typedoc-plugin-zod --theme default",
|
||||
"doc": "typedoc --out docs src/**/*.ts --exclude src/sample.ts --plugin typedoc-plugin-zod --theme default",
|
||||
"deploy-doc": "pnpm doc && gh-pages -d docs",
|
||||
"semantic-release": "semantic-release"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -27,6 +28,7 @@
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/jest": "^29.5.8",
|
||||
"@types/node": "^20.9.2",
|
||||
"gh-pages": "^6.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"semantic-release": "^22.0.8",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
||||
123
pnpm-lock.yaml
generated
123
pnpm-lock.yaml
generated
@@ -22,6 +22,9 @@ devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.9.2
|
||||
version: 20.9.2
|
||||
gh-pages:
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.9.2)(ts-node@10.9.1)
|
||||
@@ -1189,6 +1192,22 @@ packages:
|
||||
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
|
||||
dev: true
|
||||
|
||||
/array-union@1.0.2:
|
||||
resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
array-uniq: 1.0.3
|
||||
dev: true
|
||||
|
||||
/array-uniq@1.0.3:
|
||||
resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/async@3.2.5:
|
||||
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
|
||||
dev: true
|
||||
|
||||
/babel-jest@29.7.0(@babel/core@7.23.3):
|
||||
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -1451,6 +1470,15 @@ packages:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: true
|
||||
|
||||
/commander@11.1.0:
|
||||
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
||||
engines: {node: '>=16'}
|
||||
dev: true
|
||||
|
||||
/commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
dev: true
|
||||
|
||||
/compare-func@2.0.0:
|
||||
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
|
||||
dependencies:
|
||||
@@ -1642,6 +1670,10 @@ packages:
|
||||
resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==}
|
||||
dev: true
|
||||
|
||||
/email-addresses@5.0.0:
|
||||
resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==}
|
||||
dev: true
|
||||
|
||||
/emittery@0.13.1:
|
||||
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1782,6 +1814,20 @@ packages:
|
||||
is-unicode-supported: 2.0.0
|
||||
dev: true
|
||||
|
||||
/filename-reserved-regex@2.0.0:
|
||||
resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/filenamify@4.3.0:
|
||||
resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
filename-reserved-regex: 2.0.0
|
||||
strip-outer: 1.0.1
|
||||
trim-repeated: 1.0.0
|
||||
dev: true
|
||||
|
||||
/fill-range@7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1789,6 +1835,15 @@ packages:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/find-cache-dir@3.3.2:
|
||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
commondir: 1.0.1
|
||||
make-dir: 3.1.0
|
||||
pkg-dir: 4.2.0
|
||||
dev: true
|
||||
|
||||
/find-up-simple@1.0.0:
|
||||
resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1878,6 +1933,20 @@ packages:
|
||||
engines: {node: '>=16'}
|
||||
dev: true
|
||||
|
||||
/gh-pages@6.1.0:
|
||||
resolution: {integrity: sha512-MdXigvqN3I66Y+tAZsQJMzpBWQOI1snD6BYuECmP+GEdryYMMOQvzn4AConk/+qNg/XIuQhB1xNGrl3Rmj1iow==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
async: 3.2.5
|
||||
commander: 11.1.0
|
||||
email-addresses: 5.0.0
|
||||
filenamify: 4.3.0
|
||||
find-cache-dir: 3.3.2
|
||||
fs-extra: 11.1.1
|
||||
globby: 6.1.0
|
||||
dev: true
|
||||
|
||||
/git-log-parser@1.2.0:
|
||||
resolution: {integrity: sha512-rnCVNfkTL8tdNryFuaY0fYiBWEBcgF748O6ZI61rslBvr2o7U65c2/6npCRqH40vuAhtgtDiqLTJjBVdrejCzA==}
|
||||
dependencies:
|
||||
@@ -1924,6 +1993,17 @@ packages:
|
||||
unicorn-magic: 0.1.0
|
||||
dev: true
|
||||
|
||||
/globby@6.1.0:
|
||||
resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
array-union: 1.0.2
|
||||
glob: 7.2.3
|
||||
object-assign: 4.1.1
|
||||
pify: 2.3.0
|
||||
pinkie-promise: 2.0.1
|
||||
dev: true
|
||||
|
||||
/graceful-fs@4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
dev: true
|
||||
@@ -2839,6 +2919,13 @@ packages:
|
||||
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
|
||||
dev: true
|
||||
|
||||
/make-dir@3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
semver: 6.3.1
|
||||
dev: true
|
||||
|
||||
/make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3084,6 +3171,11 @@ packages:
|
||||
- which
|
||||
- write-file-atomic
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
@@ -3265,11 +3357,28 @@ packages:
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/pify@2.3.0:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/pify@3.0.0:
|
||||
resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/pinkie-promise@2.0.1:
|
||||
resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
pinkie: 2.0.4
|
||||
dev: true
|
||||
|
||||
/pinkie@2.0.4:
|
||||
resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/pirates@4.0.6:
|
||||
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3685,6 +3794,13 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/strip-outer@1.0.1:
|
||||
resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
dev: true
|
||||
|
||||
/supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3779,6 +3895,13 @@ packages:
|
||||
resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==}
|
||||
dev: true
|
||||
|
||||
/trim-repeated@1.0.0:
|
||||
resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
dev: true
|
||||
|
||||
/ts-jest@29.1.1(@babel/core@7.23.3)(jest@29.7.0)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
||||
Reference in New Issue
Block a user