import { z } from 'zod'; const safeRelativePathSchema = z .string() .min(1) .max(200) .refine((value) => !value.startsWith('/') && !value.startsWith('\\'), { message: 'Path must be relative to the project root', }) .refine((value) => !value.split(/[\\/]+/).includes('..'), { message: 'Path must not contain parent directory segments', }); // Healthcheck Schema export const healthcheckSchema = z.object({ acceptableStatusCodes: z.array(z.number().int().min(100).max(599)).min(1).default([200]), timeoutMs: z.number().int().positive().default(5000), }).strict(); // Service Schema export const serviceSchema = z.object({ id: z.string().min(1).max(50), name: z.string().min(1).max(100), description: z.string().min(1).max(500), url: z.string().url(), monitorUrl: z.string().url(), category: z.string().min(1).max(50), healthcheck: healthcheckSchema.optional(), }).strict(); // App Config Schema export const appConfigSchema = z.object({ title: z.string().min(1).max(100), subtitle: z.string().min(1).max(200), description: z.string().min(1).max(500), servicesFile: safeRelativePathSchema, refreshInterval: z.number().int().min(1000).max(300000).default(30000), // 1s to 5min categories: z.array(z.string().min(1).max(50)).min(1), theme: z.enum(['light', 'dark', 'auto']).default('auto'), loggingLevel: z.enum(['error', 'warn', 'info', 'debug']).default('info'), }).strict(); // Services Array Schema export const servicesArraySchema = z.array(serviceSchema).superRefine((services, ctx) => { const ids = new Set(); services.forEach((service, index) => { if (ids.has(service.id)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Duplicate service id: ${service.id}`, path: [index, 'id'], }); return; } ids.add(service.id); }); }); // Type exports export type Service = z.infer; export type Healthcheck = z.infer; export type AppConfig = z.infer; export type Services = z.infer;