Skip to content

Instantly share code, notes, and snippets.

@yorhodes
Last active May 22, 2024 23:03
Show Gist options
  • Save yorhodes/21a5a86824eaa92d5f88450e393b7e87 to your computer and use it in GitHub Desktop.
Save yorhodes/21a5a86824eaa92d5f88450e393b7e87 to your computer and use it in GitHub Desktop.
Parses a zod schema from stdin via inquirer prompts
import { checkbox, confirm, input, select } from '@inquirer/prompts';
import { z } from 'zod';
export function promptBoolean<S extends z.ZodBoolean>(
_schema: S,
options: Parameters<typeof confirm>[0],
): () => Promise<boolean> {
return () => confirm(options);
}
export function promptString<S extends z.ZodString | z.ZodNumber>(
schema: S,
options: Parameters<typeof input>[0],
): () => Promise<string> {
return () =>
input({
validate: (d) => schema.safeParse(d).success,
...options,
});
}
export function promptNumber<S extends z.ZodNumber>(
schema: S,
options: Parameters<typeof input>[0],
): () => Promise<number> {
return () => promptString(schema, options)().then((v) => parseInt(v, 10));
}
export function promptEnum<E extends z.EnumLike, S extends z.ZodNativeEnum<E>>(
schema: S,
options: Omit<Parameters<typeof select>[0], 'choices'>,
glossary: Partial<Record<keyof E, string>> = {},
): () => Promise<z.infer<S>> {
const choices = Object.entries(schema.enum).map(([key, value]) => ({
name: key, // is this right?
value: value as E[keyof E],
description: glossary[key],
}));
return () =>
select({
...options,
choices,
});
}
export function promptMultipleEnum<
E extends z.EnumLike,
S extends z.ZodNativeEnum<E>,
>(
schema: S,
options: Omit<Parameters<typeof checkbox>[0], 'choices'>,
glossary: Partial<Record<keyof E, string>> = {},
): () => Promise<E[keyof E][]> {
const choices = Object.entries(schema.enum).map(([key, value]) => ({
name: key, // is this right?
value: value as E[keyof E],
description: glossary[key],
}));
return () =>
checkbox({
...options,
choices,
});
}
export function promptObject<S extends z.ZodObject<any>>(
schema: S,
maxDepth = 10,
): () => Promise<z.infer<S>> {
return async () => {
let result: Record<string, unknown> = {};
for (const [key, value] of Object.entries<z.ZodTypeAny>(schema.shape)) {
const description = value.description ?? key;
result[key] = await promptAny(
value.describe(description),
maxDepth - 1,
)();
}
return schema.parse(result);
};
}
export function promptArray<S extends z.ZodArray<any>>(
schema: S,
maxDepth = 10,
): () => Promise<z.infer<S>> {
return async () => {
const result: z.infer<S> = [];
const length = await promptNumber(z.number(), {
message: 'Enter the number of elements',
})();
const elemSchema = schema.element.describe(
schema.element.description ?? schema.description,
);
for (let i = 0; i < length; i++) {
const element = await promptAny(elemSchema, maxDepth - 1)();
result.push(element);
}
return schema.parse(result);
};
}
export function promptAny<S extends z.ZodTypeAny>(
schema: S,
maxDepth = 10,
): () => Promise<z.infer<S>> {
if (maxDepth <= 0) throw new Error('Max depth exceeded');
if (!schema.description) throw new Error('No schema description provided');
if (schema instanceof z.ZodBoolean) {
return promptBoolean(schema, { message: schema.description });
} else if (schema instanceof z.ZodString) {
return promptString(schema, { message: schema.description });
} else if (schema instanceof z.ZodNumber) {
return promptNumber(schema, { message: schema.description });
} else if (schema instanceof z.ZodNativeEnum) {
return promptEnum(schema, { message: schema.description });
} else if (schema instanceof z.ZodObject) {
return promptObject(schema, maxDepth - 1);
} else if (schema instanceof z.ZodArray) {
return promptArray(schema, maxDepth - 1);
}
throw new Error(`Unsupported schema type ${schema}`);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment