import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { type Schema } from "effect"; import { toJsonSchemaObject } from "@t3tools/shared/schemaJson"; const JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"; export function buildJsonSchemaDocument( schema: Schema.Top, options: { readonly title: string; readonly description: string; }, ): Record { const jsonSchema = toJsonSchemaObject(schema); if (!jsonSchema || typeof jsonSchema !== "object" || Array.isArray(jsonSchema)) { throw new Error( `${options.title} JSON schema must be an object or array JSON schema document.`, ); } return { $schema: JSON_SCHEMA_DRAFT_2020_12, title: options.title, description: options.description, ...jsonSchema, }; } export const asJsonSchemaRecord = (value: unknown): Record | null => value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : null; export const getJsonSchemaProperty = ( schema: Record, propertyName: string, ): Record | null => { const properties = asJsonSchemaRecord(schema.properties); return properties ? asJsonSchemaRecord(properties[propertyName]) : null; }; export const getNullableJsonSchemaBranch = ( schema: Record | null, ): Record | null => { if (!schema) { return null; } const anyOf = Array.isArray(schema.anyOf) ? schema.anyOf : null; if (!anyOf) { return schema; } for (const entry of anyOf) { const branch = asJsonSchemaRecord(entry); if (!branch || branch.type === "null") { continue; } return branch; } return null; }; export const getJsonSchemaAnyOfBranches = ( schema: Record | null, ): ReadonlyArray> => { if (!schema || !Array.isArray(schema.anyOf)) { return []; } return schema.anyOf .map(asJsonSchemaRecord) .filter( (branch): branch is Record => branch !== null && branch.type !== "null", ); }; export const setJsonSchemaDescription = ( schema: Record | null, description: string, ): void => { if (!schema) { return; } schema.description = description; }; function writeJsonFileIfChanged(filePath: string, document: Record): boolean { const nextContent = `${JSON.stringify(document, null, 2)}\n`; const previousContent = (() => { try { return readFileSync(filePath, "utf8"); } catch { return null; } })(); if (previousContent === nextContent) { return false; } mkdirSync(dirname(filePath), { recursive: true }); writeFileSync(filePath, nextContent); return true; } export function writeJsonSchemaArtifacts(options: { readonly rootDir?: string; readonly version?: string; readonly latestRelativePath: string; readonly getVersionedRelativePath: (version: string) => string; readonly document: Record; }): { readonly changed: boolean; } { const rootDir = resolve(options.rootDir ?? process.cwd()); let changed = writeJsonFileIfChanged( resolve(rootDir, options.latestRelativePath), options.document, ); if (options.version) { changed = writeJsonFileIfChanged( resolve(rootDir, options.getVersionedRelativePath(options.version)), options.document, ) || changed; } return { changed }; }