Skip to content

Commit

Permalink
fix: exported types fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
chenasraf committed Dec 1, 2023
1 parent 47dbe32 commit a0aa8ec
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: chenasraf
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
Expand Down
1 change: 1 addition & 0 deletions release.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = {
'@semantic-release/changelog',
{
changelogFile: 'CHANGELOG.md',
changelogTitle: '# Change Log',
},
],
[
Expand Down
42 changes: 26 additions & 16 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { DeepRequired, setOrPush, deepMerge } from './utils'
import { MassargExample, ExampleConfig } from './example'

export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
export const CommandConfig = <RunArgs extends ArgsObject = ArgsObject>(args: z.ZodType<RunArgs>) =>
z.object({
/** Command name */
name: z.string(),
Expand All @@ -26,16 +26,18 @@ export const CommandConfig = <RunArgs extends z.ZodType>(args: RunArgs) =>
run: z
.function()
.args(args, z.any())
.returns(z.union([z.promise(z.void()), z.void()])) as z.ZodType<Runner<z.infer<RunArgs>>>,
.returns(z.union([z.promise(z.void()), z.void()])) as z.ZodType<Runner<RunArgs>>,
})

export type CommandConfig<T = unknown> = z.infer<ReturnType<typeof CommandConfig<z.ZodType<T>>>>
export type CommandConfig<RunArgs extends ArgsObject = ArgsObject> = z.infer<
ReturnType<typeof CommandConfig<RunArgs>>
>

export type ArgsObject = Record<string, unknown>
export type ArgsObject = object

export type Runner<Args extends ArgsObject> = <A extends ArgsObject = Args>(
options: A,
instance: MassargCommand<A>,
export type Runner<Args extends ArgsObject> = (
options: Args,
instance: MassargCommand<Args>,
) => Promise<void> | void

/**
Expand Down Expand Up @@ -178,12 +180,18 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
* value passed to the command. This is useful if you want to parse a string
* into a more complex type, or if you want to validate the value.
*/
option<T = string>(config: MassargOption<T>): MassargCommand<Args>
option<T = string>(config: TypedOptionConfig<T>): MassargCommand<Args>
option<T = string>(config: TypedOptionConfig<T> | MassargOption<T>): MassargCommand<Args> {
option<T = string, A extends ArgsObject = Args>(config: MassargOption<T, A>): MassargCommand<Args>
option<T = string, A extends ArgsObject = Args>(
config: TypedOptionConfig<T, A>,
): MassargCommand<Args>
option<T = string, A extends ArgsObject = Args>(
config: TypedOptionConfig<T, A> | MassargOption<T, A>,
): MassargCommand<Args> {
try {
const option =
config instanceof MassargOption ? config : MassargOption.fromTypedConfig(config)
config instanceof MassargOption
? config
: MassargOption.fromTypedConfig(config as TypedOptionConfig<T, A>)
const existing = this.options.find((c) => c.name === option.name)
if (existing) {
throw new ValidationError({
Expand Down Expand Up @@ -256,7 +264,7 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
*
* If none is provided, help will be printed.
*/
main<A extends ArgsObject = Args>(run: Runner<A>): MassargCommand<Args> {
main(run: Runner<Args>): MassargCommand<Args> {
this._run = run
return this
}
Expand All @@ -281,7 +289,7 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
message: 'Unknown option',
})
}
const res = option._parseDetails([arg, ...argv])
const res = option._parseDetails([arg, ...argv], { ...this.args })
this.args[res.key as keyof Args] = setOrPush<Args[keyof Args]>(
res.value,
this.args[res.key as keyof Args],
Expand Down Expand Up @@ -361,7 +369,7 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {

// no sub command found, run main command
if (this._run) {
this._run(this.args, parent ?? this)
this._run(this.args as Args, parent ?? this)
}
}

Expand All @@ -380,13 +388,15 @@ export class MassargCommand<Args extends ArgsObject = ArgsObject> {
}
}

export class MassargHelpCommand<T extends ArgsObject = ArgsObject> extends MassargCommand<T> {
export class MassargHelpCommand<
T extends { command?: string } = { command?: string },
> extends MassargCommand<T> {
constructor(config: Partial<Omit<CommandConfig<T>, 'run'>> = {}) {
super({
name: 'help',
aliases: ['h'],
description: 'Print help for this command, or a subcommand if specified',
run: (args, parent) => {
run: (args: { command?: string }, parent) => {
if (args.command) {
const command = parent.commands.find((c) => c.name === args.command)
if (command) {
Expand Down
58 changes: 37 additions & 21 deletions src/option.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { z } from 'zod'
import { isZodError, ParseError } from './error'
import { toCamelCase } from './utils'
import { ArgsObject } from './command'

export const OptionConfig = <T extends z.ZodType>(type: T) =>
export const OptionConfig = <OptionType, Args extends ArgsObject = ArgsObject>(
type: z.ZodType<OptionType>,
) =>
z.object({
/** Name of the option */
name: z.string(),
Expand All @@ -16,7 +19,9 @@ export const OptionConfig = <T extends z.ZodType>(type: T) =>
* Parse the value of the option. You can return any type here, or throw an error if the value
* is invalid.
*/
parse: z.function().args(z.string()).returns(type).optional(),
parse: z.function().args(z.string(), z.any()).returns(type).optional() as z.ZodOptional<
z.ZodType<Parser<Args, OptionType>>
>,
/**
* Whether the option is an array.
*
Expand All @@ -41,24 +46,33 @@ export const OptionConfig = <T extends z.ZodType>(type: T) =>
/** Specify a custom name for the output, which will be used when parsing the args. */
outputName: z.string().optional(),
})
export type OptionConfig<T = unknown> = z.infer<ReturnType<typeof OptionConfig<z.ZodType<T>>>>
export type OptionConfig<T = unknown, Args extends ArgsObject = ArgsObject> = z.infer<
ReturnType<typeof OptionConfig<T, Args>>
>

export type Parser<Args extends ArgsObject = ArgsObject, OptionType extends any = any> = (
x: string,
y: Args,
) => OptionType

export const TypedOptionConfig = <T extends z.ZodType>(type: T) =>
OptionConfig(type).merge(
export const TypedOptionConfig = <OptionType, Args extends ArgsObject = ArgsObject>(
type: z.ZodType<OptionType>,
) =>
OptionConfig<OptionType, Args>(type).merge(
z.object({
type: z.enum(['number']).optional(),
}),
)
export type TypedOptionConfig<T = unknown> = z.infer<
ReturnType<typeof TypedOptionConfig<z.ZodType<T>>>
export type TypedOptionConfig<T, A extends ArgsObject = ArgsObject> = z.infer<
ReturnType<typeof TypedOptionConfig<T, A>>
>

/**
* @see OptionConfig
* @see ArrayOptionConfig
*/
export const ArrayOptionConfig = <T extends z.ZodType>(type: T) =>
TypedOptionConfig(z.array(type)).merge(
export const ArrayOptionConfig = <T, A extends ArgsObject = ArgsObject>(type: z.ZodType<T>) =>
TypedOptionConfig<T[], A>(z.array(type)).merge(
// OptionConfig(z.array(type)).merge(
z.object({
defaultValue: z.array(type).optional(),
Expand Down Expand Up @@ -105,29 +119,31 @@ export type ArgvValue<T> = { argv: string[]; value: T; key: string }
* })
* ```
*/
export class MassargOption<T = unknown> {
export class MassargOption<OptionType extends any = unknown, Args extends ArgsObject = ArgsObject> {
name: string
description: string
defaultValue?: T
defaultValue?: OptionType
aliases: string[]
parse: (value: string) => T
parse: Parser<Args, OptionType>
isArray: boolean
isDefault: boolean
outputName?: string

constructor(options: OptionConfig<T>) {
constructor(options: OptionConfig<OptionType, Args>) {
OptionConfig(z.any()).parse(options)
this.name = options.name
this.description = options.description
this.defaultValue = options.defaultValue
this.aliases = options.aliases
this.parse = options.parse ?? ((x) => x as unknown as T)
this.parse = options.parse ?? ((x: string) => x as OptionType)
this.isArray = options.array ?? false
this.isDefault = options.isDefault ?? false
this.outputName = options.outputName
}

static fromTypedConfig<T = unknown>(config: TypedOptionConfig<T>): MassargOption<T> {
static fromTypedConfig<T = unknown, A extends ArgsObject = ArgsObject>(
config: TypedOptionConfig<T, A>,
): MassargOption<T> {
switch (config.type) {
case 'number':
return new MassargNumber(config as OptionConfig<number>) as MassargOption<T>
Expand All @@ -139,7 +155,7 @@ export class MassargOption<T = unknown> {
return this.outputName || toCamelCase(this.name)
}

_parseDetails(argv: string[]): ArgvValue<T> {
_parseDetails(argv: string[], options: ArgsObject): ArgvValue<OptionType> {
// TODO: support --option=value
let input = ''
try {
Expand All @@ -153,7 +169,7 @@ export class MassargOption<T = unknown> {
}
argv.shift()
input = argv.shift()!
const value = this.parse(input)
const value = this.parse(input, options as Args)
return { key: this.getOutputName(), value, argv }
} catch (e) {
if (isZodError(e)) {
Expand Down Expand Up @@ -243,13 +259,13 @@ export class MassargNumber extends MassargOption<number> {
constructor(options: Omit<OptionConfig<number>, 'parse'>) {
super({
...options,
parse: (value) => Number(value),
parse: (value) => Number(value) as any,
})
}

_parseDetails(argv: string[]): ArgvValue<number> {
_parseDetails(argv: string[], options: ArgsObject): ArgvValue<number> {
try {
const { argv: _argv, value } = super._parseDetails(argv)
const { argv: _argv, value } = super._parseDetails(argv, options)
if (isNaN(value)) {
throw new ParseError({
path: [this.name],
Expand Down Expand Up @@ -298,7 +314,7 @@ export class MassargFlag extends MassargOption<boolean> {
constructor(options: Omit<OptionConfig<boolean>, 'parse'>) {
super({
...options,
parse: () => true,
parse: () => true as any,
})
}

Expand Down

0 comments on commit a0aa8ec

Please sign in to comment.