/** * Fetches the OpenAPI spec from the running stemedb-api server * and saves it to public/openapi.json for use by Scalar. * * Usage: * npx tsx scripts/fetch-openapi.ts * * Set STEMEDB_API_URL to override the default endpoint. */ import { writeFileSync, existsSync, readFileSync } from "fs"; import { join } from "path"; const API_URL = process.env.STEMEDB_API_URL || "http://127.0.0.1:18180"; const OPENAPI_ENDPOINT = `${API_URL}/api-docs/openapi.json`; const OUTPUT_PATH = join(process.cwd(), "public", "openapi.json"); async function fetchOpenApiSpec(): Promise { console.log(`Fetching OpenAPI spec from ${OPENAPI_ENDPOINT}...`); try { const response = await fetch(OPENAPI_ENDPOINT); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const spec = await response.json(); // Validate it looks like an OpenAPI spec if (!spec.openapi || !spec.info || !spec.paths) { throw new Error("Invalid OpenAPI spec: missing required fields"); } writeFileSync(OUTPUT_PATH, JSON.stringify(spec, null, 2)); console.log(`OpenAPI spec saved to ${OUTPUT_PATH}`); console.log(` Version: ${spec.info.version}`); console.log(` Title: ${spec.info.title}`); console.log(` Endpoints: ${Object.keys(spec.paths).length}`); } catch (error) { if (error instanceof Error && error.message.includes("ECONNREFUSED")) { console.warn("Could not connect to stemedb-api server."); console.warn("Using existing spec if available, or provide a bundled fallback."); if (existsSync(OUTPUT_PATH)) { const existing = JSON.parse(readFileSync(OUTPUT_PATH, "utf-8")); console.log(`Using cached spec (v${existing.info?.version || "unknown"})`); return; } console.error("No cached spec available. Start stemedb-api and retry."); process.exit(1); } console.error("Failed to fetch OpenAPI spec:", error); process.exit(1); } } fetchOpenApiSpec();