docs(services): derive nav from @hanzo/products catalog

Wire the services 'All Services' nav to the shared @hanzo/products catalog:
- add scripts/gen-services-nav.ts (guarded generator, run in build:pre)
- regenerate meta.json under canonical console categories
- add @hanzo/products dep

Recovered nav work from the registry pass; base for the OSS-docs fork.
This commit is contained in:
2026-07-01 18:19:11 -07:00
parent abd252a178
commit e9cd3e7d68
3 changed files with 117 additions and 54 deletions
+56 -53
View File
@@ -1,70 +1,73 @@
{
"title": "Platform",
"description": "Hanzo Cloud Platform services",
"defaultOpen": true,
"icon": "Cloud",
"defaultOpen": true,
"pages": [
"---Core Platform---",
"cloud",
"gateway",
"console",
"platform",
"paas",
"---AI & Models---",
"chat",
"studio",
"flow",
"auto",
"operative",
"---AI---",
"models",
"engine",
"search",
"bot",
"brain",
"agents",
"nexus",
"vector",
"ml",
"---Identity & Security---",
"iam",
"identity",
"did",
"kms",
"mpc",
"guard",
"zt",
"---Backend Services---",
"base",
"orm",
"db",
"kv",
"s3",
"mq",
"stream",
"pubsub",
"---Compute---",
"functions",
"edge",
"---Data & Observability---",
"analytics",
"insights",
"tasks",
"---Data---",
"vector",
"kv",
"s3",
"base",
"---Network---",
"gateway",
"dns",
"---Security---",
"iam",
"kms",
"mpc",
"zt",
"---Observe---",
"o11y",
"status",
"dns",
"registry",
"visor",
"---Commerce & Billing---",
"commerce",
"pricing",
"sign",
"dataroom",
"captable",
"---Platform---",
"registry",
"---Web3---",
"referral",
"---Event Catalogs---",
"---Apps---",
"chat",
"bot",
"search",
"studio",
"console",
"---Commerce---",
"commerce",
"---More---",
"analytics",
"auto",
"brain",
"captable",
"cloud",
"commerce-events",
"dataroom",
"db",
"did",
"engine",
"flow",
"guard",
"identity",
"ingress",
"insights",
"insights-events",
"---Web3 Infrastructure---",
"web3",
"---Infrastructure Stack---",
"ingress"
"ml",
"mq",
"nexus",
"operative",
"orm",
"paas",
"platform",
"pubsub",
"sign",
"stream",
"visor",
"web3"
]
}
+3 -1
View File
@@ -4,8 +4,9 @@
"private": true,
"scripts": {
"build": "pnpm build:pre && next build && pnpm build:post",
"build:pre": "bun ./scripts/pre-build.ts",
"build:pre": "bun ./scripts/gen-services-nav.ts && bun ./scripts/pre-build.ts",
"build:post": "bun ./scripts/post-build.ts",
"gen:services": "bun ./scripts/gen-services-nav.ts",
"types:check": "@hanzo/docs-mdx && next typegen && tsc --noEmit",
"clean": "rimraf .next",
"dev": "next dev",
@@ -73,6 +74,7 @@
},
"devDependencies": {
"@hanzo/docs-cli": "workspace:*",
"@hanzo/products": "^0.1.0",
"@next/bundle-analyzer": "16.2.6",
"@next/env": "16.2.6",
"@tailwindcss/postcss": "^4.3.0",
+58
View File
@@ -0,0 +1,58 @@
/**
* Generate `content/docs/services/meta.json` FROM the shared `@hanzo/products`
* catalog, so docs' "All Services" taxonomy is DERIVED, never hand-maintained.
*
* ONE source of truth (the commerce-backed catalog, via @hanzo/products' snapshot),
* ONE generator, checked in. The nav groups every EXISTING docs page under the 13
* canonical console categories (the reconciled taxonomy). Docs-only pages (real MDX
* that back no catalog product) are preserved under a trailing "More" group, so
* nothing already documented is orphaned, and no nav entry ever points at a page
* that does not exist.
*
* Wired into `build:pre`. Guarded: if `@hanzo/products` is not yet installed it
* keeps the committed meta.json and exits 0 — it never breaks the build.
*
* Run manually: bun ./scripts/gen-services-nav.ts (or `pnpm gen:services`)
*/
import { readdirSync, statSync, writeFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import { dirname, join } from "node:path"
const here = dirname(fileURLToPath(import.meta.url))
const servicesDir = join(here, "..", "content", "docs", "services")
let products: typeof import("@hanzo/products")
try {
products = await import("@hanzo/products")
} catch {
console.warn("[gen-services-nav] @hanzo/products not installed — keeping committed meta.json")
process.exit(0)
}
const { SNAPSHOT, docsServicesMeta, docsCoverage } = products
// Existing docs slugs = top-level *.mdx (minus the index landing) + nested service dirs.
const existing: string[] = []
for (const name of readdirSync(servicesDir)) {
const st = statSync(join(servicesDir, name))
if (st.isDirectory()) existing.push(name)
else if (name.endsWith(".mdx") && name !== "index.mdx") existing.push(name.replace(/\.mdx$/, ""))
}
const coverage = docsCoverage(SNAPSHOT, existing)
const meta = docsServicesMeta(SNAPSHOT, {
existingSlugs: existing, // list only pages that exist — never a broken nav link
extraSlugs: coverage.extra.slice().sort(), // preserve docs-only pages under "More"
})
writeFileSync(join(servicesDir, "meta.json"), JSON.stringify(meta, null, 2) + "\n")
const groups = meta.pages.filter((p) => p.startsWith("---")).length
const navPages = meta.pages.filter((p) => !p.startsWith("---")).length
console.log(`[gen-services-nav] meta.json: ${navPages} nav pages across ${groups} groups (${SNAPSHOT.length} catalog products)`)
if (coverage.missing.length) {
console.log(`[gen-services-nav] ${coverage.missing.length} catalog products have NO docs page yet:`)
const byCat: Record<string, string[]> = {}
for (const m of coverage.missing) (byCat[m.category] ??= []).push(m.docsSlug)
for (const [c, s] of Object.entries(byCat)) console.log(` ${c}: ${s.join(", ")}`)
}