mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-30 14:41:16 +00:00
Finish the fumadocs→Hanzo Docs rebrand: rename the remaining fuma-named
workspace packages and every reference so the framework is uniformly
@hanzo/docs-*. One brand, no Fuma in package names or product surface.
Package renames (name field + all refs: deps, imports, CSS @import,
configs, .changeset, next/vite build config):
fumadocs-core → @hanzo/docs-core
fumadocs-mdx → @hanzo/docs-mdx
fumadocs-ui → @hanzo/docs-ui (packages/radix-ui)
fumadocs-openapi → @hanzo/docs-openapi
fumadocs-preview → @hanzo/docs-preview
@fumadocs/base-ui → @hanzo/docs-base-ui
@fumadocs/basehub → @hanzo/docs-basehub
@fumadocs/language → @hanzo/docs-language
@fumadocs/local-md → @hanzo/docs-local-md
@fumadocs/sanity → @hanzo/docs-sanity
@fumadocs/shadcn → @hanzo/docs-shadcn
@fumadocs/vite → @hanzo/docs-vite
@fumari/stf → @hanzo/docs-stf
create-fumadocs-app → @hanzo/create-docs
create-fumadocs-versions → @hanzo/docs-create-versions
Stale @fumadocs/{cli,story,tailwind,mdx-remote,...} references collapsed to
their existing @hanzo/docs-* canonical packages (no dup created).
De-branded product strings: Fumadocs/Fuma prose → Hanzo Docs/Hanzo; site
branding, CLI output, doc copy, AI-assistant name, metadata. URLs
fumadocs.dev → docs.hanzo.ai, repo fuma-nama/fumadocs → hanzoai/docs.
Filesystem renames: packages/python/fumapy → hanzodocs_py, preview config,
what-is-fumadocs.mdx → what-is-hanzo-docs.mdx, api/fumadocs-ui route.
KEPT (real external npm deps / attribution — NOT renamed):
fuma-cli, fuma-content, @fumari/json-schema-ts (published upstream pkgs),
typesense-/trieve-fumadocs-adapter (3rd-party search adapters),
LICENSE "Copyright (c) 2023 Fuma" (OSS attribution, all 7 LICENSE files
untouched), CHANGELOG.md history (accurate release records, left as-is).
Build: node 24, pnpm install clean; all 4 target apps next build GREEN
(spec, gui-docs, zen-docs, base-docs) + full package closure (18/18 turbo).
Gate: zero @hanzo-package fumadocs-/@fumadocs/ references remain.
290 lines
10 KiB
TypeScript
290 lines
10 KiB
TypeScript
import { applyMdxPreset, defineCollections, defineConfig, defineDocs } from '@hanzo/docs-mdx/config';
|
|
import { z } from 'zod';
|
|
import type { ElementContent } from 'hast';
|
|
import jsonSchema from '@hanzo/docs-mdx/plugins/json-schema';
|
|
import lastModified from '@hanzo/docs-mdx/plugins/last-modified';
|
|
import type { ShikiTransformer } from 'shiki';
|
|
import type { RemarkAutoTypeTableOptions } from '@hanzo/docs-typescript';
|
|
import { shikiConfig } from './lib/shiki';
|
|
import { metaSchema, pageSchema } from '@hanzo/docs-core/source/schema';
|
|
import { visit } from 'unist-util-visit';
|
|
import type { Transformer } from 'unified';
|
|
import type { Root } from 'mdast';
|
|
|
|
const isLint = process.env.LINT === '1';
|
|
|
|
const isExport = process.env.NEXT_EXPORT === '1';
|
|
|
|
// Federated docs: DOCS_SECTION controls which MDX pages are built.
|
|
// Each section deploys to its own CF Pages project; meta.json files are
|
|
// always included so every build renders the complete sidebar tree.
|
|
const section = process.env.DOCS_SECTION as 'core' | 'kms' | 'iam' | 'platform' | 'projects' | undefined;
|
|
|
|
const sectionFilters: Record<string, string[]> = {
|
|
core: ['**/*.mdx', '!**/projects/**', '!**/services/kms/**', '!**/services/iam/**', '!**/services/platform/**'],
|
|
kms: ['**/services/kms/**/*.mdx'],
|
|
iam: ['**/services/iam/**/*.mdx'],
|
|
platform: ['**/services/platform/**/*.mdx'],
|
|
projects: ['**/projects/**/*.mdx'],
|
|
};
|
|
|
|
function getDocsFiles(): string[] | undefined {
|
|
if (section && sectionFilters[section]) return sectionFilters[section];
|
|
if (isExport) return ['**/*.mdx', '!**/projects/**'];
|
|
return undefined;
|
|
}
|
|
|
|
export const docs = defineDocs({
|
|
docs: {
|
|
...(getDocsFiles() ? { files: getDocsFiles() } : {}),
|
|
schema: pageSchema.extend({
|
|
preview: z.string().optional(),
|
|
index: z.boolean().default(false),
|
|
/**
|
|
* API routes only
|
|
*/
|
|
method: z.string().optional(),
|
|
}),
|
|
postprocess: {
|
|
includeProcessedMarkdown: true,
|
|
extractLinkReferences: true,
|
|
valueToExport: ['elementIds'],
|
|
},
|
|
async: true,
|
|
async mdxOptions(environment) {
|
|
const { rehypeCodeDefaultOptions } = await import('@hanzo/docs-core/mdx-plugins/rehype-code');
|
|
const { remarkSteps } = await import('@hanzo/docs-core/mdx-plugins/remark-steps');
|
|
const { remarkFeedbackBlock } =
|
|
await import('@hanzo/docs-core/mdx-plugins/remark-feedback-block');
|
|
const { transformerTwoslash } = await import('@hanzo/docs-twoslash');
|
|
const { createFileSystemTypesCache } = await import('@hanzo/docs-twoslash/cache-fs');
|
|
const { default: remarkMath } = await import('remark-math');
|
|
const { remarkTypeScriptToJavaScript } = await import('@hanzo/docs-docgen/remark-ts2js');
|
|
const { default: rehypeKatex } = await import('rehype-katex');
|
|
const { remarkAutoTypeTable, createGenerator, createFileSystemGeneratorCache } =
|
|
await import('@hanzo/docs-typescript');
|
|
|
|
const typeTableOptions: RemarkAutoTypeTableOptions = {
|
|
generator: createGenerator({
|
|
cache: createFileSystemGeneratorCache('.next/@hanzo/docs-typescript'),
|
|
}),
|
|
shiki: shikiConfig,
|
|
};
|
|
return applyMdxPreset({
|
|
remarkImageOptions: {
|
|
// Imported upstream docs may reference images that don't exist in
|
|
// this monorepo. Log a warning instead of failing the build.
|
|
onError: 'ignore',
|
|
},
|
|
rehypeCodeOptions: isLint
|
|
? false
|
|
: {
|
|
inline: 'tailing-curly-colon',
|
|
themes: {
|
|
light: 'catppuccin-latte',
|
|
dark: 'catppuccin-mocha',
|
|
},
|
|
transformers: [
|
|
...(rehypeCodeDefaultOptions.transformers ?? []),
|
|
transformerTwoslash({
|
|
typesCache: createFileSystemTypesCache(),
|
|
twoslashOptions: {
|
|
compilerOptions: {
|
|
types: ['@types/node'],
|
|
},
|
|
},
|
|
}),
|
|
transformerEscape(),
|
|
],
|
|
},
|
|
remarkCodeTabOptions: {
|
|
parseMdx: true,
|
|
},
|
|
remarkStructureOptions: {
|
|
stringify: {
|
|
filterElement(node) {
|
|
switch (node.type) {
|
|
case 'mdxJsxFlowElement':
|
|
case 'mdxJsxTextElement':
|
|
switch (node.name) {
|
|
case 'File':
|
|
case 'TypeTable':
|
|
case 'Callout':
|
|
case 'Card':
|
|
case 'Custom':
|
|
return true;
|
|
}
|
|
return 'children-only';
|
|
}
|
|
|
|
return true;
|
|
},
|
|
},
|
|
},
|
|
remarkImageOptions: isLint ? false : undefined,
|
|
remarkNpmOptions: {
|
|
persist: {
|
|
id: 'package-manager',
|
|
},
|
|
},
|
|
remarkPlugins: isLint
|
|
? [remarkElementIds]
|
|
: [
|
|
remarkPassthroughUnknownJsx,
|
|
remarkSteps,
|
|
remarkMath,
|
|
[remarkBlockId, { addDataAttribute: 'feedback' }],
|
|
[remarkAutoTypeTable, typeTableOptions],
|
|
remarkTypeScriptToJavaScript,
|
|
],
|
|
rehypePlugins: (v) => [rehypeKatex, ...v],
|
|
})(environment);
|
|
},
|
|
},
|
|
meta: {
|
|
schema: metaSchema.extend({
|
|
description: z.string().optional(),
|
|
}),
|
|
// Only pick up actual meta files — the default **/*.{json,yaml} pattern
|
|
// also matches data files (openapi.json, package.json, etc.) which then
|
|
// get processed through the MDX loader and corrupt the build.
|
|
files: ['**/meta.json', '**/meta.yaml'],
|
|
},
|
|
});
|
|
|
|
export const blog = defineCollections({
|
|
type: 'doc',
|
|
dir: 'content/blog',
|
|
schema: pageSchema.extend({
|
|
author: z.string(),
|
|
date: z.iso.date().or(z.date()),
|
|
}),
|
|
async: true,
|
|
async mdxOptions(environment) {
|
|
const { rehypeCodeDefaultOptions } = await import('@hanzo/docs-core/mdx-plugins/rehype-code');
|
|
const { remarkSteps } = await import('@hanzo/docs-core/mdx-plugins/remark-steps');
|
|
|
|
return applyMdxPreset({
|
|
rehypeCodeOptions: isLint
|
|
? false
|
|
: {
|
|
inline: 'tailing-curly-colon',
|
|
themes: {
|
|
light: 'catppuccin-latte',
|
|
dark: 'catppuccin-mocha',
|
|
},
|
|
transformers: [...(rehypeCodeDefaultOptions.transformers ?? []), transformerEscape()],
|
|
},
|
|
remarkCodeTabOptions: {
|
|
parseMdx: true,
|
|
},
|
|
remarkImageOptions: isLint ? false : undefined,
|
|
remarkNpmOptions: {
|
|
persist: {
|
|
id: 'package-manager',
|
|
},
|
|
},
|
|
remarkPlugins: isLint ? [remarkElementIds] : [remarkSteps],
|
|
})(environment);
|
|
},
|
|
});
|
|
|
|
function transformerEscape(): ShikiTransformer {
|
|
return {
|
|
name: '@shikijs/transformers:remove-notation-escape',
|
|
code(hast) {
|
|
function replace(node: ElementContent) {
|
|
if (node.type === 'text') {
|
|
node.value = node.value.replace('[\\!code', '[!code');
|
|
} else if ('children' in node) {
|
|
for (const child of node.children) {
|
|
replace(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
replace(hast);
|
|
return hast;
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Remark plugin that converts unknown JSX components (from upstream doc
|
|
* platforms like Mintlify, Docusaurus, GitBook, etc.) into JSX fragments
|
|
* so MDX doesn't generate _missingMdxReference checks that crash SSG.
|
|
*
|
|
* Only applies to files inside content/docs/projects/.
|
|
*/
|
|
function remarkPassthroughUnknownJsx(): Transformer<Root, Root> {
|
|
return (tree, file) => {
|
|
// Only transform upstream project docs — leave first-party docs alone
|
|
// so unknown components still surface as errors during development.
|
|
const filePath = file.path ?? file.history[0] ?? '';
|
|
if (!filePath.includes('content/docs/projects/') &&
|
|
!filePath.includes('content\\docs\\projects\\') &&
|
|
!filePath.includes('content/docs/services/') &&
|
|
!filePath.includes('content\\docs\\services\\')) {
|
|
return;
|
|
}
|
|
|
|
// Convert ALL PascalCase JSX elements to fragments in project docs.
|
|
// Upstream docs use components from various platforms (Mintlify, Docusaurus,
|
|
// GitBook, etc.) with incompatible APIs. Rather than maintaining a
|
|
// whitelist and risking API mismatches (e.g. Mintlify <Tab title="..."> vs
|
|
// Hanzo Docs <Tab value="...">), we strip all custom components and render
|
|
// just their children. First-party docs are unaffected by this plugin.
|
|
visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node: any) => {
|
|
if (!node.name) return; // already a fragment
|
|
// Only target PascalCase names (custom components), not lowercase HTML
|
|
if (!/^[A-Z]/.test(node.name)) return;
|
|
|
|
// Convert to JSX fragment: strips the unknown tag but keeps children
|
|
node.name = null;
|
|
node.attributes = [];
|
|
});
|
|
|
|
// Convert stray {expression} nodes to literal text. Upstream docs use
|
|
// template-style placeholders like {region} which MDX evaluates as JS.
|
|
visit(tree, ['mdxFlowExpression', 'mdxTextExpression'], (node: any, index, parent) => {
|
|
if (index == null || !parent) return;
|
|
// Skip expressions that look like actual JS (contain operators, calls, etc.)
|
|
const expr = (node.value || '').trim();
|
|
if (!expr || /[()=><+\-*/!&|?:,;{}[\]`'"]/.test(expr)) return;
|
|
// Simple identifier like {region} — convert to literal text
|
|
parent.children.splice(index, 1, {
|
|
type: 'text',
|
|
value: `{${expr}}`,
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
function remarkElementIds(): Transformer<Root, Root> {
|
|
return (tree, file) => {
|
|
file.data ??= {};
|
|
file.data.elementIds ??= [];
|
|
|
|
visit(tree, 'mdxJsxFlowElement', (element) => {
|
|
if (!element.name || !element.attributes) return;
|
|
|
|
const idAttr = element.attributes.find(
|
|
(attr) => attr.type === 'mdxJsxAttribute' && attr.name === 'id',
|
|
);
|
|
|
|
if (idAttr && typeof idAttr.value === 'string') {
|
|
(file.data.elementIds as string[]).push(idAttr.value);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
jsonSchema({
|
|
insert: true,
|
|
}),
|
|
lastModified(),
|
|
],
|
|
});
|