fix: type inferences

This commit is contained in:
2023-12-16 01:32:17 +02:00
committed by Chen Asraf
parent a9eafbaf0f
commit c92785e746
6 changed files with 52 additions and 22 deletions

View File

@@ -47,7 +47,7 @@ export type CommandConfig<RunArgs extends ArgsObject = ArgsObject> = z.infer<
ReturnType<typeof CommandConfig<RunArgs>>
>
export type ArgsObject = object
export type ArgsObject = Record<string | number | symbol, any>
export type Runner<Args extends ArgsObject> = (
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
description: 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
* to the sub-command when invoked.
*/
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args>
command<A extends ArgsObject = Args>(config: MassargCommand<A>): MassargCommand<Args>
command<A extends ArgsObject = Args>(config: CommandConfig<A>): MassargCommand<Args & A>
command<A extends ArgsObject = Args>(config: MassargCommand<A>): MassargCommand<Args & A>
command<A extends ArgsObject = Args>(
config: CommandConfig<A> | MassargCommand<A>,
): MassargCommand<Args> {
): MassargCommand<Args & A> {
try {
const command = config instanceof MassargCommand ? config : new MassargCommand(config)
const existing = this.commands.find((c) => c.name === command.name)
@@ -158,15 +160,16 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
}
command.parent = this
this.commands.push(command)
return this
return this as unknown as MassargCommand<Args & A>
} catch (e) {
if (isZodError(e)) {
throw new ValidationError({
e = new ValidationError({
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
code: e.issues[0].code,
message: e.issues[0].message,
})
}
this.printError(e)
throw e
}
}
@@ -192,12 +195,13 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
return this
} catch (e) {
if (isZodError(e)) {
throw new ValidationError({
e = new ValidationError({
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
code: e.issues[0].code,
message: e.issues[0].message,
})
}
this.printError(e)
throw e
}
}
@@ -233,12 +237,13 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
return this
} catch (e) {
if (isZodError(e)) {
throw new ValidationError({
e = new ValidationError({
path: [this.name, config.name, ...e.issues[0].path.map((p) => p.toString())],
code: e.issues[0].code,
message: e.issues[0].message,
})
}
this.printError(e)
throw e
}
}
@@ -307,14 +312,14 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
*/
help(config: HelpConfig): MassargCommand<Args> {
this._helpConfig = HelpConfig.parse(config)
let ret: MassargCommand<any> = this
if (this.helpConfig.bindCommand) {
this.command(new MassargHelpCommand())
ret = ret.command(new MassargHelpCommand())
}
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 {
this.getArgs(argv, args, parent, true)
} catch (e) {
const message = getErrorMessage(e)
console.error(format(message, { color: 'red' }))
this.printError(e)
}
}
private printError(e: unknown) {
const message = getErrorMessage(e)
console.error(format(message, { color: 'red' }))
}
private parseOption(arg: string, argv: string[]) {
const prefixes = { ...this.optionPrefixes }
const option = this.options.find((o) => o.isMatch(arg, prefixes))

View File

@@ -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
description: string
defaultValue?: OptionType

View File

@@ -3,7 +3,7 @@ import { MassargCommand } from './command'
import { ParseError } from './error'
type A = { test: boolean }
const echoCmd = massarg<any>({
const echoCmd = massarg<{}>({
name: 'echo',
description: 'Echo back the arguments',
aliases: ['e'],
@@ -88,7 +88,6 @@ const main = massarg<A>({
.main((opts, parser) => {
console.log('Main command - printing all opts')
console.log(opts, '\n')
parser.printHelp()
})
.command(echoCmd)
.command(addCmd)

View File

@@ -18,13 +18,16 @@ describe('sub command', () => {
)
})
test('add duplicate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts)
.command({ name: 'test2', description: 'test2', run: jest.fn() })
.command({ name: 'test2', description: 'test2', run: jest.fn() }),
).toThrow('Command "test2" already exists')
error.mockRestore()
})
test('validate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts).command({
name: 'test2',
@@ -32,6 +35,7 @@ describe('sub command', () => {
run: jest.fn(),
}),
).toThrow('Expected string, received number')
error.mockRestore()
})
})

View File

@@ -38,10 +38,12 @@ test('prints help from command', () => {
const command = massarg(opts).help({
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'])
expect(log).toHaveBeenCalled()
log.mockRestore()
error.mockRestore()
})
test('binds option', () => {
@@ -72,7 +74,7 @@ describe('prints help from option', () => {
const command = massarg(opts).help({
bindOption: true,
})
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
command.parse(['--help'])
expect(log).toHaveBeenCalled()
})
@@ -99,7 +101,7 @@ describe('prints help from option', () => {
.help({
bindOption: true,
})
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
command.parse(['--help'])
expect(log).toHaveBeenCalled()
})
@@ -111,7 +113,7 @@ test('help string', () => {
})
test('print help', () => {
const log = jest.spyOn(console, 'log').mockImplementation(() => {})
const log = jest.spyOn(console, 'log').mockImplementation(() => { })
const command = massarg(opts)
command.printHelp()
expect(log).toHaveBeenCalled()

View File

@@ -14,6 +14,7 @@ describe('option', () => {
).toBeInstanceOf(MassargCommand)
})
test('validate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts).option({
name: 'test2',
@@ -22,8 +23,10 @@ describe('option', () => {
defaultValue: '',
}),
).toThrow('Expected string, received number')
error.mockRestore()
})
test('add duplicate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts)
.option({
@@ -39,6 +42,7 @@ describe('option', () => {
defaultValue: '',
}),
).toThrow('test.test2: Option name "test2" already exists')
error.mockRestore()
})
test('default', () => {
const command = massarg(opts)
@@ -57,6 +61,7 @@ describe('option', () => {
expect(command.getArgs(['123'])).toHaveProperty('def', '123')
})
test('add 2 defaults', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts)
.option({
@@ -74,6 +79,7 @@ describe('option', () => {
).toThrow(
'Option "test2" cannot be set as default because option "test" is already set as default',
)
error.mockRestore()
})
test('uses output name', () => {
const command = massarg(opts).option({
@@ -85,6 +91,7 @@ describe('option', () => {
expect(command.getArgs(['--test2', 'test'])).toHaveProperty('test', 'test')
})
test('required', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
const command = massarg(opts).option({
name: 'test2',
description: 'test2',
@@ -92,6 +99,7 @@ describe('option', () => {
required: true,
})
expect(() => command.getArgs([])).toThrow('Missing required option: test2')
error.mockRestore()
})
})
@@ -104,13 +112,16 @@ describe('flag', () => {
)
})
test('add duplicate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts)
.flag({ name: 'test2', description: 'test2', aliases: [] })
.flag({ name: 'test2', description: 'test2', aliases: [] }),
).toThrow('test.test2: Flag name "test2" already exists')
error.mockRestore()
})
test('validate', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
expect(() =>
massarg(opts).flag({
name: 'test2',
@@ -118,13 +129,16 @@ describe('flag', () => {
aliases: [],
}),
).toThrow('Expected string, received number')
error.mockRestore()
})
describe('negation', () => {
test('no negation', () => {
const error = jest.spyOn(console, 'error').mockImplementation(() => { })
const command = massarg(opts).flag({ name: 'test2', description: 'test2', aliases: [] })
expect(() => command.getArgs(['--no-test2'])).toThrow(
'test2: Option test2 cannot be negated (received: "--no-test2")',
)
error.mockRestore()
})
test('negation', () => {
const command = massarg(opts).flag({