zlye
Version:
A type-safe CLI parser with a Zod-like schema validation.
128 lines (127 loc) • 4.4 kB
text/typescript
type Prettify<T> = { [K in keyof T] : T[K] } & {};
type ExtractPositionalType<T> = T extends PositionalSchema<infer U> ? U : never;
type ExtractPositionalTypes<T extends readonly PositionalSchema<any>[]> = { readonly [K in keyof T] : ExtractPositionalType<T[K]> };
type SchemaType = "string" | "number" | "boolean" | "array" | "object";
interface BaseSchema<T = any> {
_type: SchemaType;
_output: T;
_input: unknown;
parse(value: unknown, path?: string): T;
optional(): Schema<T | undefined>;
default(value: T): Schema<T>;
transform<U>(fn: (value: T) => U): Schema<U>;
describe(description: string): this;
alias(alias: string): this;
example(example: string): this;
_description?: string;
_alias?: string;
_example?: string;
_isOptional?: boolean;
_defaultValue?: T;
}
type Schema<T = any> = BaseSchema<T>;
interface StringSchemaBase<T extends string = string> extends BaseSchema<T> {
min(length: number): this;
max(length: number): this;
regex(pattern: RegExp, message?: string): this;
choices<const U extends readonly string[]>(choices: U): StringSchema<U[number]>;
_minLength?: number;
_maxLength?: number;
_regex?: {
pattern: RegExp
message?: string
};
_choices?: readonly string[];
}
type StringSchema<T extends string = string> = StringSchemaBase<T>;
interface NumberSchema extends BaseSchema<number> {
min(value: number): this;
max(value: number): this;
int(): this;
positive(): this;
negative(): this;
_min?: number;
_max?: number;
_isInt?: boolean;
_isPositive?: boolean;
_isNegative?: boolean;
}
interface BooleanSchema extends BaseSchema<boolean> {}
interface ArraySchema<T> extends BaseSchema<T[]> {
min(length: number): this;
max(length: number): this;
_itemSchema: Schema<T>;
_minLength?: number;
_maxLength?: number;
}
interface ObjectSchema<T extends Record<string, any>> extends BaseSchema<T> {
_shape: { [K in keyof T] : Schema<T[K]> };
}
interface PositionalSchema<T = string> extends BaseSchema<T> {
_name: string;
}
interface Command<
TOptions extends Record<string, Schema> = any,
TPositionals extends readonly PositionalSchema<any>[] = readonly []
> {
name: string;
description?: string;
usage?: string;
example?: string | string[];
options: TOptions;
positionals?: PositionalSchema<any>[];
action: (args: {
options: Prettify<{ [K in keyof TOptions] : TOptions[K]["_output"] }>
positionals: ExtractPositionalTypes<TPositionals>
}) => void | Promise<void>;
}
interface CommandBuilder<
TOptions extends Record<string, Schema>,
TPositionals extends readonly PositionalSchema<any>[] = readonly []
> {
description(desc: string): this;
usage(usage: string): this;
example(example: string | string[]): this;
positional<T>(name: string, schema?: Schema<T>): CommandBuilder<TOptions, [...TPositionals, PositionalSchema<T>]>;
action(fn: (args: {
options: Prettify<{ [K in keyof TOptions] : TOptions[K]["_output"] }>
positionals: ExtractPositionalTypes<TPositionals>
}) => void | Promise<void>): Command<TOptions, TPositionals>;
}
interface CLI<
TOptions extends Record<string, Schema> = Record<string, never>,
TPositionals extends readonly PositionalSchema<any>[] = readonly []
> {
name(name: string): this;
version(version: string): this;
description(description: string): this;
usage(usage: string): this;
example(example: string | string[]): this;
option<
K extends string,
S extends Schema
>(name: K, schema: S): CLI<TOptions & { [P in K] : S }, TPositionals>;
positional<T>(name: string, schema?: Schema<T>): CLI<TOptions, [...TPositionals, PositionalSchema<T>]>;
command<T extends Record<string, Schema> = Record<string, never>>(name: string, options?: T): CommandBuilder<T>;
parse(argv?: string[]): {
options: Prettify<{ [K in keyof TOptions] : TOptions[K]["_output"] }>
positionals: ExtractPositionalTypes<TPositionals>
} | undefined;
_name?: string;
_version?: string;
_description?: string;
_usage?: string;
_examples?: string[];
_options: TOptions;
_positionals?: PositionalSchema<any>[];
_commands: Command<any, any>[];
}
declare const z: {
string: () => StringSchema
number: () => NumberSchema
boolean: () => BooleanSchema
array: <T>(schema: Schema<T>) => ArraySchema<T>
object: <T extends Record<string, Schema>>(shape: T) => ObjectSchema<{ [K in keyof T] : T[K]["_output"] }>
};
declare function cli(): CLI<Record<string, never>, readonly []>;
export { z, cli };