diff --git a/bun.lock b/bun.lock
index 4170c86..c45f33e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,33 +1,34 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "markdown-gen",
"dependencies": {
- "@json-schema-tools/traverse": "^1.11.0",
- "@open-rpc/examples": "^1.7.2",
- "@open-rpc/schema-utils-js": "^2.2.1",
- "@open-rpc/spec-types": "^0.0.2",
- "mdast": "^3.0.0",
- "mdast-util-gfm": "^3.1.0",
- "mdast-util-mdx": "^3.0.0",
- "mdast-util-to-markdown": "^2.1.2",
- "remark-gfm": "^4.0.1",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.5",
+ "@json-schema-tools/traverse": "1.11.0",
+ "@open-rpc/examples": "1.7.2",
+ "@open-rpc/schema-utils-js": "2.2.1",
+ "@open-rpc/spec-types": "0.0.2",
+ "mdast": "3.0.0",
+ "mdast-util-gfm": "3.1.0",
+ "mdast-util-mdx": "3.0.0",
+ "mdast-util-to-markdown": "2.1.2",
+ "remark-gfm": "4.0.1",
+ "remark-stringify": "11.0.0",
+ "unified": "11.0.5",
},
"devDependencies": {
- "@changesets/cli": "^2.29.8",
- "@eslint/js": "^9.39.2",
- "@types/bun": "^1.3.5",
+ "@changesets/cli": "2.29.8",
+ "@eslint/js": "9.39.3",
+ "@types/bun": "1.3.9",
"@types/node": "22.13.5",
- "@typescript-eslint/eslint-plugin": "^8.50.0",
- "@typescript-eslint/parser": "^8.50.0",
- "eslint": "^9.39.2",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.4",
- "globals": "^16.5.0",
- "typescript": "~5.9.3",
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "@typescript-eslint/parser": "8.56.1",
+ "eslint": "9.39.3",
+ "eslint-config-prettier": "10.1.8",
+ "eslint-plugin-prettier": "5.5.5",
+ "globals": "16.5.0",
+ "typescript": "5.9.3",
},
},
"packages/docusaurus-plugin": {
@@ -44,12 +45,12 @@
},
"devDependencies": {
"@docusaurus/types": "3.9.2",
- "@types/react": "^19.2.4",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "19.2.4",
+ "@types/react-dom": "19.2.3",
},
"peerDependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "react": "19.2.0",
+ "react-dom": "19.2.0",
},
},
"packages/example-site": {
@@ -58,18 +59,18 @@
"dependencies": {
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
- "@mdx-js/react": "^3.0.0",
+ "@mdx-js/react": "3.1.1",
"@open-rpc/docusaurus-plugin": "workspace:*",
- "clsx": "^2.0.0",
- "prism-react-renderer": "^2.3.0",
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
+ "clsx": "2.1.1",
+ "prism-react-renderer": "2.4.1",
+ "react": "19.2.0",
+ "react-dom": "19.2.0",
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2",
- "typescript": "~5.6.2",
+ "typescript": "5.9.3",
},
},
"packages/markdown-generator": {
@@ -80,22 +81,22 @@
},
"dependencies": {
"@open-rpc/schema-utils-js": "2.2.1",
- "@types/mdast": "^4.0.4",
- "mdast-util-from-markdown": "^2.0.2",
- "mdast-util-frontmatter": "^2.0.1",
- "mdast-util-gfm": "^3.1.0",
- "mdast-util-mdx": "^3.0.0",
- "mdast-util-to-markdown": "^2.1.0",
- "micromark-extension-gfm": "^3.0.0",
+ "@types/mdast": "4.0.4",
+ "mdast-util-from-markdown": "2.0.2",
+ "mdast-util-frontmatter": "2.0.1",
+ "mdast-util-gfm": "3.1.0",
+ "mdast-util-mdx": "3.0.0",
+ "mdast-util-to-markdown": "2.1.2",
+ "micromark-extension-gfm": "3.0.0",
},
"devDependencies": {
- "@types/bun": "^1.3.1",
+ "@types/bun": "1.3.9",
"@types/node": "22.13.5",
- "@typescript-eslint/eslint-plugin": "^8.46.2",
- "eslint": "^9.38.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.4",
- "typescript": "5.9",
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "eslint": "9.39.3",
+ "eslint-config-prettier": "10.1.8",
+ "eslint-plugin-prettier": "5.5.5",
+ "typescript": "5.9.3",
},
},
},
@@ -2686,8 +2687,6 @@
"null-loader/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="],
- "open-rpc-markdown-gen-beta-example-site/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
-
"p-filter/p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="],
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
diff --git a/package.json b/package.json
index 00c7631..338b1f3 100644
--- a/package.json
+++ b/package.json
@@ -24,29 +24,29 @@
"ci:publish": "for dir in packages/markdown-generator packages/docusaurus-plugin; do (cd \"$dir\" && bun publish --access public || true); done && changeset tag"
},
"devDependencies": {
- "@changesets/cli": "^2.29.8",
- "@eslint/js": "^9.39.3",
- "@types/bun": "^1.3.9",
+ "@changesets/cli": "2.29.8",
+ "@eslint/js": "9.39.3",
+ "@types/bun": "1.3.9",
"@types/node": "22.13.5",
- "@typescript-eslint/eslint-plugin": "^8.56.1",
- "@typescript-eslint/parser": "^8.56.1",
- "eslint": "^9.39.3",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.5",
- "globals": "^16.5.0",
- "typescript": "~5.9.3"
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "@typescript-eslint/parser": "8.56.1",
+ "eslint": "9.39.3",
+ "eslint-config-prettier": "10.1.8",
+ "eslint-plugin-prettier": "5.5.5",
+ "globals": "16.5.0",
+ "typescript": "5.9.3"
},
"dependencies": {
- "@json-schema-tools/traverse": "^1.11.0",
- "@open-rpc/examples": "^1.7.2",
- "@open-rpc/schema-utils-js": "^2.2.1",
- "@open-rpc/spec-types": "^0.0.2",
- "mdast": "^3.0.0",
- "mdast-util-gfm": "^3.1.0",
- "mdast-util-mdx": "^3.0.0",
- "mdast-util-to-markdown": "^2.1.2",
- "remark-gfm": "^4.0.1",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.5"
+ "@json-schema-tools/traverse": "1.11.0",
+ "@open-rpc/examples": "1.7.2",
+ "@open-rpc/schema-utils-js": "2.2.1",
+ "@open-rpc/spec-types": "0.0.2",
+ "mdast": "3.0.0",
+ "mdast-util-gfm": "3.1.0",
+ "mdast-util-mdx": "3.0.0",
+ "mdast-util-to-markdown": "2.1.2",
+ "remark-gfm": "4.0.1",
+ "remark-stringify": "11.0.0",
+ "unified": "11.0.5"
}
-}
\ No newline at end of file
+}
diff --git a/packages/docusaurus-plugin/package.json b/packages/docusaurus-plugin/package.json
index b065e4f..fef505e 100644
--- a/packages/docusaurus-plugin/package.json
+++ b/packages/docusaurus-plugin/package.json
@@ -27,11 +27,11 @@
},
"devDependencies": {
"@docusaurus/types": "3.9.2",
- "@types/react": "^19.2.4",
- "@types/react-dom": "^19.2.3"
+ "@types/react": "19.2.4",
+ "@types/react-dom": "19.2.3"
},
"peerDependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react": "19.2.0",
+ "react-dom": "19.2.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/docusaurus-plugin/src/lib.ts b/packages/docusaurus-plugin/src/lib.ts
index 5591680..94c9c6e 100644
--- a/packages/docusaurus-plugin/src/lib.ts
+++ b/packages/docusaurus-plugin/src/lib.ts
@@ -6,6 +6,7 @@ import {
identitySchemaEdits,
renderMethodsToMarkdown,
renderIndex,
+ tagPermalinkPrefixForDocsRoute,
} from "@open-rpc/markdown-generator";
import type {
DereffedMethodObject,
@@ -84,7 +85,8 @@ export async function cleanUpExistingDocs(specPath: string, outputDir: string) {
(entry) =>
entry.isFile() &&
methodFileNamesToGenerate.has(entry.name) === false &&
- entry.name !== "index.md",
+ entry.name !== "index.md" &&
+ entry.name !== "index.mdx",
)
.map((entry) => `${entry.parentPath}/${entry.name}`);
@@ -127,13 +129,21 @@ export async function generateDocs(
additionalFrontmatter["slug"] = options.indexSlug;
}
- const indexContent = renderIndex(doc, "mdx", additionalFrontmatter);
+ const indexContent = renderIndex(
+ doc,
+ "mdx",
+ additionalFrontmatter,
+ tagPermalinkPrefixForDocsRoute(options.docsRouteBasePath),
+ );
const finalIndex =
options.showPoweredBy === true
? `${indexContent}\n---\n\n*Powered by [OpenRPC](https://open-rpc.org)*\n`
: indexContent;
- await fs.writeFile(path.join(outDir, "index.md"), finalIndex, "utf8");
+ // Drop any legacy .md index before writing .mdx so Docusaurus doesn't see
+ // two route candidates with the same slug.
+ await fs.rm(path.join(outDir, "index.md"), { force: true });
+ await fs.writeFile(path.join(outDir, "index.mdx"), finalIndex, "utf8");
}
/* ---------- Renderers ---------- */
diff --git a/packages/docusaurus-plugin/src/options.ts b/packages/docusaurus-plugin/src/options.ts
index f0f6405..f30fc4e 100644
--- a/packages/docusaurus-plugin/src/options.ts
+++ b/packages/docusaurus-plugin/src/options.ts
@@ -6,6 +6,8 @@ export type Options = {
docOutputPath: string;
showPoweredBy: boolean;
indexSlug: string | undefined;
+ /** Docusaurus docs routeBasePath (default preset: "docs"). */
+ docsRouteBasePath?: string;
};
/**
@@ -25,5 +27,6 @@ export function normalizeOptions(options: Options): PluginOptions {
showPoweredBy:
options.showPoweredBy === undefined ? true : options.showPoweredBy,
indexSlug: options.indexSlug === undefined ? undefined : options.indexSlug,
+ docsRouteBasePath: options.docsRouteBasePath ?? "docs",
};
}
diff --git a/packages/example-site/docs/api-reference/index.md b/packages/example-site/docs/api-reference/index.mdx
similarity index 97%
rename from packages/example-site/docs/api-reference/index.md
rename to packages/example-site/docs/api-reference/index.mdx
index 7b4884d..67c6b09 100644
--- a/packages/example-site/docs/api-reference/index.md
+++ b/packages/example-site/docs/api-reference/index.mdx
@@ -3,7 +3,6 @@
title: "Ethereum JSON-RPC Specification"
description: "A specification of the standard interface for Ethereum clients."
-
---
# Ethereum JSON-RPC Specification
@@ -79,6 +78,10 @@ A specification of the standard interface for Ethereum clients.
- [`eth_syncing`](./methods/eth_syncing.mdx)
- [`eth_uninstallFilter`](./methods/eth_uninstallFilter.mdx)
+import TagsListInline from '@theme/TagsListInline';
+
+
+
---
*Powered by [OpenRPC](https://open-rpc.org)*
diff --git a/packages/example-site/docs/api-reference/methods/eth_chainId.mdx b/packages/example-site/docs/api-reference/methods/eth_chainId.mdx
index 8e69bd6..bb1040e 100644
--- a/packages/example-site/docs/api-reference/methods/eth_chainId.mdx
+++ b/packages/example-site/docs/api-reference/methods/eth_chainId.mdx
@@ -3,6 +3,8 @@
title: "eth_chainId"
hide_table_of_contents: true
description: ""
+tags:
+ - client
---
import {TwoColumnLayout, InteractiveRequest, ResponseExample} from '@open-rpc/docusaurus-plugin/components';
diff --git a/packages/example-site/error-group-openrpc.json b/packages/example-site/error-group-openrpc.json
index fea5430..5a0618f 100644
--- a/packages/example-site/error-group-openrpc.json
+++ b/packages/example-site/error-group-openrpc.json
@@ -5598,6 +5598,11 @@
{
"name": "eth_chainId",
"summary": "Returns the chain ID of the current network.",
+ "tags": [
+ {
+ "name": "client"
+ }
+ ],
"params": [],
"result": {
"name": "Chain ID",
diff --git a/packages/example-site/package.json b/packages/example-site/package.json
index 28e500d..7528a68 100644
--- a/packages/example-site/package.json
+++ b/packages/example-site/package.json
@@ -19,17 +19,17 @@
"@open-rpc/docusaurus-plugin": "workspace:*",
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
- "@mdx-js/react": "^3.0.0",
- "clsx": "^2.0.0",
- "prism-react-renderer": "^2.3.0",
- "react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "@mdx-js/react": "3.1.1",
+ "clsx": "2.1.1",
+ "prism-react-renderer": "2.4.1",
+ "react": "19.2.0",
+ "react-dom": "19.2.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"@docusaurus/types": "3.9.2",
- "typescript": "~5.6.2"
+ "typescript": "5.9.3"
},
"browserslist": {
"production": [
@@ -46,4 +46,4 @@
"engines": {
"node": ">=20.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/markdown-generator/README.md b/packages/markdown-generator/README.md
index 20ca012..99648cf 100644
--- a/packages/markdown-generator/README.md
+++ b/packages/markdown-generator/README.md
@@ -30,6 +30,13 @@ Writes markdown files directly to disk for each method, plus an `index.md`. The
Generates an index markdown string listing all methods with links. Returns the markdown as a string.
+The index intentionally does **not** aggregate method tags into its YAML frontmatter—that previously made the index doc appear under every `/tags/*` listing in Docusaurus. Instead, when at least one method has tags, the index appends an inline tag strip after the methods list:
+
+- For `"mdx"` output, it emits `` (Docusaurus theme component) so the chips match the standard `tags:` row visually.
+- For `"md"` output, it emits a `**Tags:** [name](./tags/) · ...` fallback line.
+
+When no methods carry tags, no tag block is emitted at all. Tag links must use **absolute** permalinks such as `/docs/tags/` — Docusaurus registers tag list pages at `/tags/`, and relative paths like `../tags/` are resolved by `@docusaurus/Link` to `/tags/` (404). Pass `tagPermalinkPrefixForDocsRoute("docs")` as the fourth argument to `renderIndex` (the Docusaurus plugin does this automatically). Write the index as `.mdx` when using ``. Tag pages list method docs that carry matching `tags:` frontmatter; add `docs/tags.yml` entries if you want tag descriptions on the tag page.
+
### `identityEdits` / `identitySchemaEdits`
Default edit functions that pass content through unchanged. Use these as a starting point when creating custom edits.
diff --git a/packages/markdown-generator/package.json b/packages/markdown-generator/package.json
index f5cd16a..2d4e106 100644
--- a/packages/markdown-generator/package.json
+++ b/packages/markdown-generator/package.json
@@ -30,22 +30,22 @@
"build": "bun build.ts && bun run types"
},
"devDependencies": {
- "@types/bun": "^1.3.1",
+ "@types/bun": "1.3.9",
"@types/node": "22.13.5",
- "typescript": "5.9",
- "@typescript-eslint/eslint-plugin": "^8.46.2",
- "eslint": "^9.38.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.4"
+ "typescript": "5.9.3",
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "eslint": "9.39.3",
+ "eslint-config-prettier": "10.1.8",
+ "eslint-plugin-prettier": "5.5.5"
},
"dependencies": {
"@open-rpc/schema-utils-js": "2.2.1",
- "@types/mdast": "^4.0.4",
- "mdast-util-from-markdown": "^2.0.2",
- "mdast-util-frontmatter": "^2.0.1",
- "mdast-util-gfm": "^3.1.0",
- "mdast-util-mdx": "^3.0.0",
- "mdast-util-to-markdown": "^2.1.0",
- "micromark-extension-gfm": "^3.0.0"
+ "@types/mdast": "4.0.4",
+ "mdast-util-from-markdown": "2.0.2",
+ "mdast-util-frontmatter": "2.0.1",
+ "mdast-util-gfm": "3.1.0",
+ "mdast-util-mdx": "3.0.0",
+ "mdast-util-to-markdown": "2.1.2",
+ "micromark-extension-gfm": "3.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/markdown-generator/src/fixtures/comprehensive.test.ts b/packages/markdown-generator/src/fixtures/comprehensive.test.ts
index 763eeb8..3701814 100644
--- a/packages/markdown-generator/src/fixtures/comprehensive.test.ts
+++ b/packages/markdown-generator/src/fixtures/comprehensive.test.ts
@@ -1,6 +1,10 @@
import { describe, it, expect } from "bun:test";
import { renderMethod, identitySchemaEdits, identityEdits } from "../schema";
-import { renderMethodsToMarkdown, renderIndex } from "../lib";
+import {
+ renderMethodsToMarkdown,
+ renderIndex,
+ tagPermalinkPrefixForDocsRoute,
+} from "../lib";
import { toMarkdown } from "mdast-util-to-markdown";
import { gfmToMarkdown } from "mdast-util-gfm";
import { mdxToMarkdown } from "mdast-util-mdx";
@@ -113,17 +117,84 @@ describe("renderIndex", () => {
);
});
- it("should include frontmatter with tags", () => {
+ it("should not aggregate method tags into the index frontmatter", () => {
const doc = comprehensiveTestDoc as DereffedOpenrpcDocument;
const index = renderIndex(doc, "mdx");
- // Frontmatter should exist
expect(index.startsWith("---")).toBe(true);
- // Should have tags section
- expect(index).toContain("tags:");
- // Should include unique tags from methods
- expect(index).toContain('"composition"');
- expect(index).toContain('"arrays"');
+ const frontmatterEnd = index.indexOf("\n---", 3);
+ expect(frontmatterEnd).toBeGreaterThan(0);
+ const frontmatter = index.slice(0, frontmatterEnd);
+ expect(frontmatter).not.toContain("tags:");
+ });
+
+ it("should render an mdx tag strip in the body when methods have tags", () => {
+ const doc = comprehensiveTestDoc as DereffedOpenrpcDocument;
+ const index = renderIndex(doc, "mdx");
+
+ expect(index).toContain(
+ "import TagsListInline from '@theme/TagsListInline';",
+ );
+ expect(index).toContain(" {
+ const doc = comprehensiveTestDoc as DereffedOpenrpcDocument;
+ const index = renderIndex(doc, "mdx", {}, "/docs/tags/");
+
+ expect(index).toContain(
+ '{label: "composition", permalink: "/docs/tags/composition"}',
+ );
+ expect(index).toContain(
+ '{label: "arrays", permalink: "/docs/tags/arrays"}',
+ );
+ });
+
+ it("should derive tag permalink prefix from docs routeBasePath", () => {
+ expect(tagPermalinkPrefixForDocsRoute()).toBe("/docs/tags/");
+ expect(tagPermalinkPrefixForDocsRoute("docs")).toBe("/docs/tags/");
+ expect(tagPermalinkPrefixForDocsRoute("/docs/")).toBe("/docs/tags/");
+ expect(tagPermalinkPrefixForDocsRoute("api")).toBe("/api/tags/");
+ expect(tagPermalinkPrefixForDocsRoute("/")).toBe("/tags/");
+ expect(tagPermalinkPrefixForDocsRoute("")).toBe("/tags/");
+ });
+
+ it("should render a markdown tag line in the body when output is md", () => {
+ const doc = comprehensiveTestDoc as DereffedOpenrpcDocument;
+ const index = renderIndex(doc, "md");
+
+ expect(index).not.toContain("TagsListInline");
+ expect(index).toContain("**Tags:**");
+ expect(index).toContain("[composition](./tags/composition)");
+ expect(index).toContain("[arrays](./tags/arrays)");
+ });
+
+ it("should omit any tag block when no methods carry tags", () => {
+ const untaggedDoc: DereffedOpenrpcDocument = {
+ openrpc: "1.0.0",
+ info: { title: "Untagged API", version: "1.0.0" },
+ methods: [
+ {
+ name: "noop",
+ params: [],
+ result: { name: "ok", schema: {} },
+ },
+ ],
+ } as DereffedOpenrpcDocument;
+
+ const indexMdx = renderIndex(untaggedDoc, "mdx");
+ const indexMd = renderIndex(untaggedDoc, "md");
+
+ for (const out of [indexMdx, indexMd]) {
+ expect(out).not.toContain("tags:");
+ expect(out).not.toContain("TagsListInline");
+ expect(out).not.toContain("**Tags:**");
+ expect(out).not.toContain("./tags/");
+ }
});
it("should list all methods with correct .mdx links", () => {
diff --git a/packages/markdown-generator/src/index.ts b/packages/markdown-generator/src/index.ts
index 4facd38..c3d9cdc 100644
--- a/packages/markdown-generator/src/index.ts
+++ b/packages/markdown-generator/src/index.ts
@@ -2,6 +2,7 @@ export {
renderMethodsToMarkdown,
renderDocumentToMarkdownFiles,
renderIndex,
+ tagPermalinkPrefixForDocsRoute,
} from "./lib";
export {
identityEdits,
diff --git a/packages/markdown-generator/src/lib.ts b/packages/markdown-generator/src/lib.ts
index 3603548..fb1e420 100644
--- a/packages/markdown-generator/src/lib.ts
+++ b/packages/markdown-generator/src/lib.ts
@@ -103,10 +103,26 @@ export async function renderDocumentToMarkdownFiles(
);
}
+// Build the prefix used for tag links in the index. Docusaurus registers tag
+// list pages at `//tags/` (default routeBasePath is
+// "docs"). Permalinks must be ABSOLUTE — `@docusaurus/Link` resolves any
+// relative path from the site root, so `./tags/x` or `../tags/x` both end up
+// at `/tags/x` (404) regardless of the index file's location.
+export function tagPermalinkPrefixForDocsRoute(
+ docsRouteBasePath = "docs",
+): string {
+ const base = docsRouteBasePath.replace(/^\/+|\/+$/g, "");
+ return base === "" ? "/tags/" : `/${base}/tags/`;
+}
+
export function renderIndex(
doc: DereffedOpenrpcDocument,
markdownType: "mdx" | "md",
additionalFrontmatter: Record = {},
+ // Default emits a relative `./tags/` link that works for plain markdown
+ // viewers / GitHub but is BROKEN inside Docusaurus. Pass
+ // `tagPermalinkPrefixForDocsRoute(routeBasePath)` when generating for a site.
+ tagPermalinkPrefix = "./tags/",
): string {
const title = doc.info?.title || "API";
const version = doc.info?.version || "";
@@ -117,11 +133,12 @@ export function renderIndex(
const tags = methods
.flatMap((m) => m.tags?.map((t) => t?.name) || [])
.filter(Boolean);
- const uniqueTags = [...new Set(tags)];
- const tagsList =
- uniqueTags.length === 0
- ? ""
- : `tags:\n${uniqueTags.map((t) => ` - "${t}"`).join("\n")}`;
+ const uniqueTags = [...new Set(tags)].sort();
+ const tagsBlock = renderTagsBlock(
+ uniqueTags,
+ markdownType,
+ tagPermalinkPrefix,
+ );
const methodsList =
methods.length === 0
@@ -149,7 +166,6 @@ export function renderIndex(
title: "${title}"
description: "${escapeYaml(desc)}"
${frontmatter}
-${tagsList}
---
# ${title}
@@ -159,5 +175,46 @@ ${version ? `Version: \`${version}\`\n` : ""}${desc ? `\n${desc}\n` : ""}
## Methods
${methodsList}
+${tagsBlock}`;
+}
+
+// Approximation of `lodash.kebabCase`, which is what Docusaurus uses to derive
+// the tag slug from inline frontmatter tags. Matches for ASCII alnum tags; may
+// diverge for tags containing digit/underscore boundaries (e.g. "API_v2").
+function slugifyTag(tag: string): string {
+ return tag
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+}
+
+function renderTagsBlock(
+ tags: string[],
+ markdownType: "mdx" | "md",
+ tagPermalinkPrefix: string,
+): string {
+ if (tags.length === 0) return "";
+
+ const tagHref = (tag: string) => `${tagPermalinkPrefix}${slugifyTag(tag)}`;
+
+ if (markdownType === "mdx") {
+ const items = tags
+ .map(
+ (t) =>
+ `{label: ${JSON.stringify(t)}, permalink: ${JSON.stringify(
+ tagHref(t),
+ )}}`,
+ )
+ .join(", ");
+ return `
+import TagsListInline from '@theme/TagsListInline';
+
+
`;
+ }
+
+ const links = tags.map((t) => `[${t}](${tagHref(t)})`).join(" · ");
+ return `\n**Tags:** ${links}\n`;
}