import { existsSync, readdirSync, readFileSync } from "node:fs"; import { basename, join } from "node:path"; import { type ArticleSample, type LoadedArticleSamples, supportedPlatforms, type SamplePlatform, } from "./types"; interface RawSample { name?: unknown; input?: { title?: unknown; body?: unknown; image_lines?: unknown; platform?: unknown; user_instructions?: unknown; }; expectedHardFailures?: unknown; expectedWarnings?: unknown; } export function loadArticleSamples(samplesDir: string): LoadedArticleSamples { if (!existsSync(samplesDir)) { return { valid: [], invalid: [] }; } const files = readdirSync(samplesDir) .filter((fileName) => fileName.endsWith(".json")) .sort((left, right) => left.localeCompare(right)); const valid: ArticleSample[] = []; const invalid: LoadedArticleSamples["invalid"] = []; for (const fileName of files) { const filePath = join(samplesDir, fileName); try { const raw = JSON.parse(readFileSync(filePath, "utf8")) as RawSample; const sample = normalizeSample(raw, filePath, fileName); valid.push(sample); } catch (error) { invalid.push({ filePath, fileName, reason: error instanceof Error ? error.message : "sample could not be parsed", }); } } return { valid, invalid }; } function normalizeSample( raw: RawSample, filePath: string, fileName: string, ): ArticleSample { if (!raw || typeof raw !== "object") { throw new Error("sample must be a JSON object"); } if (!raw.input || typeof raw.input !== "object") { throw new Error("input must be an object"); } if (typeof raw.input.body !== "string" || raw.input.body.trim().length === 0) { throw new Error("input.body must be a non-empty string"); } if (!isSupportedPlatform(raw.input.platform)) { throw new Error( `input.platform must be one of ${supportedPlatforms.join(", ")}`, ); } return { filePath, fileName, slug: slugFromFileName(fileName), name: typeof raw.name === "string" && raw.name.trim().length > 0 ? raw.name.trim() : basename(fileName, ".json"), input: { title: stringOrEmpty(raw.input.title), body: raw.input.body, image_lines: stringOrEmpty(raw.input.image_lines), platform: raw.input.platform, user_instructions: stringOrEmpty(raw.input.user_instructions), }, expectedHardFailures: stringArrayOrEmpty(raw.expectedHardFailures), expectedWarnings: stringArrayOrEmpty(raw.expectedWarnings), }; } function isSupportedPlatform(value: unknown): value is SamplePlatform { return ( typeof value === "string" && supportedPlatforms.includes(value as SamplePlatform) ); } function stringOrEmpty(value: unknown) { return typeof value === "string" ? value : ""; } function stringArrayOrEmpty(value: unknown) { return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : []; } function slugFromFileName(fileName: string) { return basename(fileName, ".json") .toLowerCase() .replace(/[^a-z0-9-]+/g, "-") .replace(/^-+|-+$/g, ""); }