UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

296 lines (295 loc) 8.3 kB
import { logger } from "../../logger/index.js"; import { parseJsonc } from "../common.js"; import { parse } from "../toml.js"; import { parseSingleYaml, parseYaml } from "../yaml.js"; import { DateTime } from "luxon"; import { z } from "zod/v4"; import JSON5 from "json5"; import ini from "ini"; //#region lib/util/schema-utils/index.ts /** * Works like `z.array()`, but drops wrong elements instead of invalidating the whole array. * * **Important**: non-array inputs are still invalid. * Use `LooseArray(...).catch([])` to handle it. * * @param Elem Schema for array elements * @param onError Callback for errors * @returns Schema for array */ function LooseArray(Elem, { onError } = {}) { if (!onError) return z.array(z.any()).transform((input) => { const output = []; for (const x of input) { const parsed = Elem.safeParse(x); if (parsed.success) output.push(parsed.data); } return output; }); return z.array(z.any()).transform((input) => { const output = []; const issues = []; for (let idx = 0; idx < input.length; idx += 1) { const x = input[idx]; const parsed = Elem.safeParse(x); if (parsed.success) { output.push(parsed.data); continue; } for (const issue of parsed.error.issues) issues.push({ ...issue, path: [idx, ...issue.path] }); } if (issues.length) onError({ error: new z.ZodError(issues), input }); return output; }); } function LooseRecord(arg1, arg2, arg3) { let Key = z.any(); let Value; let opts = {}; if (arg2 && arg3) { Key = arg1; Value = arg2; opts = arg3; } else if (arg2) if (arg2 instanceof z.ZodType) { Key = arg1; Value = arg2; } else { Value = arg1; opts = arg2; } else Value = arg1; const { onError } = opts; if (!onError) return z.record(z.string(), z.any()).transform((input) => { const output = {}; for (const [inputKey, inputVal] of Object.entries(input)) { const parsedKey = Key.safeParse(inputKey); const parsedValue = Value.safeParse(inputVal); if (parsedKey.success && parsedValue.success) output[parsedKey.data] = parsedValue.data; } return output; }); return z.record(z.string(), z.any()).transform((input) => { const output = {}; const issues = []; for (const [inputKey, inputVal] of Object.entries(input)) { const parsedKey = Key.safeParse(inputKey); if (!parsedKey.success) { for (const issue of parsedKey.error.issues) issues.push({ ...issue, path: [inputKey, ...issue.path] }); continue; } const parsedValue = Value.safeParse(inputVal); if (!parsedValue.success) { for (const issue of parsedValue.error.issues) issues.push({ ...issue, path: [inputKey, ...issue.path] }); continue; } output[parsedKey.data] = parsedValue.data; } if (issues.length) onError({ error: new z.ZodError(issues), input }); return output; }); } /** * Accepts `null`, `undefined`, or an absent key and normalizes all to * `undefined`. Keeps the output type as `T | undefined` (never `null`), so * assignment into non-nullable targets needs no `?? undefined` coalescing. */ function Nullish(schema) { return schema.nullable().transform((value) => value ?? void 0).optional(); } function deepNullishRewrite(node) { if (node instanceof z.ZodOptional) return Nullish(deepNullishRewrite(node.unwrap())); if (node instanceof z.ZodObject) { const shape = {}; for (const [key, value] of Object.entries(node.shape)) shape[key] = deepNullishRewrite(value); return z.object(shape); } if (node instanceof z.ZodArray) return z.array(deepNullishRewrite(node.element)); if (node instanceof z.ZodRecord) return z.record(node.keyType, deepNullishRewrite(node.valueType)); if (node instanceof z.ZodUnion) { const options = node.options; return z.union(options.map(deepNullishRewrite)); } if (node instanceof z.ZodDefault) return deepNullishRewrite(node.unwrap()).default(node.def.defaultValue); if (node instanceof z.ZodNullable) return z.nullable(deepNullishRewrite(node.unwrap())); if (node instanceof z.ZodPipe) return z.pipe(node.def.in, deepNullishRewrite(node.def.out)); return node; } /** * Walks `schema` at build time and replaces every `.optional()` position with * `Nullish` semantics — accepting `null`/`undefined`/absent and normalizing all * to `undefined`. Recurses through `object`, `array`, `record`, `union`, * `default`, `nullable`, and the **output side** of `pipe`/transform nodes * (e.g. `Json.pipe(…)`). * * Intentional `.nullable()` fields are preserved (null kept), distinguishing * this from a blunt null-stripping preprocessor. * * The pipe **input** side (the decoder, e.g. `Json`) is left untouched. * **`LooseArray`/`LooseRecord` remain opaque** because their element schema is * captured in a closure — wrap the inner schema directly for those * (e.g. `LooseArray(DeepNullish(Inner))`). * * Object modifiers (`.strict()`/`.catchall()`/`.passthrough()`/object-level * `.refine()`) are dropped by the `z.object(shape)` rebuild. */ function DeepNullish(schema) { return deepNullishRewrite(schema); } const Json = z.string().transform((str, ctx) => { try { return JSON.parse(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid JSON" }); return z.NEVER; } }); const Json5 = z.string().transform((str, ctx) => { try { return JSON5.parse(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid JSON5" }); return z.NEVER; } }); const Jsonc = z.string().transform((str, ctx) => { try { return parseJsonc(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid JSONC" }); return z.NEVER; } }); const UtcDate = z.string().describe("ISO 8601 string").transform((str, ctx) => { const date = DateTime.fromISO(str, { zone: "utc" }); if (!date.isValid) { ctx.addIssue({ code: "custom", message: "Invalid date" }); return z.NEVER; } return date; }); const Yaml = z.string().transform((str, ctx) => { try { return parseSingleYaml(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid YAML" }); return z.NEVER; } }); z.string().transform((str, ctx) => { try { return parseYaml(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid YAML" }); return z.NEVER; } }); function multidocYaml(opts) { return z.string().transform((str, ctx) => { try { return parseYaml(str, opts); } catch { ctx.addIssue({ code: "custom", message: "Invalid YAML" }); return z.NEVER; } }); } const Toml = z.string().transform((str, ctx) => { try { return parse(str); } catch { ctx.addIssue({ code: "custom", message: "Invalid TOML" }); return z.NEVER; } }); const Ini = z.string().transform((str, ctx) => { try { return ini.parse(str); } catch /* v8 ignore next -- TODO: add test #40625 */ { ctx.addIssue({ code: "custom", message: "Invalid INI" }); return z.NEVER; } }); function withDepType(schema, depType, force = true) { return schema.transform((deps) => { for (const dep of deps) if (!dep.depType || force) dep.depType = depType; return deps; }); } function withDebugMessage(value, msg) { return ({ error: err }) => { logger.debug({ err }, msg); return value; }; } function isCircular(value, visited = /* @__PURE__ */ new Set()) { if (value === null || typeof value !== "object") return false; if (visited.has(value)) return true; const downstreamVisited = new Set(visited); downstreamVisited.add(value); if (Array.isArray(value)) { for (const childValue of value) if (isCircular(childValue, downstreamVisited)) return true; return false; } const values = Object.values(value); for (const ov of values) if (isCircular(ov, downstreamVisited)) return true; return false; } const NotCircular = z.unknown().superRefine((val, ctx) => { if (isCircular(val)) { ctx.addIssue({ code: "custom", message: "values cannot be circular data structures", fatal: true }); return z.NEVER; } }); const EmailAddress = z.email(); function isEmailAdress(value) { return EmailAddress.safeParse(value).success; } //#endregion export { DeepNullish, EmailAddress, Ini, Json, Json5, Jsonc, LooseArray, LooseRecord, NotCircular, Toml, UtcDate, Yaml, isEmailAdress, multidocYaml, withDebugMessage, withDepType }; //# sourceMappingURL=index.js.map