import * as path from 'path' import * as CLI from 'yargs' import { z, ZodTypeAny, ZodObject, ZodEffects, ZodOptional, ZodDefault } from 'zod' import { sync as writeFS } from '@plastichub/fs/write' import { zodToTs, printNode } from 'zod-to-ts' import { zodToJsonSchema } from "zod-to-json-schema" import { logger } from '../logger' type InnerType = T extends ZodEffects ? InnerType : T type GetInnerType = T extends ZodObject ? T : T extends ZodEffects> ? InnerType : never; export * from './path' export const generate_interfaces = (schemas: ZodObject[], dst: string) => { const types = schemas.map(schema => `export interface ${schema.description || 'IOptions'} ${printNode(zodToTs(schema).node)}`) writeFS(dst, types.join('\n')) } export const enumerateHelpStrings = (schema: ZodTypeAny, path: string[] = [], logger: any): void => { if (schema instanceof ZodObject) { for (const key in schema.shape) { const nestedSchema = schema.shape[key]; enumerateHelpStrings(nestedSchema, [...path, key], logger) } } else { const description = schema._def.description; if (description) { logger.debug(`\t ${path.join('.')}: ${description}`) } } } export const yargsDefaults = (yargs: CLI.Argv) => yargs.parserConfiguration({ "camel-case-expansion": false }) export const getInnerSchema = (schema: ZodTypeAny): ZodTypeAny => { while (schema instanceof ZodEffects) { schema = schema._def.schema } return schema } export const getInnerType = (type: ZodTypeAny) => { while (type instanceof ZodOptional) { type = type._def.innerType } while (type._def.typeName === 'ZodDefault' || type._def.typeName === 'ZodOptional') { type = type._def.innerType; } return type._def.typeName } export const getDefaultValue = (schema: ZodTypeAny) => { if (schema instanceof ZodDefault) { return schema._def.defaultValue(); } return undefined; } export const getFieldDefaultValue = (schema: ZodTypeAny): any | undefined => { if(!schema){ return undefined } if (schema._def.typeName === 'ZodDefault') { return schema._def.defaultValue(); } if (schema instanceof ZodOptional) { return getFieldDefaultValue(schema.unwrap()); } if (schema instanceof ZodEffects) { return getFieldDefaultValue(schema._def.schema); } if(typeof schema._def){ return getFieldDefaultValue(schema._def.schema) } return undefined; } export const getDescription = (schema: ZodTypeAny): string | undefined =>{ if(!schema){ return undefined } if (schema._def.description) { return schema._def.description; } if (schema instanceof ZodOptional) { return getDescription(schema.unwrap()); } if (schema instanceof ZodEffects) { return getDescription(schema._def.schema); } if(typeof schema._def){ return getDescription(schema._def.schema) } return undefined; } export const toYargs = (yargs: CLI.Argv, zodSchema: ZodObject, options?: { onKey?: (yargs: CLI.Argv, key: string, options:any) => any }) => { yargsDefaults(yargs) try { const shape = zodSchema.shape for (const key in shape) { const zodField = shape[key] as ZodTypeAny const innerDef = getInnerSchema(zodField) if (!innerDef) { continue } let type: 'string' | 'boolean' | 'number' | undefined; const inner_type = getInnerType(innerDef) let descriptionExtra = '' switch (inner_type) { case 'ZodString': type = 'string' break case 'ZodBoolean': type = 'boolean' break case 'ZodNumber': type = 'number' break case 'ZodOptional': case 'ZodEnum': type = getInnerType(innerDef) if (innerDef._def.typeName === 'ZodEnum') { descriptionExtra = `\n\t ${innerDef._def.values.join(' \n\t ')}` } break } const defaultValue = getFieldDefaultValue(zodField) let handled = false const args = { type, default: defaultValue, describe: `${zodField._def.description || ''} ${descriptionExtra}`.trim() } if(options?.onKey){ handled = options.onKey(yargs, key, args) } if(!handled){ yargs.option(key,args) } } return yargs } catch (error) { logger.error('Error processing schema:', error) return yargs } } ///////////////////////////////////////////////////////// // // Schema Writers // const extension = (file: string) => path.parse(file).ext const json = (data: any, file: string, name: string, options: {}) => writeFS(file, data.map((s) => zodToJsonSchema(s, name))) export const WRITERS = { '.json': json } export const writer = (file: string) => WRITERS[extension(file)] export const write = (schemas: ZodObject[], file: string, name: string, options: {}) => { if (!WRITERS[extension(file)]) { logger.error(`No writer found for file extension: ${extension(file)} : file: ${file}`) return } logger.debug(`Writing schema to ${file} : ${name}`) try { writer(file)(schemas, file, name, options) } catch (e) { logger.trace(`Error writing schema to ${file} : ${name}`, e, e.stack, e.message) } } //////////////////////////////////////////////////////////////////// // // Schema Combinators export const combineValidatorsOr = (validators: z.ZodTypeAny[]) => { return z.string().refine((value) => { const errors = []; const isValid = validators.some((validator) => { try { validator.parse(value) return true; } catch (err) { errors.push(err.errors) return false; } }); if (!isValid) { throw new z.ZodError(errors.flat()) } return true; }, 'Invalid value for all provided validators') } export const combineValidatorsOrUsingZod = (validators: z.ZodTypeAny[]) => { return validators.reduce((acc, validator) => acc.or(validator)); }; export const combineValidatorsOrUsingZod2 = (validators: z.ZodTypeAny[]) => { return validators.reduce((acc, validator) => { return acc.or(validator).refine((value) => { try { acc.parse(value); return true; } catch (errAcc) { try { validator.parse(value); return true; } catch (errValidator) { throw new z.ZodError([...errAcc.errors, ...errValidator.errors]); } } }) }) } export * from './zod_map'