Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/type-table-source-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"leadtype": patch
---

Default `<ExtractedTypeTable>` and `<AutoTypeTable>` path resolution to the Leadtype source root instead of `process.cwd()/docs`.

This fixes generated docs for source roots such as `.c15t` or `.leadtype`, where `path="./packages/..."` should resolve against the configured source root. Source-MDX consumers can now pass `typeTableBasePath` / `typeTableStrict` through `createDocsSource()` or use `createMdxSourcePlugins()` for bundler-level configuration. Failed type extraction now emits a visible warning by default and can fail generation in strict mode.

This changes the bare `mdxSourcePlugins` default for bundler consumers: when Leadtype can see the source MDX file path, it derives the base path from the first `docs` path segment instead of always using `process.cwd()/docs`. Projects that intentionally keep referenced TypeScript files under their docs folder should switch to `createMdxSourcePlugins({ typeTableBasePath: path.resolve(process.cwd(), "docs") })`.
11 changes: 8 additions & 3 deletions apps/example/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import mdx from "@mdx-js/rollup";
import tailwindcss from "@tailwindcss/vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import type { Root } from "mdast";
import { nitro } from "nitro/vite";
import remarkFrontmatter from "remark-frontmatter";
import remarkGfm from "remark-gfm";
import { defineConfig, searchForWorkspaceRoot } from "vite";
import viteTsConfigPaths from "vite-tsconfig-paths";

const configDir = dirname(fileURLToPath(import.meta.url));
const typeTableBasePath = resolve(configDir, "..", "..");

