Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/bombshell-dev/configliere. A statically typed entry-point router for command-line applications
Something went wrong. Try again.
4.0 kB · 175 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176import type { StandardSchemaV1 } from "@standard-schema/spec";
import { validate, ValidationError } from "./validate.ts";import type { Field, Mods, Source } from "./types.ts";import { toSnake } from "ts-case-convert";import { isBoolean } from "./parse-args.ts";
export function field<T>( schema: StandardSchemaV1<T>, ...mod: Mod[]): Field<T> { let mods = mod.reduce((all, current) => { return current(all); }, { argument: false, array: false, } as Mods);
let field: Field<T> = { path: [], required: !!validate(schema, undefined).issues, schema, mods, parse(input) { let sources: Source<T>[] = [];
// collect object sources for (let value of input.values ?? []) { let { issues } = validate(schema, value.value); sources.push({ sourceName: value.name, sourceType: "object", value: value.value as T, issues, }); }
// collect env sources let key = this.path.map((el) => toSnake(el).toUpperCase()).join("_"); for (let env of input.envs ?? []) { let strval = env.value[key]; if (strval === undefined) continue; let value = parseEnvValue(field, strval); let { issues } = validate(schema, value); sources.push({ sourceName: env.name, sourceType: "env", value: value as T, issues, }); }
// pick the best valid source from inputs (last one wins) let winner = sources.findLast((s) => !s.issues); if (winner) { return { ok: true as const, value: winner.value, data: { source: winner, sources }, remainder: input, }; }
// try default if (mods.default !== undefined) { let { issues } = validate(schema, mods.default); let source: Source<T> = { sourceName: "default", sourceType: "default", value: mods.default as T, issues, }; sources.push(source); if (!issues) { return { ok: true as const, value: source.value, data: { source, sources }, remainder: input, }; } }
// try undefined (optional fields) let { issues } = validate(schema, undefined); let source: Source<T> = { sourceName: "none", sourceType: "none", value: undefined as T, issues, };
if (!issues) { sources.push(source); return { ok: true as const, value: source.value, data: { source, sources }, remainder: input, }; }
return { ok: false as const, error: new ValidationError(sources), remainder: input, }; }, }; return field;}
function parseEnvValue<T>(field: Field<T>, value: string): unknown { if (isBoolean(field.schema)) { return parseEnvBoolean(value); } else if (field.mods.array) { return parseEnvArray(field, value); } else if (!isNaN(Number(value))) { return Number(value); } else { return value; }}
function parseEnvBoolean(value: string) { switch (value.toLowerCase().trim()) { case "true": case "yes": case "1": return true; case "false": case "no": case "0": return false; default: return value; }}
function parseEnvArray(field: Field<unknown>, value: string) { return value.split(",").map((s) => s.trim()).map((s) => parseEnvValue(field, s) );}
field.array = (): Mod => { return (field) => ({ ...field, array: true, });};
field.default = <T>(value: T): Mod => { return (mods) => ({ ...mods, default: value, });};
export const cli: CLIMods = { argument(): Mod { return (mods) => ({ ...mods, argument: true, }); },};
export interface CLIMods { argument: () => Mod;}
export interface Mod { (mods: Mods): Mods;}