init: initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
99
scripts/sync-spec.mjs
Normal file
99
scripts/sync-spec.mjs
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
// Fetches the arcadia OpenAPI spec and regenerates ../src/generated/openapi.d.ts.
|
||||
//
|
||||
// Usage (from a consuming app, with arcadia reachable):
|
||||
// node ../lib-arcadia-client/scripts/sync-spec.mjs
|
||||
//
|
||||
// Configurable via env:
|
||||
// ARCADIA_OPENAPI_URL default: http://localhost:4000/api/openapi
|
||||
// ARCADIA_BEARER_TOKEN optional, sent as Authorization if the spec route
|
||||
// is gated in your deployment
|
||||
//
|
||||
// Requires `openapi-typescript` to be available (in the consuming app's
|
||||
// node_modules, or globally). Run from a consuming app so the dep resolves:
|
||||
// npm i -D openapi-typescript
|
||||
|
||||
import { writeFile, mkdir } from "node:fs/promises";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const outDir = resolve(here, "..", "src", "generated");
|
||||
const outFile = resolve(outDir, "openapi.d.ts");
|
||||
const specUrl = process.env.ARCADIA_OPENAPI_URL ?? "http://localhost:4000/api/openapi";
|
||||
|
||||
async function main() {
|
||||
console.log(`→ fetching ${specUrl}`);
|
||||
const headers = {};
|
||||
if (process.env.ARCADIA_BEARER_TOKEN) {
|
||||
headers["Authorization"] = `Bearer ${process.env.ARCADIA_BEARER_TOKEN}`;
|
||||
}
|
||||
const res = await fetch(specUrl, { headers });
|
||||
if (!res.ok) {
|
||||
console.error(`spec fetch failed: ${res.status} ${res.statusText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const spec = await res.json();
|
||||
|
||||
// Resolve openapi-typescript from the consuming app's node_modules, since
|
||||
// the lib has no package.json of its own.
|
||||
let openapiTs;
|
||||
let astToString;
|
||||
try {
|
||||
const requireFromCwd = createRequire(resolve(process.cwd(), "package.json"));
|
||||
const resolved = requireFromCwd.resolve("openapi-typescript");
|
||||
const mod = await import(pathToFileURL(resolved).href);
|
||||
// Handle both ESM (mod.default is the function) and CJS-via-import
|
||||
// (mod.default is the namespace, mod.default.default is the function).
|
||||
openapiTs = typeof mod.default === "function" ? mod.default : mod.default?.default;
|
||||
astToString = mod.astToString ?? mod.default?.astToString;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`openapi-typescript could not be resolved from ${process.cwd()}.\n` +
|
||||
`Run from your consuming app's directory after installing it:\n` +
|
||||
` cd <app> && npm i -D openapi-typescript\n` +
|
||||
`Original error: ${err.message}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Scrub malformed operation entries. Arcadia's spec contains some
|
||||
// endpoints whose operation value is the bare string "ok" (a controller
|
||||
// placeholder that never got replaced with a real OperationObject).
|
||||
// openapi-typescript can't transform them — strip them and report so
|
||||
// the generation succeeds for the rest. Fix is on the arcadia side.
|
||||
const skipped = [];
|
||||
if (spec.paths) {
|
||||
for (const [path, item] of Object.entries(spec.paths)) {
|
||||
if (!item || typeof item !== "object") continue;
|
||||
for (const [verb, op] of Object.entries(item)) {
|
||||
if (typeof op !== "object" || op === null) {
|
||||
skipped.push(`${verb.toUpperCase()} ${path}`);
|
||||
delete item[verb];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skipped.length) {
|
||||
console.warn(`⚠ skipped ${skipped.length} malformed operations (arcadia spec bug):`);
|
||||
for (const s of skipped.slice(0, 10)) console.warn(` - ${s}`);
|
||||
if (skipped.length > 10) console.warn(` …and ${skipped.length - 10} more`);
|
||||
}
|
||||
|
||||
console.log(`→ generating types`);
|
||||
const ast = await openapiTs(spec);
|
||||
const dts = astToString(ast);
|
||||
await mkdir(outDir, { recursive: true });
|
||||
const banner =
|
||||
"// AUTO-GENERATED — do not edit by hand. Regenerate with sync-spec.mjs.\n" +
|
||||
`// Source: ${specUrl}\n` +
|
||||
`// Generated: ${new Date().toISOString()}\n\n`;
|
||||
await writeFile(outFile, banner + dts, "utf8");
|
||||
console.log(`✓ wrote ${outFile}`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user