function stripYamlFrontmatter() {
return (tree: Root) => {
if (!tree.children) {
Expand Down Expand Up @@ -37,14 +42,14 @@ export default defineConfig({
...mdx({
providerImportSource: "@mdx-js/react",
remarkPlugins: [
// Frontmatter parsing first (mdxSourcePlugins expects bodies only).
// Frontmatter parsing first (Leadtype's source preset expects bodies only).
remarkFrontmatter,
stripYamlFrontmatter,
remarkGfm,
// Leadtype's MDX-source preset: expand <include>, resolve
// <ExtractedTypeTable>, strip authoring `import`s. Keeps every
// other custom tag as live JSX for the React components below.
...mdxSourcePlugins,
...createMdxSourcePlugins({ typeTableBasePath }),
],
}),
enforce: "pre",
Expand Down
2 changes: 1 addition & 1 deletion apps/fumadocs-example/lib/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const contentDir = resolve(
* fumadocs source backed by leadtype/fumadocs. Walks `.docs-src/c15t/docs`,
* picks up both `.mdx` pages and the c15t-authored `meta.json` files, and
* resolves `<include>` / `<ExtractedTypeTable>` at build time via
* `mdxSourcePlugins` (wired in `next.config.mjs`).
* `createMdxSourcePlugins()` (wired in `next.config.mjs`).
*/
const fumadocsSourceResult = await fumadocsSource({ contentDir });

Expand Down
17 changes: 15 additions & 2 deletions apps/fumadocs-example/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { resolve } from "node:path";
import createMDX from "@next/mdx";
import { rehypeCode } from "fumadocs-core/mdx-plugins";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import remarkFrontmatter from "remark-frontmatter";
import remarkGfm from "remark-gfm";

const typeTableBasePath = resolve(
process.cwd(),
"..",
"..",
".docs-src",
"c15t"
);

const withMDX = createMDX({
options: {
// Frontmatter parsing must precede leadtype's stack (it expects bodies).
remarkPlugins: [remarkFrontmatter, remarkGfm, ...mdxSourcePlugins],
remarkPlugins: [
remarkFrontmatter,
remarkGfm,
...createMdxSourcePlugins({ typeTableBasePath }),
],
// Shiki-based highlighter from fumadocs-core. Pairs with fumadocs-ui's
// codeblock CSS so tokens, copy button, and frame styling all kick in.
rehypePlugins: [rehypeCode],
Expand Down
2 changes: 1 addition & 1 deletion docs/build/build-a-docs-site.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Leadtype offers two integration shapes for a docs site. Pick the one that fits h

| Path | When to choose | What you wire |
| --- | --- | --- |
| **Source primitive** | Most cases. You're building a Next, Vite, Astro, or Fumadocs app and compile MDX with your bundler. | `createDocsSource()` (or `leadtype/fumadocs`) + `mdxSourcePlugins` |
| **Source primitive** | Most cases. You're building a Next, Vite, Astro, or Fumadocs app and compile MDX with your bundler. | `createDocsSource()` (or `leadtype/fumadocs`) + `createMdxSourcePlugins()` |
| **Static artifacts** | Your runtime needs flat files on disk (CDN-only deploy, static export, agent-only consumption, multi-app sharing). | `leadtype generate` CLI in your build script |

The two paths are not mutually exclusive — you can use the source primitive for your UI and still run `leadtype generate` for the `llms.txt` and agent-readability artifacts.
Expand Down
20 changes: 14 additions & 6 deletions docs/build/integrate-with-fumadocs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ export const leadtypeSource = fumaSource.leadtype;

```ts title="next.config.mjs"
import createMDX from "@next/mdx";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import path from "node:path";

const typeTableBasePath = path.resolve(process.cwd(), ".c15t");

export default createMDX({
options: { remarkPlugins: [...mdxSourcePlugins] },
options: {
remarkPlugins: [...createMdxSourcePlugins({ typeTableBasePath })],
},
})({ pageExtensions: ["ts", "tsx", "mdx"] });
```

Expand Down Expand Up @@ -71,18 +76,21 @@ Leadtype's MDX-source preset expands `<include>`, resolves `<ExtractedTypeTable>

```ts title="next.config.mjs"
import createMDX from "@next/mdx";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import path from "node:path";

const typeTableBasePath = path.resolve(process.cwd(), ".c15t");

const withMDX = createMDX({
options: {
remarkPlugins: [...mdxSourcePlugins],
remarkPlugins: [...createMdxSourcePlugins({ typeTableBasePath })],
},
});

export default withMDX({ pageExtensions: ["ts", "tsx", "mdx"] });
```

The same plugin list works with `@mdx-js/rollup`, fumadocs-mdx, and any other MDX compiler that accepts a remark plugin list.
The same plugin list works with `@mdx-js/rollup`, fumadocs-mdx, and any other MDX compiler that accepts a remark plugin list. Set `typeTableBasePath` to the source root that contains files referenced by `<ExtractedTypeTable path="…">`.

## Implement the tag components

Expand Down Expand Up @@ -131,7 +139,7 @@ export default async function Page({
}
```

If you prefer fumadocs's built-in page resolution, call `source.getPage(slug)` and import the source `.mdx` directly through fumadocs-mdx as you normally would — the `mdxSourcePlugins` preset will resolve includes during MDX compilation.
If you prefer fumadocs's built-in page resolution, call `source.getPage(slug)` and import the source `.mdx` directly through fumadocs-mdx as you normally would — the `createMdxSourcePlugins()` preset will resolve includes during MDX compilation.

## Add search

Expand Down
41 changes: 23 additions & 18 deletions docs/build/use-the-source-primitive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const source = await createDocsSource({
});
```

Wire `mdxSourcePlugins` into your bundler's remark stack, then call `source.loadPage(slug)` from your framework's page renderer. The "Wire into your framework" section below has minimal setups for each host.
Wire `createMdxSourcePlugins()` into your bundler's remark stack, then call `source.loadPage(slug)` from your framework's page renderer. The "Wire into your framework" section below has minimal setups for each host.

## Install

Expand All @@ -36,16 +36,21 @@ Plus an MDX integration for your bundler (`@next/mdx`, `@astrojs/mdx`, `@mdx-js/

## Wire into your framework

`mdxSourcePlugins` expands `<include>` partials and resolves `<ExtractedTypeTable>` at build time, while leaving every custom tag (`<Callout>`, `<Tabs>`, `<Steps>`, …) as JSX for your runtime components.
`createMdxSourcePlugins()` expands `<include>` partials and resolves `<ExtractedTypeTable>` at build time, while leaving every custom tag (`<Callout>`, `<Tabs>`, `<Steps>`, …) as JSX for your runtime components. Set `typeTableBasePath` to the source root that contains referenced TypeScript files; use `path.resolve(process.cwd(), "docs")` only when those files intentionally live under your docs folder.

### Next App Router

```ts title="next.config.mjs"
import createMDX from "@next/mdx";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import path from "node:path";

const typeTableBasePath = path.resolve(process.cwd(), ".c15t");

export default createMDX({
options: { remarkPlugins: [...mdxSourcePlugins] },
options: {
remarkPlugins: [...createMdxSourcePlugins({ typeTableBasePath })],
},
})({ pageExtensions: ["ts", "tsx", "mdx"] });
```

Expand Down Expand Up @@ -82,10 +87,10 @@ export async function generateStaticParams() {
```ts title="astro.config.mjs"
import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";

export default defineConfig({
integrations: [mdx({ remarkPlugins: [...mdxSourcePlugins] })],
integrations: [mdx({ remarkPlugins: [...createMdxSourcePlugins()] })],
});
```

Expand All @@ -97,7 +102,7 @@ Use Astro's native content collection schema for typed frontmatter. Call `source
import mdx from "@mdx-js/rollup";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import remarkFrontmatter from "remark-frontmatter";
import { defineConfig } from "vite";

Expand All @@ -106,7 +111,7 @@ export default defineConfig({
{
...mdx({
providerImportSource: "@mdx-js/react",
remarkPlugins: [remarkFrontmatter, ...mdxSourcePlugins],
remarkPlugins: [remarkFrontmatter, ...createMdxSourcePlugins()],
}),
enforce: "pre",
},
Expand Down Expand Up @@ -189,11 +194,11 @@ Generate `docs-pages.json` at build time by calling `createDocsSource().listPage

```ts title="vite.config.ts"
import mdx from "@mdx-js/rollup";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";

export default {
plugins: [
mdx({ remarkPlugins: [...mdxSourcePlugins] }),
mdx({ remarkPlugins: [...createMdxSourcePlugins()] }),
// ...your framework plugin: viteReact / vue / solid / svelte
],
};
Expand All @@ -202,31 +207,31 @@ export default {
### Nuxt

```ts title="nuxt.config.ts"
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";

export default defineNuxtConfig({
modules: ["@nuxtjs/mdc"],
mdc: { remarkPlugins: [...mdxSourcePlugins] },
mdc: { remarkPlugins: [...createMdxSourcePlugins()] },
});
```

### SvelteKit + `mdsvex`

```ts title="svelte.config.js"
import { mdsvex } from "mdsvex";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";

export default {
extensions: [".svelte", ".svx", ".mdx"],
preprocess: mdsvex({ remarkPlugins: [...mdxSourcePlugins] }),
preprocess: mdsvex({ remarkPlugins: [...createMdxSourcePlugins()] }),
};
```

### Pattern for any other host

If your framework's MDX integration accepts a remark plugin list, leadtype works. Three pieces every time:

1. Add `mdxSourcePlugins` to the remark list so `<include>` and `<ExtractedTypeTable>` resolve at build time.
1. Add `createMdxSourcePlugins()` to the remark list so `<include>` and `<ExtractedTypeTable>` resolve at build time.
2. Implement components against the [tag types from `leadtype/mdx`](/docs/reference/mdx).
3. Call `createDocsSource()` if you want navigation, search, or programmatic page loading.

Expand Down Expand Up @@ -320,13 +325,13 @@ For provider-specific search (Vercel AI, TanStack, Cloudflare), feed the bundle

## Troubleshooting

- **`<include>` tags survive into the rendered output.** You forgot to add `mdxSourcePlugins` to your MDX compiler's remark plugin list.
- **`<ExtractedTypeTable>` renders unresolved.** The source preset converts these to `<TypeTable>` only when `extractTypeFromFile` succeeds. Make sure `typescript` is installed in the docs app and `basePath` resolves to the project that contains the type.
- **`<include>` tags survive into the rendered output.** You forgot to add `createMdxSourcePlugins()` to your MDX compiler's remark plugin list.
- **`<ExtractedTypeTable>` renders unresolved.** The source preset converts these to `<TypeTable>` only when `extractTypeFromFile` succeeds. Make sure `typescript` is installed in the docs app and `typeTableBasePath` resolves to the project that contains the type.
- **TOC links don't scroll.** Rendered heading IDs don't match. Wire `slugifyDocsHeading` into your heading components.
- **Sidebar order doesn't match `llms.txt`.** Your app and the CLI are loading different `docs.config.ts` files. Centralize the config and import it in both.

## Reference

- [`leadtype/mdx`](/docs/reference/mdx) — tag types, `mdxSourcePlugins`, include helpers
- [`leadtype/mdx`](/docs/reference/mdx) — tag types, `createMdxSourcePlugins()`, include helpers
- [`createDocsSource`](/docs/reference/source) — full API surface for the primitive
- [`leadtype/fumadocs`](/docs/build/integrate-with-fumadocs) — fumadocs adapter recipe
8 changes: 4 additions & 4 deletions docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Pick the recipe that matches your stack. Each one shows the wiring on top of the
<Cards>
<Card
title="Next App Router"
description="Catch-all route + @next/mdx + mdxSourcePlugins."
description="Catch-all route + @next/mdx + createMdxSourcePlugins()."
href="/docs/build/use-the-source-primitive#next-app-router"
/>
<Card
Expand All @@ -68,7 +68,7 @@ Pick the recipe that matches your stack. Each one shows the wiring on top of the
/>
<Card
title="Astro Content Collections"
description="@astrojs/mdx + mdxSourcePlugins. Compose with Astro's native content schema."
description="@astrojs/mdx + createMdxSourcePlugins(). Compose with Astro's native content schema."
href="/docs/build/use-the-source-primitive#astro-content-collections"
/>
<Card
Expand All @@ -78,12 +78,12 @@ Pick the recipe that matches your stack. Each one shows the wiring on top of the
/>
<Card
title="Nuxt"
description="@nuxtjs/mdc + mdxSourcePlugins."
description="@nuxtjs/mdc + createMdxSourcePlugins()."
href="/docs/build/use-the-source-primitive#nuxt"
/>
<Card
title="SvelteKit"
description="mdsvex preprocessor + mdxSourcePlugins."
description="mdsvex preprocessor + createMdxSourcePlugins()."
href="/docs/build/use-the-source-primitive#sveltekit-mdsvex"
/>
</Cards>
Expand Down
15 changes: 11 additions & 4 deletions docs/reference/mdx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The `leadtype/mdx` subpath is the **consumer-facing MDX surface** — everything
It exports three things:

1. **Tag type contracts** — typed prop shapes for every custom MDX tag.
2. **`mdxSourcePlugins`** — a remark preset that performs build-time resolution only (expand includes, resolve `<ExtractedTypeTable>`, strip authoring `import`s) and leaves every other custom tag as JSX.
2. **`createMdxSourcePlugins()` / `mdxSourcePlugins`** — a remark preset that performs build-time resolution only (expand includes, resolve `<ExtractedTypeTable>`, strip authoring `import`s) and leaves every other custom tag as JSX.
3. **Include-resolution helpers** — `resolveInclude`, `parseIncludeSpecifier`, `extractMdxSection` for direct use.

Pair it with `leadtype/remark` (markdown flattening for the agent/LLM pipeline) — they are sibling surfaces, not alternatives. Most projects use both.
Expand All @@ -20,15 +20,22 @@ Pair it with `leadtype/remark` (markdown flattening for the agent/LLM pipeline)

```ts
import createMDX from "@next/mdx";
import { mdxSourcePlugins } from "leadtype/mdx";
import { createMdxSourcePlugins } from "leadtype/mdx";
import path from "node:path";

const typeTableBasePath = path.resolve(process.cwd(), ".c15t");

const withMDX = createMDX({
options: {
remarkPlugins: [...mdxSourcePlugins],
remarkPlugins: [
...createMdxSourcePlugins({ typeTableBasePath }),
],
},
});
```

Use the `mdxSourcePlugins` constant only when the default path inference is enough for your project. When `<ExtractedTypeTable path="./packages/..." />` should resolve from a known source root (`.c15t`, `.leadtype`, the repo root, or a docs folder), prefer `createMdxSourcePlugins({ typeTableBasePath })`. If you intentionally keep type files inside `docs/`, pass `path.resolve(process.cwd(), "docs")`.

The preset is intentionally minimal — only the transforms that **must** run at build time:

| Plugin | Purpose |
Expand Down Expand Up @@ -218,7 +225,7 @@ export function TypeTable({ properties, title, description }: TypeTableProps) {
}
```

`<ExtractedTypeTable>` requires `typescript` to be installed as an optional peer dep in your docs project.
`<ExtractedTypeTable>` requires `typescript` to be installed as an optional peer dep in your docs project. Failed extraction emits a visible warning by default; pass `typeTableStrict: true` to `createMdxSourcePlugins()` when a missing type should fail the build.

## Include resolution

Expand Down
7 changes: 5 additions & 2 deletions docs/reference/remark.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Nested includes are supported. The plugin keeps the included file's directory as

### remarkTypeTableToMarkdown (with basePath)

`<ExtractedTypeTable>` reads a TypeScript file at conversion time. When the file path is relative, the plugin needs a `basePath` to resolve from. Override the default plugin entry to pass options:
`<ExtractedTypeTable>` reads a TypeScript file at conversion time. When the file path is relative, Leadtype defaults the base path to the source root inferred from the current MDX file. Override the plugin entry when your conversion script should resolve from a specific root or fail on missing types:

```ts
import {
Expand All @@ -141,7 +141,10 @@ import {

const remarkPlugins = [
...defaultRemarkPlugins.filter((p) => p !== remarkTypeTableToMarkdown),
[remarkTypeTableToMarkdown, { basePath: process.cwd() }],
[
remarkTypeTableToMarkdown,
{ basePath: process.cwd(), strict: true },
],
];
```

Expand Down
Loading
Loading