renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
296 lines (295 loc) • 8.3 kB
JavaScript
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