From 53f6d07df1f92b1db4387838ed148478f2bc3db5 Mon Sep 17 00:00:00 2001 From: Austen Stone Date: Mon, 22 Jun 2026 13:52:47 -0400 Subject: [PATCH] docs: wire up Fumadocs Twoslash for TypeScript examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the Shiki Twoslash transformer to the Fumadocs MDX pipeline so TypeScript code blocks tagged `ts twoslash` get build-time type hovers and error annotations. Twoslash runs the TS compiler at build, so a twoslashed block doubles as a compile guardrail: a type error fails `docs:build`. - Install fumadocs-twoslash + twoslash (peer) - Register transformerTwoslash() in source.config.ts - Externalize typescript/twoslash from the Next bundler - Import twoslash.css and register the `fumadocs-twoslash/ui` components - Convert the real `defineConfig()` example in configuration.mdx to `ts twoslash` — it type-checks against the actual actio-core types (resolved via the file:../packages/core symlink), so the config docs can't drift from the ActioConfig type without breaking the build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/app/global.css | 1 + docs/components/mdx.tsx | 2 + docs/content/docs/configuration.mdx | 2 +- docs/next.config.mjs | 3 ++ docs/package-lock.json | 79 ++++++++++++++++++++++++++++- docs/package.json | 2 + docs/source.config.ts | 2 + 7 files changed, 89 insertions(+), 2 deletions(-) diff --git a/docs/app/global.css b/docs/app/global.css index 0dd3999..c505428 100644 --- a/docs/app/global.css +++ b/docs/app/global.css @@ -1,6 +1,7 @@ @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; +@import 'fumadocs-twoslash/twoslash.css'; html { scrollbar-gutter: stable; diff --git a/docs/components/mdx.tsx b/docs/components/mdx.tsx index 984cb86..4e367e6 100644 --- a/docs/components/mdx.tsx +++ b/docs/components/mdx.tsx @@ -3,6 +3,7 @@ import { AutoTypeTable } from 'fumadocs-typescript/ui'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { TypeTable } from 'fumadocs-ui/components/type-table'; import defaultMdxComponents from 'fumadocs-ui/mdx'; +import * as Twoslash from 'fumadocs-twoslash/ui'; import type { MDXComponents } from 'mdx/types'; import { CodeCompare } from './code-compare'; @@ -18,6 +19,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { Tab, Tabs, TypeTable, + ...Twoslash, ...components, }; } diff --git a/docs/content/docs/configuration.mdx b/docs/content/docs/configuration.mdx index 0d11113..b55613d 100644 --- a/docs/content/docs/configuration.mdx +++ b/docs/content/docs/configuration.mdx @@ -11,7 +11,7 @@ discovery won't reach, like alongside your sources). Supported formats: `actio.config.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`, `.json` (TS/ESM are loaded at runtime via [jiti](https://github.com/unjs/jiti) — no build step). -```ts title="actio.config.ts" +```ts twoslash title="actio.config.ts" import { defineConfig } from "actio-core"; // also re-exported from "actio-cli/config" export default defineConfig({ diff --git a/docs/next.config.mjs b/docs/next.config.mjs index f8a203e..0410fc3 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -16,6 +16,9 @@ const basePath = process.env.PAGES_BASE_PATH ?? ''; const config = { output: 'export', reactStrictMode: true, + // Twoslash runs the TypeScript compiler at build time; keep these heavy, + // Node-only deps out of the bundler (per the Fumadocs Twoslash guide). + serverExternalPackages: ['typescript', 'twoslash'], // actio-core ships as ESM built for node20; let Next transpile it for the browser. transpilePackages: ['actio-core'], trailingSlash: true, diff --git a/docs/package-lock.json b/docs/package-lock.json index 8f6100a..461c9b5 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -14,6 +14,7 @@ "actio-core": "file:../packages/core", "fumadocs-core": "16.10.4", "fumadocs-mdx": "15.0.12", + "fumadocs-twoslash": "^3.2.0", "fumadocs-typescript": "^5.2.6", "fumadocs-ui": "16.10.4", "lucide-react": "^1.20.0", @@ -23,6 +24,7 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "tailwind-merge": "^3.6.0", + "twoslash": "^0.3.9", "yaml": "^2.9.0" }, "devDependencies": { @@ -2222,6 +2224,23 @@ "node": ">=20" } }, + "node_modules/@shikijs/twoslash": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-4.2.0.tgz", + "integrity": "sha512-PG/F0tMyt4zAvHVBL7Ehtk/ZpI2Rq3PwaXRYJaOO41eIK/iV9GOO/20jZhQkScOdcP6aFwHC2/x/GxmeR4tMlA==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.2.0", + "@shikijs/types": "4.2.0", + "twoslash": "^0.3.8" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "typescript": ">=5.5.0" + } + }, "node_modules/@shikijs/types": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", @@ -2664,6 +2683,18 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@typescript/vfs": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.4.tgz", + "integrity": "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", @@ -3876,6 +3907,34 @@ } } }, + "node_modules/fumadocs-twoslash": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fumadocs-twoslash/-/fumadocs-twoslash-3.2.0.tgz", + "integrity": "sha512-hzoxc2HR9dw2z5T1NNLYCbMY1DFiSF71+X2KJS4t/BalRyiBxpCcP9zAyzofba07YkrGhZ6xW3B105EcpNI2Vw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-popover": "^1.1.15", + "@shikijs/twoslash": "^4.0.2", + "mdast-util-from-markdown": "^2.0.3", + "mdast-util-gfm": "^3.1.0", + "mdast-util-to-hast": "^13.2.1", + "tailwind-merge": "^3.5.0", + "twoslash": "^0.3.7" + }, + "peerDependencies": { + "@types/react": "*", + "fumadocs-core": "^16.7.16", + "fumadocs-ui": "^16.7.16", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "shiki": "4.x.x" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/fumadocs-typescript": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/fumadocs-typescript/-/fumadocs-typescript-5.2.6.tgz", @@ -7191,6 +7250,25 @@ "fsevents": "~2.3.3" } }, + "node_modules/twoslash": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/twoslash/-/twoslash-0.3.9.tgz", + "integrity": "sha512-rDclk+OtzuTX+tnea7DYLCkqGQ3eP0IyfD+kzUJ7t46X/NzlaxwrhecmEBNuSCuEn3V+n1PhcjUUQQ7gUJzX5Q==", + "license": "MIT", + "dependencies": { + "@typescript/vfs": "^1.6.4", + "twoslash-protocol": "0.3.9" + }, + "peerDependencies": { + "typescript": "^5.5.0 || ^6.0.0" + } + }, + "node_modules/twoslash-protocol": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/twoslash-protocol/-/twoslash-protocol-0.3.9.tgz", + "integrity": "sha512-9/iwp+CXOnjFMPQuPL5PkuRbZnDoNpBvtJCLs9t8kDYkL3YHujbvnHfZA1i5fApDftVEdBw+T/4F+dH5kIzpYQ==", + "license": "MIT" + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -7284,7 +7362,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/docs/package.json b/docs/package.json index d717844..da94dbb 100644 --- a/docs/package.json +++ b/docs/package.json @@ -19,6 +19,7 @@ "actio-core": "file:../packages/core", "fumadocs-core": "16.10.4", "fumadocs-mdx": "15.0.12", + "fumadocs-twoslash": "^3.2.0", "fumadocs-typescript": "^5.2.6", "fumadocs-ui": "16.10.4", "lucide-react": "^1.20.0", @@ -28,6 +29,7 @@ "react": "^19.2.7", "react-dom": "^19.2.7", "tailwind-merge": "^3.6.0", + "twoslash": "^0.3.9", "yaml": "^2.9.0" }, "devDependencies": { diff --git a/docs/source.config.ts b/docs/source.config.ts index 3302879..93cd549 100644 --- a/docs/source.config.ts +++ b/docs/source.config.ts @@ -1,6 +1,7 @@ import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins'; +import { transformerTwoslash } from 'fumadocs-twoslash'; import { transformerActioKeywords } from './lib/shiki-actio'; // You can customize Zod schemas for frontmatter and `meta.json` here @@ -25,6 +26,7 @@ export default defineConfig({ transformers: [ ...(rehypeCodeDefaultOptions.transformers ?? []), transformerActioKeywords(), + transformerTwoslash(), ], }, },