fix: type inferences

This commit is contained in:
2023-12-16 01:32:17 +02:00
parent 05598312dc
commit 215cd64248
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>> 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))

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

View File

@@ -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)

View File

@@ -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()
}) })
}) })

View File

@@ -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()

View File

@@ -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({