From 87538f29e1b838fefc735bd434120f101d208811 Mon Sep 17 00:00:00 2001 From: Federico Coppede Date: Mon, 27 Jul 2026 11:59:02 -0300 Subject: [PATCH] docs: prefix legacy V1 API paths in generated OpenAPI (#12491) # Which Problems Are Solved - Generated V1 API reference docs are missing their base path prefixes (e.g. `/admin/v1`, `/auth/v1`, `/management/v1`, `/system/v1`). - Endpoint pages show routes like `/healthz` instead of `/admin/v1/healthz`. This happens because V1 protos still define `base_path` via legacy OpenAPI v2 swagger annotations, while docs generation uses `protoc-gen-connect-openapi`, which ignores those annotations and emits the bare `google.api.http` paths. # How the Problems Are Solved - After OpenAPI generation in `apps/docs/scripts/generate-proto-docs.mjs`, apply a small hardcoded map of the four legacy V1 services and prepend the correct prefix to each path in the generated specs. - The rewrite is idempotent (`startsWith(prefix)`), so already-prefixed paths are left alone. - Because the prefixes are written into the generated OpenAPI files, Fumadocs MDX generation and runtime page rendering stay in sync. # Additional Changes - None. # Additional Context - V1 APIs are legacy and frozen; hardcoding the four known prefixes is simpler and more reliable than dynamically scraping `.proto` files. - Changing the protos or dual-generating with `openapiv2` would be much more invasive for a docs-only issue. - Verified locally for Admin, Auth, Management, and System pages (e.g. `/admin/v1/healthz`, `/management/v1/users/{id}`). --------- Co-authored-by: Livio Spring <9405495+livio-a@users.noreply.github.com> Co-authored-by: Cursor Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- apps/docs/scripts/generate-proto-docs.mjs | 46 ++++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/docs/scripts/generate-proto-docs.mjs b/apps/docs/scripts/generate-proto-docs.mjs index 116c530ef..9281aa783 100644 --- a/apps/docs/scripts/generate-proto-docs.mjs +++ b/apps/docs/scripts/generate-proto-docs.mjs @@ -1,6 +1,6 @@ import fs from 'fs'; import { glob } from 'glob'; -import { join, dirname, resolve } from 'path'; +import { join, dirname, resolve, basename } from 'path'; import { spawn, spawnSync } from 'child_process'; import { fileURLToPath } from 'url'; @@ -13,6 +13,42 @@ const VERSIONS_FILE = join(ROOT_DIR, 'content/versions.json'); const REPO_URL = 'https://github.com/zitadel/zitadel.git'; const PNPM_COMMAND = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'; +// Legacy V1 APIs declare base_path via OpenAPI v2 swagger options that +// protoc-gen-connect-openapi ignores. Prefix paths so fumadocs shows the real URLs. +const V1_PATH_PREFIXES = { + admin: '/admin/v1', + auth: '/auth/v1', + management: '/management/v1', + system: '/system/v1', +}; + +function applyV1PathPrefixes(outputPath) { + for (const [service, prefix] of Object.entries(V1_PATH_PREFIXES)) { + const files = glob.sync(`**/${service}.openapi.json`, { cwd: outputPath, absolute: true, nodir: true }); + for (const file of files) { + try { + const doc = JSON.parse(fs.readFileSync(file, 'utf8')); + if (!doc.paths || Object.keys(doc.paths).length === 0) continue; + + const newPaths = {}; + let changed = false; + for (const [route, operations] of Object.entries(doc.paths)) { + const newRoute = route.startsWith(prefix) ? route : `${prefix}${route}`; + if (newRoute !== route) changed = true; + newPaths[newRoute] = operations; + } + if (!changed) continue; + + doc.paths = newPaths; + fs.writeFileSync(file, JSON.stringify(doc, null, 2)); + console.log(`Applied ${prefix} to ${basename(file)}`); + } catch (e) { + console.warn(`Failed to apply ${prefix} to ${basename(file)}`, e); + } + } + } +} + async function run() { if (!fs.existsSync(VERSIONS_FILE)) { console.error('versions.json not found. Run fetch-docs.mjs first.'); @@ -182,7 +218,13 @@ async function run() { } catch (e) { console.warn(`Failed to clean up OpenAPI specs for ${label}`, e); } - + + try { + applyV1PathPrefixes(outputPath); + } catch (e) { + console.warn(`Failed to apply V1 path prefixes for ${label}`, e); + } + resolvePromise(); } });