/** * Typed, validated access to src/data/*.json. * A schema mismatch throws at import time, so `astro build` fails loudly. */ import { z } from 'astro/zod'; import siteJson from '../data/site.json'; import socialJson from '../data/social.json'; const link = z.object({ label: z.string().min(1), href: z.string().min(1) }); const siteSchema = z.object({ name: z.string(), title: z.string(), description: z.string().max(160), url: z.url(), lang: z.string(), locale: z.string(), titleSeparator: z.string(), author: z.object({ name: z.string(), callsign: z.string(), locator: z.string() }), postsPerPage: z.number().int().positive(), nav: z.array(link).min(1), footer: z.object({ about: z.string(), quickLinksTitle: z.string(), legalTitle: z.string(), socialTitle: z.string(), rss: link, legal: z.array(link), license: z.string(), signoff: z.string(), }), ui: z.object({ skipToContent: z.string(), mainNav: z.string(), footerNav: z.string(), menuOpen: z.string(), menuClose: z.string(), themeToggle: z.string(), newTab: z.string(), home: z.string(), }), }); const socialSchema = z.array( z.object({ id: z.string(), label: z.string(), // Empty string = account not set → hidden in the UI. href: z.union([z.url(), z.literal('')]), icon: z.string().startsWith('tabler:'), handle: z.string().optional(), }), ); function parse(name: string, schema: z.ZodType, data: unknown): T { const result = schema.safeParse(data); if (!result.success) { throw new Error(`src/data/${name} is invalid:\n${z.prettifyError(result.error)}`); } return result.data; } export const site = parse('site.json', siteSchema, siteJson); export const social = parse('social.json', socialSchema, socialJson).filter((s) => s.href); export type Site = z.infer; export type SocialLink = (typeof social)[number];