mirror of
https://github.com/chenasraf/massarg.git
synced 2026-05-18 01:39:05 +00:00
fix: type inferences
This commit is contained in:
@@ -47,7 +47,7 @@ export type CommandConfig<RunArgs extends ArgsObject = ArgsObject> = z.infer<
|
|||||||
ReturnType<typeof CommandConfig<RunArgs>>
|
ReturnType<typeof CommandConfig<RunArgs>>
|
||||||
>
|
>
|
||||||
|
|
||||||
export type ArgsObject = object
|
export type ArgsObject = Record<string | number | symbol, any>
|
||||||
|
|
||||||
export type Runner<Args extends ArgsObject> = (
|
export type Runner<Args extends ArgsObject> = (
|
||||||
options: Args,
|
options: Args,
|
||||||
@@ -73,7 +73,9 @@ export type Runner<Args extends ArgsObject> = (
|
|||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
export class MassargCommand<Args extends ArgsObject = ArgsObject>
|
||||||
|
implements Omit<CommandConfig<Args>, 'run'>
|
||||||
|
{
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
aliases: string[]
|
aliases: string[]
|
||||||
@@ -141,11 +143,11 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
* While options are not inherited, they will be passed from any parent commands
|
* While options are not inherited, they will be passed from any parent commands
|
||||||
* to the sub-command when invoked.
|
* to the sub-command when invoked.
|
||||||
*/
|
*/
|
||||||
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args>
|
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args & A>
|
||||||
command<A extends ArgsObject = Args>(config: MassargCommand<A>): MassargCommand<Args>
|
command<A extends ArgsObject = Args>(config: MassargCommand<A>): MassargCommand<Args & A>
|
||||||
command<A extends ArgsObject = Args>(
|
command<A extends ArgsObject = Args>(
|
||||||
config: CommandConfig<A> | MassargCommand<A>,
|
config: CommandConfig<A> | MassargCommand<A>,
|
||||||
): MassargCommand<Args> {
|
): MassargCommand<Args & A> {
|
||||||
try {
|
try {
|
||||||
const command = config instanceof MassargCommand ? config : new MassargCommand(config)
|
const command = config instanceof MassargCommand ? config : new MassargCommand(config)
|
||||||
const existing = this.commands.find((c) => c.name === command.name)
|
const existing = this.commands.find((c) => c.name === command.name)
|
||||||
@@ -158,15 +160,16 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
}
|
}
|
||||||
command.parent = this
|
command.parent = this
|
||||||
this.commands.push(command)
|
this.commands.push(command)
|
||||||
return this
|
return this as unknown as MassargCommand<Args & A>
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isZodError(e)) {
|
if (isZodError(e)) {
|
||||||
throw new ValidationError({
|
e = new ValidationError({
|
||||||
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
||||||
code: e.issues[0].code,
|
code: e.issues[0].code,
|
||||||
message: e.issues[0].message,
|
message: e.issues[0].message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.printError(e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,12 +195,13 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
return this
|
return this
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isZodError(e)) {
|
if (isZodError(e)) {
|
||||||
throw new ValidationError({
|
e = new ValidationError({
|
||||||
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
||||||
code: e.issues[0].code,
|
code: e.issues[0].code,
|
||||||
message: e.issues[0].message,
|
message: e.issues[0].message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.printError(e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,12 +237,13 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
return this
|
return this
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isZodError(e)) {
|
if (isZodError(e)) {
|
||||||
throw new ValidationError({
|
e = new ValidationError({
|
||||||
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
|
||||||
code: e.issues[0].code,
|
code: e.issues[0].code,
|
||||||
message: e.issues[0].message,
|
message: e.issues[0].message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.printError(e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,14 +312,14 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
*/
|
*/
|
||||||
help(config: HelpConfig): MassargCommand<Args> {
|
help(config: HelpConfig): MassargCommand<Args> {
|
||||||
this._helpConfig = HelpConfig.parse(config)
|
this._helpConfig = HelpConfig.parse(config)
|
||||||
|
let ret: MassargCommand<any> = this
|
||||||
if (this.helpConfig.bindCommand) {
|
if (this.helpConfig.bindCommand) {
|
||||||
this.command(new MassargHelpCommand())
|
ret = ret.command(new MassargHelpCommand())
|
||||||
}
|
}
|
||||||
if (this.helpConfig.bindOption) {
|
if (this.helpConfig.bindOption) {
|
||||||
this.option(new MassargHelpFlag())
|
ret = ret.option(new MassargHelpFlag())
|
||||||
}
|
}
|
||||||
return this
|
return this as MassargCommand<Args>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -343,11 +348,15 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
|
|||||||
try {
|
try {
|
||||||
this.getArgs(argv, args, parent, true)
|
this.getArgs(argv, args, parent, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = getErrorMessage(e)
|
this.printError(e)
|
||||||
console.error(format(message, { color: 'red' }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private printError(e: unknown) {
|
||||||
|
const message = getErrorMessage(e)
|
||||||
|
console.error(format(message, { color: 'red' }))
|
||||||
|
}
|
||||||
|
|
||||||
private parseOption(arg: string, argv: string[]) {
|
private parseOption(arg: string, argv: string[]) {
|
||||||
const prefixes = { ...this.optionPrefixes }
|
const prefixes = { ...this.optionPrefixes }
|
||||||
const option = this.options.find((o) => o.isMatch(arg, prefixes))
|
const option = this.options.find((o) => o.isMatch(arg, prefixes))
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ export type ArgvValue<T> = { argv: string[]; value: T; key: string }
|
|||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class MassargOption<OptionType extends any = unknown, Args extends ArgsObject = ArgsObject> {
|
export class MassargOption<OptionType extends any = unknown, Args extends ArgsObject = ArgsObject>
|
||||||
|
implements OptionConfig<OptionType, Args>
|
||||||
|
{
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
defaultValue?: OptionType
|
defaultValue?: OptionType
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { MassargCommand } from './command'
|
|||||||
import { ParseError } from './error'
|
import { ParseError } from './error'
|
||||||
|
|
||||||
type A = { test: boolean }
|
type A = { test: boolean }
|
||||||
const echoCmd = massarg<any>({
|
const echoCmd = massarg<{}>({
|
||||||
name: 'echo',
|
name: 'echo',
|
||||||
description: 'Echo back the arguments',
|
description: 'Echo back the arguments',
|
||||||
aliases: ['e'],
|
aliases: ['e'],
|
||||||
@@ -88,7 +88,6 @@ const main = massarg<A>({
|
|||||||
.main((opts, parser) => {
|
.main((opts, parser) => {
|
||||||
console.log('Main command - printing all opts')
|
console.log('Main command - printing all opts')
|
||||||
console.log(opts, '\n')
|
console.log(opts, '\n')
|
||||||
parser.printHelp()
|
|
||||||
})
|
})
|
||||||
.command(echoCmd)
|
.command(echoCmd)
|
||||||
.command(addCmd)
|
.command(addCmd)
|
||||||
|
|||||||
@@ -18,13 +18,16 @@ describe('sub command', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
test('add duplicate', () => {
|
test('add duplicate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts)
|
massarg(opts)
|
||||||
.command({ name: 'test2', description: 'test2', run: jest.fn() })
|
.command({ name: 'test2', description: 'test2', run: jest.fn() })
|
||||||
.command({ name: 'test2', description: 'test2', run: jest.fn() }),
|
.command({ name: 'test2', description: 'test2', run: jest.fn() }),
|
||||||
).toThrow('Command "test2" already exists')
|
).toThrow('Command "test2" already exists')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('validate', () => {
|
test('validate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts).command({
|
massarg(opts).command({
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
@@ -32,6 +35,7 @@ describe('sub command', () => {
|
|||||||
run: jest.fn(),
|
run: jest.fn(),
|
||||||
}),
|
}),
|
||||||
).toThrow('Expected string, received number')
|
).toThrow('Expected string, received number')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ test('prints help from command', () => {
|
|||||||
const command = massarg(opts).help({
|
const command = massarg(opts).help({
|
||||||
bindCommand: true,
|
bindCommand: true,
|
||||||
})
|
})
|
||||||
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
|
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
command.parse(['help'])
|
command.parse(['help'])
|
||||||
expect(log).toHaveBeenCalled()
|
expect(log).toHaveBeenCalled()
|
||||||
log.mockRestore()
|
log.mockRestore()
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('binds option', () => {
|
test('binds option', () => {
|
||||||
@@ -72,7 +74,7 @@ describe('prints help from option', () => {
|
|||||||
const command = massarg(opts).help({
|
const command = massarg(opts).help({
|
||||||
bindOption: true,
|
bindOption: true,
|
||||||
})
|
})
|
||||||
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
|
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
|
||||||
command.parse(['--help'])
|
command.parse(['--help'])
|
||||||
expect(log).toHaveBeenCalled()
|
expect(log).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -99,7 +101,7 @@ describe('prints help from option', () => {
|
|||||||
.help({
|
.help({
|
||||||
bindOption: true,
|
bindOption: true,
|
||||||
})
|
})
|
||||||
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
|
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
|
||||||
command.parse(['--help'])
|
command.parse(['--help'])
|
||||||
expect(log).toHaveBeenCalled()
|
expect(log).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -111,7 +113,7 @@ test('help string', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('print help', () => {
|
test('print help', () => {
|
||||||
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
|
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
|
||||||
const command = massarg(opts)
|
const command = massarg(opts)
|
||||||
command.printHelp()
|
command.printHelp()
|
||||||
expect(log).toHaveBeenCalled()
|
expect(log).toHaveBeenCalled()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe('option', () => {
|
|||||||
).toBeInstanceOf(MassargCommand)
|
).toBeInstanceOf(MassargCommand)
|
||||||
})
|
})
|
||||||
test('validate', () => {
|
test('validate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts).option({
|
massarg(opts).option({
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
@@ -22,8 +23,10 @@ describe('option', () => {
|
|||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
}),
|
}),
|
||||||
).toThrow('Expected string, received number')
|
).toThrow('Expected string, received number')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('add duplicate', () => {
|
test('add duplicate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts)
|
massarg(opts)
|
||||||
.option({
|
.option({
|
||||||
@@ -39,6 +42,7 @@ describe('option', () => {
|
|||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
}),
|
}),
|
||||||
).toThrow('test.test2: Option name "test2" already exists')
|
).toThrow('test.test2: Option name "test2" already exists')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('default', () => {
|
test('default', () => {
|
||||||
const command = massarg(opts)
|
const command = massarg(opts)
|
||||||
@@ -57,6 +61,7 @@ describe('option', () => {
|
|||||||
expect(command.getArgs(['123'])).toHaveProperty('def', '123')
|
expect(command.getArgs(['123'])).toHaveProperty('def', '123')
|
||||||
})
|
})
|
||||||
test('add 2 defaults', () => {
|
test('add 2 defaults', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts)
|
massarg(opts)
|
||||||
.option({
|
.option({
|
||||||
@@ -74,6 +79,7 @@ describe('option', () => {
|
|||||||
).toThrow(
|
).toThrow(
|
||||||
'Option "test2" cannot be set as default because option "test" is already set as default',
|
'Option "test2" cannot be set as default because option "test" is already set as default',
|
||||||
)
|
)
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('uses output name', () => {
|
test('uses output name', () => {
|
||||||
const command = massarg(opts).option({
|
const command = massarg(opts).option({
|
||||||
@@ -85,6 +91,7 @@ describe('option', () => {
|
|||||||
expect(command.getArgs(['--test2', 'test'])).toHaveProperty('test', 'test')
|
expect(command.getArgs(['--test2', 'test'])).toHaveProperty('test', 'test')
|
||||||
})
|
})
|
||||||
test('required', () => {
|
test('required', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
const command = massarg(opts).option({
|
const command = massarg(opts).option({
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
description: 'test2',
|
description: 'test2',
|
||||||
@@ -92,6 +99,7 @@ describe('option', () => {
|
|||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
expect(() => command.getArgs([])).toThrow('Missing required option: test2')
|
expect(() => command.getArgs([])).toThrow('Missing required option: test2')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -104,13 +112,16 @@ describe('flag', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
test('add duplicate', () => {
|
test('add duplicate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts)
|
massarg(opts)
|
||||||
.flag({ name: 'test2', description: 'test2', aliases: [] })
|
.flag({ name: 'test2', description: 'test2', aliases: [] })
|
||||||
.flag({ name: 'test2', description: 'test2', aliases: [] }),
|
.flag({ name: 'test2', description: 'test2', aliases: [] }),
|
||||||
).toThrow('test.test2: Flag name "test2" already exists')
|
).toThrow('test.test2: Flag name "test2" already exists')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('validate', () => {
|
test('validate', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
expect(() =>
|
expect(() =>
|
||||||
massarg(opts).flag({
|
massarg(opts).flag({
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
@@ -118,13 +129,16 @@ describe('flag', () => {
|
|||||||
aliases: [],
|
aliases: [],
|
||||||
}),
|
}),
|
||||||
).toThrow('Expected string, received number')
|
).toThrow('Expected string, received number')
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
describe('negation', () => {
|
describe('negation', () => {
|
||||||
test('no negation', () => {
|
test('no negation', () => {
|
||||||
|
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||||
const command = massarg(opts).flag({ name: 'test2', description: 'test2', aliases: [] })
|
const command = massarg(opts).flag({ name: 'test2', description: 'test2', aliases: [] })
|
||||||
expect(() => command.getArgs(['--no-test2'])).toThrow(
|
expect(() => command.getArgs(['--no-test2'])).toThrow(
|
||||||
'test2: Option test2 cannot be negated (received: "--no-test2")',
|
'test2: Option test2 cannot be negated (received: "--no-test2")',
|
||||||
)
|
)
|
||||||
|
error.mockRestore()
|
||||||
})
|
})
|
||||||
test('negation', () => {
|
test('negation', () => {
|
||||||
const command = massarg(opts).flag({
|
const command = massarg(opts).flag({
|
||||||
|
|||||||
Reference in New Issue
Block a user