|
import { z } from 'zod'; |
|
|
|
import { defineOptions } from './define-options.ts'; |
|
import type { |
|
DefineCommandConfig, |
|
DefineOptionsConfig, |
|
ParseConfig, |
|
ResultData, |
|
SafeFailure, |
|
SafeSuccess, |
|
StandardDefineReturn, |
|
} from './types.ts'; |
|
|
|
export function defineCommandRaw<T extends z.ZodObject<z.ZodRawShape>>( |
|
config: DefineCommandConfig<T>, |
|
): DefineCommandConfig<T> { |
|
return config; |
|
} |
|
|
|
export function defineCommand< |
|
T extends z.ZodObject<z.ZodRawShape>, |
|
CommandArgs extends z.ZodTypeAny[] = z.ZodTypeAny[], |
|
>({ |
|
name, |
|
// description, |
|
args, |
|
aliases, |
|
options, |
|
action, |
|
}: DefineCommandConfig<T>): StandardDefineReturn<T> { |
|
const issues: any[] = []; |
|
const ctx = { addIssue: (issue: any) => issues.push(issue) }; |
|
|
|
return { |
|
run, |
|
parse, |
|
parseAsync, |
|
safeParse, |
|
safeParseAsync, |
|
}; |
|
|
|
// TODO 1: use `allowUnknown`, maybe allow unknown commands to call the `action` function |
|
// TODO 2: add runAsync, to call `action` with await; use `runAsync` in `*parseAsync` methods |
|
function run(argv?: string[] | null, config?: ParseConfig) { |
|
const cfg = { allowUnknown: false, safe: false, ...config }; |
|
argv = argv || process.argv.slice(2); |
|
|
|
const commandName = argv.shift() || ''; |
|
const noCommand = commandName !== name && !aliases?.includes(commandName); |
|
|
|
if (noCommand) { |
|
ctx.addIssue({ |
|
code: z.ZodIssueCode.custom, |
|
message: `No such command: ${commandName}`, |
|
path: [commandName], |
|
}); |
|
|
|
if (!cfg.safe) { |
|
throw new z.ZodError(issues); |
|
} |
|
|
|
return { success: false, error: new z.ZodError(issues) } as SafeFailure<T>; |
|
} |
|
|
|
const commandIndex = argv.findIndex((arg) => arg.startsWith('-')); |
|
const commandArgs = (commandIndex === -1 ? argv : argv?.slice(0, commandIndex + 1)) || []; |
|
|
|
// Parse options |
|
const cmdOptions = defineOptions( |
|
(options || { schema: z.object({}) }) as DefineOptionsConfig<T>, |
|
); |
|
const parseMethod = cfg.safe ? cmdOptions.safeParse : cmdOptions.parse; |
|
const cmdOptsResult = parseMethod(argv.slice(0, commandIndex), config); |
|
|
|
// Validate and parse args |
|
const parsedArgs = |
|
args?.map((argSchema, index) => { |
|
const argValue = commandArgs[index]; |
|
|
|
// TODO: add support for `safeParse` |
|
return argSchema.parse(argValue); |
|
}) || []; |
|
|
|
const cmdArgs = parsedArgs as { [K in keyof CommandArgs]: z.infer<CommandArgs[K]> }; |
|
|
|
if (cfg.safe) { |
|
const optResult = cmdOptsResult as SafeSuccess<T> | SafeFailure<T>; |
|
|
|
if (optResult.success) { |
|
// ?NOTE: TypeScript tricks for types |
|
const opts = optResult.data.options as z.infer<typeof optResult.data.schema>; |
|
|
|
// TODO: support returning this `result` along the `options` and `schema` |
|
const _result = action(opts, cmdArgs); |
|
|
|
return { |
|
success: true, |
|
data: { options: opts, schema: optResult.data.schema }, |
|
} as SafeSuccess<T>; |
|
} |
|
|
|
// ?NOTE: TypeScript tricks for types |
|
const werrSchema = optResult.error.schema; |
|
(optResult.error as any).schema = werrSchema; |
|
|
|
return { success: false, error: optResult.error } as SafeFailure<T>; |
|
} |
|
|
|
// return { success: false, error: { message: 'not implemented' } }; |
|
const optResult = cmdOptsResult as ResultData<T>; |
|
const opts = optResult.options; |
|
|
|
// TODO: support returning this `result` along the `options` and `schema` |
|
const _result = action(opts, cmdArgs); |
|
|
|
return { options: opts, schema: optResult.schema } as ResultData<T>; |
|
} |
|
|
|
function parse(argv?: string[] | null, config?: ParseConfig): ResultData<T> { |
|
return run(argv, { ...config, safe: false }) as ResultData<T>; |
|
} |
|
|
|
async function parseAsync(argv?: string[] | null, config?: ParseConfig): Promise<ResultData<T>> { |
|
return run(argv, { ...config, safe: false }) as ResultData<T>; |
|
} |
|
|
|
function safeParse( |
|
argv?: string[] | null, |
|
config?: ParseConfig, |
|
): SafeSuccess<T> | SafeFailure<T> { |
|
return run(argv, { ...config, safe: true }) as SafeSuccess<T> | SafeFailure<T>; |
|
} |
|
|
|
async function safeParseAsync( |
|
argv?: string[] | null, |
|
config?: ParseConfig, |
|
): Promise<SafeSuccess<T> | SafeFailure<T>> { |
|
return run(argv, { ...config, safe: true }) as SafeSuccess<T> | SafeFailure<T>; |
|
} |
|
} |