Add OpenAPI docs generation#123
Conversation
📝 WalkthroughWalkthroughThis PR adds native OpenAPI 3.x API reference generation, including MDX page creation, markdown flattening for Api* components, source/CLI staging, app rendering support, and documentation/config updates. ChangesOpenAPI Reference Generation
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant DocsConfig as docs.docs.config.ts
participant OpenApi as packages/leadtype/src/openapi/index.ts
participant Source as packages/leadtype/src/source/index.ts
participant CLI as packages/leadtype/src/cli/generate.ts
participant AppRoute as apps/tanstack/src/routes/docs/$.tsx
DocsConfig->>OpenApi: normalizeOpenApiConfig()
OpenApi->>OpenApi: load spec, dereference $refs, normalize pages
Source->>OpenApi: stageOpenApiDocs()
OpenApi-->>Source: temp overlay pages + nav
CLI->>OpenApi: writeOpenApiPages()
AppRoute->>AppRoute: merge authored and generated MDX modules
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
- Register Api* handlers in the native markdown dispatcher so generated
pages flatten into agent-readable markdown (llms.txt, search, bundles)
- Escape MDX-significant syntax in operation prose so CommonMark
descriptions ({id}, <token>) cannot break the docs build
- Summarize nested schemas (array items, allOf merge) and render dotted
property rows (results[].title) in both renderers
- Generate real code samples: auth headers from security schemes,
synthesized JSON bodies, required query params, x-codeSamples support
- Render named examples and synthesize request/response examples from
schemas for JSON media types
- Drop the duplicate body h1; frontmatter description uses the summary
- Validate openapi config in the CLI with descriptive errors
- Add stageOpenApiDocs() and accept openapi config in createDocsSource /
fumadocsSource; simplify the package build and fumadocs example
- Share slug collision state across specs, allow webhooks-only 3.1 docs,
reject Swagger 2.0 with a conversion hint
- Expand tests: flatten round-trip, MDX-unsafe prose, external/circular
refs, tag filters, slug collisions, code samples, staging, validation
- Frontmatter description prefers the description's first paragraph over the summary (which usually mirrors the title), capped at 250 chars - Render server URL and operation ID as inline code in flattened markdown so URLs avoid escaping artifacts
The example's docs:generate output (sitemap, robots.txt, mcp.json, feeds, schema-map) was not covered by .gitignore and slipped into the previous commit. Remove it from tracking and ignore the artifact paths.
- Add Api* renderer components (data-attribute styling, tabbed code samples via the app's Tabs) and register them in the MDX map - Write generated API pages into src/generated/openapi-docs so Vite's static import.meta.glob can compile them; merge a second glob in the docs catch-all route and append manifest entries with matching globKeys - Flatten the generated pages into public/docs mirrors (pipeline:convert) so search and agent artifacts include them - Stage OpenAPI docs in pipeline:llm so llms.txt, docs-nav.json, and the agent-readability manifest carry the generated section; skills keep resolving from the real repo root - Skip the body description on generated pages when it matches the frontmatter description (docs UIs already print it under the title)
Closes the fidelity gap against Vercel-style REST API markdown: - Ship the dereferenced JSON Schema for request/response bodies (full enums, formats, constraints) in flattened markdown and as collapsible blocks in renderers; disable with includeSchemas: false - Machine-scannable frontmatter on every generated page: type, method, path, operationId, server, apiVersion, tags, deprecated - Generate an overview index page per source listing operations grouped by tag, wired into nav as the section landing page; operation pages gain a Related section linking the overview (and remote spec URLs); links use the new urlPrefix option (default /docs) - Render response headers tables in the fumadocs and tanstack renderers (the flattener already did) - Label examples and skip body descriptions that merely repeat the frontmatter (whitespace-insensitive) - Enrich the dogfood spec: second endpoint, $ref schemas, enums, formats, defaults, named examples, response headers, 401/404 errors
- New baseUrl option (per-source or via normalizeOpenApiConfig defaults) adds canonicalUrl frontmatter to operation and overview pages; the CLI forwards --base-url and createDocsSource forwards its own baseUrl - lastModified comes from the spec file's latest git commit date, falling back to file mtime; remote specs omit it rather than faking freshness from fetch time - Thread baseUrl through stageOpenApiDocs and the TanStack pipeline scripts
source now carries the spec path or URL the page was generated from (agents can fetch the machine-readable contract directly) instead of the literal "openapi"; drop the redundant generated flag — type: api-reference already marks these pages.
- Reference page: generated frontmatter example, static-glob bundler recipe (TanStack Start / Vite), overview page output, and both example component maps - Components naming contract: add the Api* tags with a pointer to the OpenAPI reference - Changelog 0.4: OpenAPI API reference generation section
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10d54dbdab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const openapi = validateDocsOpenApiConfig( | ||
| value.openapi, | ||
| `docs config at "${configPath}"` | ||
| ); |
There was a problem hiding this comment.
Allow OpenAPI-only docs configs
When a config contains only product + openapi—the quickstart shape documented in docs/reference/openapi.mdx—validateDocsConfig throws at the existing groups/navigation check before this new OpenAPI validation runs. That makes leadtype generate unusable for OpenAPI-only docs unless users add dummy groups or navigation, even though the generated OpenAPI nav is appended later.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 904597e — validateDocsConfig now accepts configs with openapi set and no groups/navigation (the documented quickstart shape); product-only configs without openapi still fail as before. Regression tests added in cli.test.ts.
| const staged = await stageOpenApiDocs({ | ||
| contentDir: sourceContentDir, | ||
| cwd: config.openapiCwd, | ||
| openapi: config.openapi, | ||
| ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}), | ||
| }); | ||
| contentDir = staged.contentDir; | ||
| nav = [...(config.nav ?? []), ...staged.nav]; |
There was a problem hiding this comment.
Keep a cleanup path for staged OpenAPI sources
When createDocsSource({ openapi }) is used, stageOpenApiDocs creates a full temp copy and returns a cleanup callback, but this block only keeps contentDir and nav. Since the returned DocsSource exposes no cleanup method, consumers such as fumadocsSource in dev/server processes cannot remove /tmp/leadtype-openapi-*, so every construction or hot reload leaks the staged docs tree.
Useful? React with 👍 / 👎.
| const allParameters = [ | ||
| ...pathParameters, | ||
| ...(Array.isArray(operation.parameters) ? operation.parameters : []), | ||
| ]; |
There was a problem hiding this comment.
Let operation parameters override path parameters
For a path that defines a parameter and an operation redeclares the same name/in with a narrower schema, example, or description, OpenAPI treats the operation parameter as the override. This processes pathParameters first and then drops duplicates, so generated parameter tables and code samples keep the path-level definition and ignore the operation-specific one.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Caution
This PR generates MDX component attributes from spec-derived strings using JS/JSON string escaping, which JSX/MDX attribute strings do not honor. A " in an untrusted spec's operationId, path, or serverUrl breaks out of the attribute and can inject arbitrary JSX/expressions into the compiled page — build-time code execution. This directly contradicts the PR's "arbitrary specs can't break the build" guarantee. See the inline comment on mdxProp.
Reviewed changes — a new native OpenAPI 3.x → MDX API-reference generator wired into the docs pipeline, example apps, and downstream agent artifacts.
- Core generator —
packages/leadtype/src/openapi/index.ts(new, ~2004 lines): YAML load, cycle-guarded$refdereferencing (incl. external file/URL refs), schema summarization (allOf/oneOf/anyOf), example synthesis, curl/fetch code samples, slug/collision handling, and MDX/nav/frontmatter rendering. - Flatteners —
packages/leadtype/src/markdown/plugins/openapi.ts(new): markdown flatteners for the sevenApi*components, reading attrs viagetAttributeValue+JSON5.parse. - Source integration —
createDocsSource/fumadocsSourcegainopenapi/openapiCwd, staging a temp copy of the docs dir and appending generated nav. - Pipeline & contract wiring — CLI
generateforces staging whenopenapiis set; the sevenApi*names are registered consistently across tag-types, dispatcher, default-transforms, and both example apps; newleadtype/openapisubpath export. - Dogfood + docs —
docs/openapi/leadtype-api.yamlplus reference/changelog/components docs.
ℹ️ $ref resolution is an unrestricted local-file-read and outbound-fetch primitive
External $refs trigger fetch() (any URL) and readFile() (any resolvable path, including ../../.. traversal), with no allowlist, no root confinement, no timeout, and no size cap. A resolved local file that happens to YAML-parse into an object is dereferenced into the operation's schema and rendered into the public docs page.
- For a build-time tool driven by a trusted docs-config author this matches how swagger-parser / Redocly behave and is likely acceptable.
- This PR is the first introduction of both
fetch()and arbitrary-file-read into this package, so the trust boundary is worth an explicit decision rather than an implicit one.
Technical details
# `$ref` resolution has no confinement
## Affected sites
- `packages/leadtype/src/openapi/index.ts` `readInput` (~L428-454) — `fetch(input)` on any `https?://`; `readFile(path.resolve(cwd, input))` on any local path.
- `packages/leadtype/src/openapi/index.ts` `loadExternalRef` (~L456-492) — resolves `$ref` targets relative to the referring doc with no jail.
## Required outcome
- A documented, intentional trust boundary for spec sources.
## Open questions for the human
- Are ingested specs ever untrusted (e.g. a hosted-docs product accepting customer specs)? If so, this needs a root-jail for file refs, a host allowlist + timeout for URL refs, and a size cap. If specs are always first-party/trusted, a one-line note in `docs/reference/openapi.mdx` stating that is enough.Claude Opus | 𝕏
|
|
||
| function mdxProp(name: string, value: unknown): string { | ||
| if (typeof value === "string") { | ||
| return `${name}=${JSON.stringify(value)}`; |
There was a problem hiding this comment.
🚨 String props are serialized with JSON.stringify, which escapes an embedded " as \". JSX/MDX attribute strings do not honor backslash escapes — the attribute value ends at the first raw ", so the rest of a crafted string is parsed as new JSX attributes (including {…} expression containers). operation.path, operation.operationId, and operation.serverUrl flow straight from the spec into <ApiEndpoint> (renderOperationMdx, ~L1646) and none of them pass through escapeMarkdownForMdx. A spec containing a " in any of these can therefore break the build or inject arbitrary JSX/JS that executes at MDX-compile time. This breaks the PR's stated "arbitrary specs can't break the build" guarantee.
Technical details
# JSX attribute-string injection via `mdxProp`
## Affected sites
- `packages/leadtype/src/openapi/index.ts:1496-1503` — `mdxProp` string branch returns `` `${name}=${JSON.stringify(value)}` ``.
- Fed by `renderMdxComponent("ApiEndpoint", …)` at ~L1646-1652 with unsanitized `path` / `operationId` / `serverUrl`.
## Why it's exploitable
Example `operationId`:
```
readUser" onLoad={(() => globalThis.process.exit(1))()} x="
```
serializes to `operationId="readUser\" onLoad={…} x=\""`. The JSX parser closes the string at the raw `"` after `readUser\`, then reads `onLoad={…}` as a real attribute whose expression is evaluated when the page compiles/renders.
## Required outcome
- Spec-derived string attribute values must be encoded so no spec content can terminate the attribute or introduce new attributes/expressions.
## Suggested approach
- Add a JSX-attribute-safe encoder for the string branch: keep the `name="…"` shape but HTML-encode `"` (→ `"`) — inside a double-quoted JSX attribute `"` is the only terminator, and both React and the mdast attribute readers decode the entity back to `"`.
- Do **NOT** switch string props to a `{JSON.stringify(value)}` expression container: the flatteners in `markdown/plugins/openapi.ts` read `method`/`path`/`operationId`/`serverUrl` as plain string attributes via `getAttributeValue`, which returns the raw expression text (with quotes) for expression-valued attributes and would break flattening.
- Add a test feeding a `"` (and `<`/`{`) through `operationId`/`serverUrl`.There was a problem hiding this comment.
Fixed in 904597e — mdxProp string props now go through a JSX-attribute encoder (& → &, then "" → "), keeping the plain-string attribute shape the markdown flatteners rely on. Tests cover a quote in operationIdround-tripping through the flattener and</{/&inserverUrl`.
| const relativePath = normalizeDocsPath( | ||
| path.posix.join(config.output, "index.mdx") | ||
| ); | ||
| if (!usedPaths.has(relativePath.toLowerCase())) { |
There was a problem hiding this comment.
output is api for every source, so openapi: [a, b] with no explicit output hits this), the second source's overview page is silently dropped here — usedPaths already contains api/index.mdx from the first source, so the guard skips it. But buildGeneratedNav (~L1846) unconditionally emits pages: ["index"] under the same base, so the second source contributes a nav node pointing at a page that was never written for it, and its operations never appear in any overview.
Technical details
# Overview page dropped / nav dangles across specs sharing an output dir
## Affected sites
- `packages/leadtype/src/openapi/index.ts:1888-1906` — index page is created only when `usedPaths` doesn't already hold `<output>/index.mdx`; operation pages get collision suffixes but the index page does not.
- `packages/leadtype/src/openapi/index.ts:1823` and `:1846` — `buildGeneratedNav` always lists `"index"` and receives no `usedPaths`, so it can't know the index wasn't written.
## Required outcome
- Two specs sharing an output dir must not silently lose the second overview page, and no emitted nav node may reference a page that wasn't written.
## Suggested approach
- Either collision-suffix the index page like operation pages (and thread the resulting slug into the nav node), or require/validate distinct `output` per source, or merge multiple specs targeting one output into a single combined overview + nav node.
## Open questions for the human
- Is multiple specs → one output dir a supported configuration, or should it be rejected at config-validation time?There was a problem hiding this comment.
Fixed in 904597e — overview pages now collision-suffix like operation pages (index-2.mdx, …) when specs share an output dir, and the actually-written overview slug is threaded through buildGeneratedNav, canonicalUrl, and each operation page's Related links, so every nav node points at a written page and each spec's operations link to their own overview. Covered by a two-specs-shared-output test.
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/fumadocs-example/lib/mdx-components.tsx`:
- Around line 125-479: The API docs rendering in this file is duplicated with
the TanStack app, including flattenSchemaRows, formatApiSchemaType, JsonExample,
MediaType, MediaTypeExamples, and the Api* components, so changes to schema
handling must be made twice. Extract the shared schema/documentation logic into
the leadtype package next to the ApiSchemaProperty types, and have this file and
the TanStack implementation consume that shared helper/render layer while
keeping only app-specific styling differences here.
- Around line 259-266: The ApiAuth requirements list in the requirements.map
render can produce duplicate React keys because key={names.join("+") ||
"anonymous"} is not guaranteed unique for repeated or identical scope sets.
Update the li key generation in mdx-components.tsx to include the map index
alongside the existing names.join("+") / "anonymous" value so each rendered
requirement has a unique key even when multiple entries share the same scopes or
are anonymous.
In `@apps/tanstack/scripts/llm-generate.ts`:
- Around line 198-199: The cleanup in llm-generate.ts is only called after all
generation steps, so it can be skipped if generateLlmsTxt,
generateLLMFullContextFiles, generateAgentReadabilityArtifacts,
generateSkillArtifacts, or resolveDocsNavigation throws. Wrap the main
generation flow in a try/finally block in llm-generate.ts and move
staged?.cleanup() into the finally, mirroring the pattern used in
generate-docs.ts so the temp directory from stageOpenApiDocs is always released.
In `@apps/tanstack/src/components/docs-mdx/api.tsx`:
- Around line 24-124: The schema-flattening logic in SchemaTable is duplicated
from the fumadocs example implementation; extract the shared MAX_SCHEMA_DEPTH,
formatSchemaType, and flattenSchemaRows behavior into a common helper and have
this api.tsx component consume it instead of maintaining a second copy. Keep the
local SchemaTable markup and JsonExample rendering here, but reference the
shared helper for building rows so both apps stay in sync.
- Around line 213-217: The requirements list in the api.tsx renderer uses a
non-unique React key in the map callback, which can collide for repeated
Anonymous or identical label entries. Update the requirements.map callback to
include the loop index alongside the existing label when setting the key in the
list item, so each rendered requirement in this block gets a stable unique key.
In `@packages/leadtype/src/openapi/index.ts`:
- Around line 1979-2004: The temp staging directory created in stageOpenApiDocs
can be left behind when writeOpenApiPages throws before cleanup is returned.
Wrap the staging work in a try/finally around the mkdtemp, cp, and
writeOpenApiPages flow, and ensure rm(stagedRoot, { force: true, recursive: true
}) runs on any failure. Keep the existing returned cleanup for successful calls,
but make the failure path in stageOpenApiDocs always release the stagedRoot and
stagedContentDir resources.
- Around line 428-454: readInput() currently fetches HTTP(S) OpenAPI specs with
no timeout, so an unresponsive URL can hang docs generation. Update the remote
branch in readInput to use AbortSignal.timeout(...) with fetch(input), and
handle the resulting abort/error with a clear OpenAPI message; make sure
recursive $ref loads still go through the same timed readInput path.
In `@packages/leadtype/src/openapi/openapi.test.ts`:
- Around line 551-585: Add a failure-path test for stageOpenApiDocs in
openapi.test.ts: the current coverage only verifies the success case. Create a
case where writeOpenApiPages throws (for example with an invalid OpenAPI spec)
and assert that the temporary staged contentDir is cleaned up even when staging
fails. Use the stageOpenApiDocs and writeOpenApiPages symbols to locate the
relevant flow and verify cleanup behavior.
In `@packages/leadtype/src/source/index.ts`:
- Around line 395-424: createDocsSource currently stages OpenAPI content via
stageOpenApiDocs but never exposes the disposer, so temporary source directories
can leak across repeated builds. Add an optional cleanup() method to the
DocsSource returned by createDocsSource, wire it to the disposer from
stageOpenApiDocs, and make sure leadtype/fumadocs forwards and invokes that
cleanup when OpenAPI staging is used.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2c38b59d-c8f1-4a45-938a-63080bed6fda
📒 Files selected for processing (31)
.changeset/openapi-generated-docs.md.gitignoreapps/fumadocs-example/lib/mdx-components.tsxapps/fumadocs-example/lib/source.tsapps/tanstack/scripts/docs-source-manifest.tsapps/tanstack/scripts/llm-generate.tsapps/tanstack/scripts/mdx-convert.tsapps/tanstack/src/components/docs-mdx/api.tsxapps/tanstack/src/components/docs-mdx/mdx-components.tsapps/tanstack/src/routes/docs/$.tsxapps/tanstack/src/styles.cssdocs/changelog/0-4.mdxdocs/docs.config.tsdocs/openapi/leadtype-api.yamldocs/reference/openapi.mdxdocs/writing/components.mdxpackages/leadtype/package.jsonpackages/leadtype/rollup.config.tspackages/leadtype/scripts/generate-docs.tspackages/leadtype/src/cli/generate.tspackages/leadtype/src/index.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/llm/llm.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/plugins/openapi.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/openapi/index.tspackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/source/index.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/rollup.config.tsapps/tanstack/src/routes/docs/$.tsxpackages/leadtype/src/mdx/index.tspackages/leadtype/src/internal/package-surface.test.tsapps/tanstack/scripts/mdx-convert.tsdocs/docs.config.tsapps/tanstack/src/components/docs-mdx/mdx-components.tsapps/fumadocs-example/lib/source.tspackages/leadtype/src/index.tspackages/leadtype/src/llm/llm.tsapps/tanstack/scripts/docs-source-manifest.tspackages/leadtype/src/markdown/default-transforms.tsapps/tanstack/scripts/llm-generate.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/source/index.tspackages/leadtype/scripts/generate-docs.tsapps/fumadocs-example/lib/mdx-components.tsxapps/tanstack/src/components/docs-mdx/api.tsxpackages/leadtype/src/markdown/plugins/openapi.tspackages/leadtype/src/cli/generate.tspackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/openapi/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/rollup.config.tsapps/tanstack/src/routes/docs/$.tsxpackages/leadtype/src/mdx/index.tspackages/leadtype/src/internal/package-surface.test.tsapps/tanstack/scripts/mdx-convert.tsdocs/docs.config.tsapps/tanstack/src/components/docs-mdx/mdx-components.tsapps/fumadocs-example/lib/source.tspackages/leadtype/src/index.tspackages/leadtype/src/llm/llm.tsapps/tanstack/scripts/docs-source-manifest.tspackages/leadtype/src/markdown/default-transforms.tsapps/tanstack/scripts/llm-generate.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/source/index.tspackages/leadtype/scripts/generate-docs.tsapps/fumadocs-example/lib/mdx-components.tsxapps/tanstack/src/components/docs-mdx/api.tsxpackages/leadtype/src/markdown/plugins/openapi.tspackages/leadtype/src/cli/generate.tspackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/openapi/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/tanstack/src/routes/docs/$.tsxapps/fumadocs-example/lib/mdx-components.tsxapps/tanstack/src/components/docs-mdx/api.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/tanstack/src/routes/docs/$.tsxapps/fumadocs-example/lib/mdx-components.tsxapps/tanstack/src/components/docs-mdx/api.tsx
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/mdx/index.tspackages/leadtype/src/index.tspackages/leadtype/src/source/index.tspackages/leadtype/src/openapi/index.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/openapi/openapi.test.ts
🧠 Learnings (1)
📚 Learning: 2026-06-09T18:30:08.038Z
Learnt from: KayleeWilliams
Repo: inthhq/leadtype PR: 97
File: .changeset/search-prototype-safety-and-scaling.md:5-5
Timestamp: 2026-06-09T18:30:08.038Z
Learning: In this repo, `.changeset/*.md` files must not start the body with an H1/first-line heading (`#`) immediately after the YAML frontmatter. The changesets tool inlines the body as bullet entries into `CHANGELOG.md` during release, and a leading `#` heading would break the generated changelog format. As a result, MD041 (`first-line-heading`) warnings for files under `.changeset/` are expected false positives and should be ignored.
Applied to files:
.changeset/openapi-generated-docs.md
🪛 ast-grep (0.44.0)
packages/leadtype/src/openapi/index.ts
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🪛 Biome (2.5.1)
apps/tanstack/src/styles.css
[error] 672-672: Tailwind-specific syntax is disabled.
(parse)
[error] 676-676: Tailwind-specific syntax is disabled.
(parse)
[error] 680-680: Tailwind-specific syntax is disabled.
(parse)
[error] 717-717: Tailwind-specific syntax is disabled.
(parse)
[error] 721-721: Tailwind-specific syntax is disabled.
(parse)
[error] 725-725: Tailwind-specific syntax is disabled.
(parse)
[error] 729-729: Tailwind-specific syntax is disabled.
(parse)
[error] 733-733: Tailwind-specific syntax is disabled.
(parse)
[error] 738-738: Tailwind-specific syntax is disabled.
(parse)
[error] 742-742: Tailwind-specific syntax is disabled.
(parse)
[error] 746-746: Tailwind-specific syntax is disabled.
(parse)
[error] 750-750: Tailwind-specific syntax is disabled.
(parse)
[error] 754-754: Tailwind-specific syntax is disabled.
(parse)
[error] 758-758: Tailwind-specific syntax is disabled.
(parse)
[error] 762-762: Tailwind-specific syntax is disabled.
(parse)
🪛 Checkov (3.3.2)
docs/openapi/leadtype-api.yaml
[high] 1-196: Ensure that the global security field has rules defined
(CKV_OPENAPI_4)
[medium] 104-109: Ensure that arrays have a maximum number of items
(CKV_OPENAPI_21)
🪛 LanguageTool
docs/writing/components.mdx
[grammar] ~326-~326: Ensure spelling is correct
Context: ...ocs/reference/mdx) (ApiEndpointProps, ApiResponsesProps, …). tsx <ApiEndpoint method="get" path="/users/{id}" operationId="readUser" /> ### Mermaid Diagrams authored as plain text...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.22.1)
.changeset/openapi-generated-docs.md
[warning] 5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🔇 Additional comments (38)
.changeset/openapi-generated-docs.md (1)
1-9: LGTM!docs/changelog/0-4.mdx (1)
12-19: LGTM!docs/docs.config.ts (1)
27-34: LGTM!Also applies to: 257-257
docs/openapi/leadtype-api.yaml (2)
133-159: 🎯 Functional Correctness | ⚡ Quick winPath parameter contains a literal slash.
urlPathis declared as a plain path parameter (in: path, defaultstyle: simple) but its example (docs/quickstart) contains an embedded/. Standard path-param matching (and most routers) treats/as a segment delimiter, so a literal value likedocs/quickstartwon't match a single{urlPath}segment withoutstyle: simple; allowReserved: true(which most tooling still doesn't fully support) or restructuring the route as a wildcard/catch-all.Since this is a dogfood/example spec used to demonstrate page generation, verify whether this is intentional illustrative text or should be corrected (e.g.
.../pages/{urlPath+}convention, or documenting the required escaping).
1-195: LGTM!docs/reference/openapi.mdx (1)
1-367: LGTM!docs/writing/components.mdx (1)
23-27: LGTM!Also applies to: 324-331
.gitignore (1)
44-49: 🎯 Functional CorrectnessOpenAPI output is already ignored.
apps/tanstack/.gitignorecoverssrc/generated/, sosrc/generated/openapi-docsis already excluded; the root.gitignoreonly needs thepublic/artifacts.> Likely an incorrect or invalid review comment.apps/fumadocs-example/lib/mdx-components.tsx (2)
10-18: LGTM!
718-724: LGTM!apps/fumadocs-example/lib/source.ts (1)
14-24: LGTM!apps/tanstack/src/components/docs-mdx/api.tsx (1)
1-9: LGTM!Also applies to: 164-366
apps/tanstack/src/components/docs-mdx/mdx-components.ts (1)
2-10: LGTM!Also applies to: 28-34
apps/tanstack/src/routes/docs/$.tsx (1)
34-51: LGTM!apps/tanstack/src/styles.css (1)
670-764: 🩺 Stability & Availability
@applyis already used elsewhere inapps/tanstack/src/styles.css, and there’s no CSS/Tailwind-specific Biome override in the repo, so this isn’t a block-specific lint issue.> Likely an incorrect or invalid review comment.packages/leadtype/src/openapi/index.ts (2)
456-492: 🔒 Security & PrivacyUnbounded local/remote
$refresolution — confirm trust model.
loadExternalRefresolves relative local paths viapath.resolve(baseDir, targetInput)with no containment check, and recursively fetches remote URLs referenced inside an already-loaded document with no allowlist. If any OpenAPI spec fed into this engine (directly or via a chain of$refs) can come from a less-trusted source (contributor-submitted spec, third-party partner spec fetched by URL, etc.), this allows arbitrary local file reads (path traversal, leaking file contents into public generated docs) and/or SSRF (fetching internal-only URLs from within$refs during doc generation).Please confirm whether
openapi.input/$reftargets are always fully trusted (e.g. only authored by the docs-site owner) in every integration point (CLI,createDocsSource,stageOpenApiDocs). If any less-trusted path exists, consider constraining$refresolution to a configured root directory and/or an allowlist of remote hosts.
319-401: LGTM!Also applies to: 494-534, 1460-1490
packages/leadtype/src/openapi/openapi.test.ts (1)
1-658: LGTM!packages/leadtype/src/markdown/plugins/openapi.ts (2)
1-345: LGTM!
219-238: 🎯 Functional CorrectnessDeprecated shorthand is already handled; no change needed.
> Likely an incorrect or invalid review comment.packages/leadtype/src/markdown/component-dispatcher.ts (1)
19-27: LGTM!Also applies to: 86-92
packages/leadtype/src/markdown/default-transforms.ts (1)
25-25: LGTM!Also applies to: 59-67, 95-95, 108-114
packages/leadtype/src/llm/llm.ts (1)
33-33: LGTM!Also applies to: 477-481
packages/leadtype/src/mdx/tag-types.ts (1)
22-32: LGTM!Also applies to: 149-190
packages/leadtype/src/mdx/index.ts (1)
55-63: LGTM!packages/leadtype/src/index.ts (1)
7-7: LGTM!Type re-exports match the OpenApi* types consumed by
tag-types.ts, and the omission of runtime functions (stageOpenApiDocs,writeOpenApiPages, etc.) from the root export is consistent with downstream consumers importing them directly fromleadtype/openapi.Also applies to: 74-97
packages/leadtype/package.json (1)
70-73: LGTM!packages/leadtype/rollup.config.ts (1)
18-18: LGTM!packages/leadtype/src/internal/package-surface.test.ts (1)
91-94: LGTM!packages/leadtype/src/source/index.ts (1)
146-156: LGTM!Also applies to: 522-522
packages/leadtype/src/cli/generate.ts (3)
74-79: LGTM!Also applies to: 309-309, 1305-1326, 1861-1861, 2338-2353
2630-2646: 🗄️ Data Integrity & IntegrationGenerated OpenAPI pages may silently overwrite authored files in the mirror.
sourceMirror.docsDiris first populated with copied authored source files (viacopySourceFiles/copyFilteredSourceFiles), thenwriteOpenApiPageswrites generated MDX into the same directory tree. Based on thewriteOpenApiPagesimplementation (via../openapi), collision handling (usedPaths) only guards against collisions among generated pages across multiple specs — it doesn't check whether a path already exists from an authored file before callingwriteFile. If an authored doc happens to share a relative path with a generated operation page, it would be overwritten without warning.Please confirm whether the underlying
generateOpenApiPages/writeOpenApiPages(inpackages/leadtype/src/openapi/index.ts) checks for pre-existing files at the target path, and if not, consider surfacing a clear error rather than a silent overwrite.
2630-2669: 🎯 Functional CorrectnessOpenAPI reference generation bypasses
--include/--excludepath filters.
writeOpenApiPagesruns unconditionally whenevermetadata.openapiis set, regardless ofhasExplicitPathFilters— the generated pages land insourceMirror.docsDireven when the mirror was built from a filtered subset of authored docs, andconvertAllMdxwill convert everything present in that directory. So a scoped/filteredleadtype generate --include "guides/**"run still gets the entire OpenAPI reference bolted on, even thougheffectiveNavcorrectly drops the OpenAPI nav in that case (Line 2667-2669) — leaving the pages present in output but orphaned from nav.Is this intentional (OpenAPI reference is always "whole" regardless of filtering) or should OpenAPI page generation itself respect
args.include/args.exclude?packages/leadtype/scripts/generate-docs.ts (1)
15-15: LGTM!Also applies to: 32-136
apps/tanstack/scripts/docs-source-manifest.ts (2)
16-46: LGTM!
67-108: 🗄️ Data Integrity & IntegrationNo collision check between authored and generated OpenAPI manifest entries.
Generated OpenAPI entries are pushed into
manifestwithout checking whether their derivedurlPath/slugalready exists among the authoredpagesentries. If an authored doc and a generated operation page resolve to the same route, the runtime catch-all lookup (routes/docs/$.tsx, not in this file set) could get ambiguous/duplicate manifest entries for the same URL.Worth confirming whether this is already prevented upstream (e.g. by convention that OpenAPI-generated paths always live under a distinct prefix) or should be validated here.
apps/tanstack/scripts/llm-generate.ts (1)
23-55: LGTM!Also applies to: 93-101, 112-112, 126-128, 165-165
apps/tanstack/scripts/mdx-convert.ts (1)
18-27: LGTM!Also applies to: 57-77
- Substitute path parameters in generated cURL/fetch samples - Let operation-level parameters override path-item parameters - Replace full-tree staging in createDocsSource with a temp overlay: authored docs read live, cleanup() + process-exit sweep, nav resolved via new resolveDocsNavigation extraDocsDirs option - Share one schema-row flattener via dependency-free leadtype/mdx/openapi - Make spec-not-found errors name the resolved path and cwd options - Serialize generated frontmatter with stringifyFrontmatter
There was a problem hiding this comment.
Important
The new commit cleanly fixes several findings and adds no new issues, but the two highest-severity findings from the prior review remain unaddressed and still block merge (both threads are still open below).
Reviewed changes — the single new commit 0c29fca ("Fix review findings in OpenAPI generation") on top of the prior-review head 10d54db.
- Shared schema-row flattener — extracted
flattenApiSchemaRows()intomdx/openapi-schema-rows.ts, exported vialeadtype/mdxand the new dependency-freeleadtype/mdx/openapisubpath; both example apps and the markdown dispatcher now consume it instead of triplicated copies. - Overlay redesign for
createDocsSource/fumadocsSource— authored docs now read live fromcontentDirwhile generated pages are written to amkdtempoverlay root;DocsSourceexposes an idempotentcleanup()plus a singleprocess.on("exit")sweep, and nav resolves overlay pages viaresolveDocsNavigation({ extraDocsDirs }). This resolves the prior temp-dir-leak finding. - Parameter precedence — operation-level parameters now override path-item parameters in original order via a
parametersByKeyMap. - Code-sample path params —
codeSampleUrlsubstitutes specexamplevalues into{param}placeholders, falling back to the literal placeholder when no sample exists. - Frontmatter serialization — switched from hand-rolled
JSON.stringifyscalars to the sharedstringifyFrontmatter(yaml lib), which properly quotes/escapes; this closes the frontmatter injection surface. - Missing-spec error — now names the resolved path and the
openapiCwd/cwdescape hatches. Tests added for all of the above.
🚨 Prior critical finding still open — MDX/JSX attribute injection in mdxProp
mdxProp (packages/leadtype/src/openapi/index.ts:1511-1519) still serializes string props as `${name}=${JSON.stringify(value)}`, and renderMdxComponent("ApiEndpoint", …) at :1655-1661 still feeds operation.path, operationId, and serverUrl through it unescaped. JSX attribute strings do not honor backslash escapes, so a " in any of these spec-derived values remains a build-time code-execution vector. The frontmatter path is now safe via stringifyFrontmatter, but this JSX-attribute path is not. Details remain in the open thread on mdxProp.
⚠️ Prior important finding still open — multi-spec overview page dropped + dangling nav
When two specs share an output directory (the default output is api for every source), generateOpenApiPages still skips the second source's index.mdx via the usedPaths guard (:1904-1922) while buildGeneratedNav still unconditionally emits pages: ["index"] under the same base (:1839-1846, :1862). The second overview is lost and its nav node points at the first spec's overview. Details remain in the open thread.
Claude Opus | 𝕏
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/leadtype/src/openapi/index.ts (1)
1550-1596: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared base-frontmatter builder to reduce duplication.
renderFrontmatterandrenderIndexMdxindependently rebuild near-identical frontmatter scaffolding (title/description, conditionalgroup,type: "api-reference",source, conditionalcanonicalUrl, conditionallastModified) before callingstringifyFrontmatter. Extracting a smallbuildBaseFrontmatter(config, { title, description, canonicalUrl, lastModified })helper would remove the duplication and prevent the two call sites from drifting out of sync as fields are added.♻️ Sketch
+function buildBaseFrontmatter( + config: ResolvedOpenApiSourceConfig, + fields: { title: string; description: string; canonicalUrl?: string; lastModified?: string } +): Record<string, unknown> { + const frontmatter: Record<string, unknown> = { + title: fields.title, + description: fields.description, + }; + if (config.group) frontmatter.group = config.group; + frontmatter.type = "api-reference"; + frontmatter.source = config.input; + if (fields.canonicalUrl) frontmatter.canonicalUrl = fields.canonicalUrl; + if (fields.lastModified) frontmatter.lastModified = fields.lastModified; + return frontmatter; +}Also applies to: 1778-1796
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/openapi/index.ts` around lines 1550 - 1596, Extract the duplicated frontmatter assembly shared by renderFrontmatter and renderIndexMdx into a small helper like buildBaseFrontmatter so both paths use the same base fields. Have the helper populate the common title/description, conditional group, type, source, canonicalUrl, and lastModified fields, then let renderFrontmatter add operation-specific metadata such as method, path, operationId, server, apiVersion, tags, and deprecated. This will keep renderFrontmatter and renderIndexMdx aligned and prevent future drift.
♻️ Duplicate comments (5)
packages/leadtype/src/openapi/index.ts (2)
1995-2020: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winTemp staging directory still leaks on generation failure.
stageOpenApiDocsstill createsstagedRootand callswriteOpenApiPageswithout atry/finally. Any failure during generation (malformed spec, unresolved$ref, network error) throws beforecleanupis returned, permanently orphaning the staged temp directory. This was already flagged in a prior review and remains unaddressed in this revision.🩹 Suggested fix
const stagedRoot = await mkdtemp(path.join(tmpdir(), "leadtype-openapi-")); const stagedContentDir = path.join(stagedRoot, path.basename(sourceDir)); - await cp(sourceDir, stagedContentDir, { recursive: true }); - const result = await writeOpenApiPages({ - configs: normalizeOpenApiConfig(config.openapi, config.cwd ?? sourceDir, { - ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}), - }), - docsDir: stagedContentDir, - }); - return { - cleanup: async () => { - await rm(stagedRoot, { force: true, recursive: true }); - }, - contentDir: stagedContentDir, - indexPages: result.indexPages, - nav: result.nav, - pages: result.pages, - }; + try { + await cp(sourceDir, stagedContentDir, { recursive: true }); + const result = await writeOpenApiPages({ + configs: normalizeOpenApiConfig(config.openapi, config.cwd ?? sourceDir, { + ...(config.baseUrl ? { baseUrl: config.baseUrl } : {}), + }), + docsDir: stagedContentDir, + }); + return { + cleanup: async () => { + await rm(stagedRoot, { force: true, recursive: true }); + }, + contentDir: stagedContentDir, + indexPages: result.indexPages, + nav: result.nav, + pages: result.pages, + }; + } catch (error) { + await rm(stagedRoot, { force: true, recursive: true }); + throw error; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/openapi/index.ts` around lines 1995 - 2020, stageOpenApiDocs leaks the temporary staging directory if writeOpenApiPages throws before cleanup is returned. Update stageOpenApiDocs to wrap the staging and generation flow in a try/finally (or equivalent) so stagedRoot is removed on any failure, and make sure the cleanup logic still covers the success path via the returned cleanup function. Use the existing stageOpenApiDocs, mkdtemp, writeOpenApiPages, and cleanup symbols to place the fix.
429-462: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winImproved missing-spec error is good, but the fetch path still has no timeout.
The new resolved-path/cwd hint (Lines 447-454) is a solid improvement for local-file errors. However, the HTTP(S) branch (Lines 430-431) still calls
fetch(input)with no timeout — an unresponsive remote spec URL can hang docs generation indefinitely. This was already flagged in a prior review withAbortSignal.timeout(...)as the suggested fix.⏱️ Suggested fix
- const response = await fetch(input); + const response = await fetch(input, { signal: AbortSignal.timeout(10_000) });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/openapi/index.ts` around lines 429 - 462, The HTTP(S) branch in readInput still uses fetch(input) without any timeout, so update the remote-spec path to use an abortable request with a bounded timeout (for example via AbortSignal.timeout or equivalent) and surface a clear timeout error before continuing to parse the response; keep the existing fetch error handling and apply this change only in the readInput function’s URL-loading path.packages/leadtype/src/openapi/openapi.test.ts (1)
695-711: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStill missing a cleanup assertion for the
stageOpenApiDocsfailure path.This test exercises
stageOpenApiDocsthrowing on a missing spec, but doesn't assert that the staged temp directory was removed. Given the temp-dir leak on failure flagged inindex.ts(Lines 1995-2020) is still unresolved, this would be a good place to also assert cleanup, per the earlier recommendation to add failure-path coverage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/openapi/openapi.test.ts` around lines 695 - 711, The `stageOpenApiDocs` missing-spec failure test should also verify temp-directory cleanup on error. Update the `stageOpenApiDocs` failure-path coverage in `openapi.test.ts` to assert that the staged temp dir created by `createTempDir()` is removed after the promise rejects, using the same setup around `resolvedPath` and the missing `./missing.yaml` input. This will cover the unresolved cleanup behavior in `stageOpenApiDocs` and ensure the failure path does not leak staged files.apps/tanstack/src/components/docs-mdx/api.tsx (1)
20-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
formatSchemaTypeduplicated with fumadocsformatApiSchemaType.Same identical implementation exists in
apps/fumadocs-example/lib/mdx-components.tsx(lines 158-168). SinceflattenApiSchemaRowswas already successfully shared vialeadtype/mdx/openapi, extracting this formatter alongside it would remove the last bit of residual duplication.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/tanstack/src/components/docs-mdx/api.tsx` around lines 20 - 32, The logic in formatSchemaType is duplicated with fumadocs’ formatApiSchemaType, so extract this formatter into the shared leadtype/mdx/openapi module alongside flattenApiSchemaRows and update api.tsx to import and use the shared helper. Keep the behavior identical for schema.format/schema.type fallbacks, and remove the local formatSchemaType implementation so both docs stacks share the same source of truth.apps/fumadocs-example/lib/mdx-components.tsx (1)
158-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
formatApiSchemaTypestill duplicated with TanStack'sformatSchemaType.This helper is byte-for-byte identical to
formatSchemaTypeinapps/tanstack/src/components/docs-mdx/api.tsx(lines 24-31). The previous review's broader duplication concern was addressed by extractingflattenApiSchemaRowsinto the sharedleadtype/mdx/openapimodule — consider moving this small formatter there too so both apps stay in sync.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/fumadocs-example/lib/mdx-components.tsx` around lines 158 - 168, The helper formatApiSchemaType is still duplicated with TanStack’s formatSchemaType, so move this formatter into the shared leadtype/mdx/openapi module and have both mdx components import it instead of keeping separate copies. Update the usages in mdx-components and the TanStack docs API component to reference the shared formatter so the behavior stays aligned in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/leadtype/src/openapi/index.ts`:
- Around line 1550-1596: Extract the duplicated frontmatter assembly shared by
renderFrontmatter and renderIndexMdx into a small helper like
buildBaseFrontmatter so both paths use the same base fields. Have the helper
populate the common title/description, conditional group, type, source,
canonicalUrl, and lastModified fields, then let renderFrontmatter add
operation-specific metadata such as method, path, operationId, server,
apiVersion, tags, and deprecated. This will keep renderFrontmatter and
renderIndexMdx aligned and prevent future drift.
---
Duplicate comments:
In `@apps/fumadocs-example/lib/mdx-components.tsx`:
- Around line 158-168: The helper formatApiSchemaType is still duplicated with
TanStack’s formatSchemaType, so move this formatter into the shared
leadtype/mdx/openapi module and have both mdx components import it instead of
keeping separate copies. Update the usages in mdx-components and the TanStack
docs API component to reference the shared formatter so the behavior stays
aligned in one place.
In `@apps/tanstack/src/components/docs-mdx/api.tsx`:
- Around line 20-32: The logic in formatSchemaType is duplicated with fumadocs’
formatApiSchemaType, so extract this formatter into the shared
leadtype/mdx/openapi module alongside flattenApiSchemaRows and update api.tsx to
import and use the shared helper. Keep the behavior identical for
schema.format/schema.type fallbacks, and remove the local formatSchemaType
implementation so both docs stacks share the same source of truth.
In `@packages/leadtype/src/openapi/index.ts`:
- Around line 1995-2020: stageOpenApiDocs leaks the temporary staging directory
if writeOpenApiPages throws before cleanup is returned. Update stageOpenApiDocs
to wrap the staging and generation flow in a try/finally (or equivalent) so
stagedRoot is removed on any failure, and make sure the cleanup logic still
covers the success path via the returned cleanup function. Use the existing
stageOpenApiDocs, mkdtemp, writeOpenApiPages, and cleanup symbols to place the
fix.
- Around line 429-462: The HTTP(S) branch in readInput still uses fetch(input)
without any timeout, so update the remote-spec path to use an abortable request
with a bounded timeout (for example via AbortSignal.timeout or equivalent) and
surface a clear timeout error before continuing to parse the response; keep the
existing fetch error handling and apply this change only in the readInput
function’s URL-loading path.
In `@packages/leadtype/src/openapi/openapi.test.ts`:
- Around line 695-711: The `stageOpenApiDocs` missing-spec failure test should
also verify temp-directory cleanup on error. Update the `stageOpenApiDocs`
failure-path coverage in `openapi.test.ts` to assert that the staged temp dir
created by `createTempDir()` is removed after the promise rejects, using the
same setup around `resolvedPath` and the missing `./missing.yaml` input. This
will cover the unresolved cleanup behavior in `stageOpenApiDocs` and ensure the
failure path does not leak staged files.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c78b9f03-9ac9-4d9e-aa42-5668038f3226
📒 Files selected for processing (18)
.changeset/openapi-generated-docs.mdapps/fumadocs-example/lib/mdx-components.tsxapps/tanstack/src/components/docs-mdx/api.tsxdocs/changelog/0-4.mdxdocs/reference/openapi.mdxpackages/leadtype/package.jsonpackages/leadtype/rollup.config.tspackages/leadtype/src/framework-adapters.test.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/llm/llm.tspackages/leadtype/src/markdown/plugins/openapi.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/mdx/openapi-schema-rows.tspackages/leadtype/src/openapi/index.tspackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/source/index.tspackages/leadtype/src/source/source.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/framework-adapters.test.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/openapi-schema-rows.tspackages/leadtype/src/source/source.test.tspackages/leadtype/rollup.config.tspackages/leadtype/src/llm/llm.tsapps/tanstack/src/components/docs-mdx/api.tsxpackages/leadtype/src/mdx/index.tspackages/leadtype/src/markdown/plugins/openapi.tsapps/fumadocs-example/lib/mdx-components.tsxpackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/source/index.tspackages/leadtype/src/openapi/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/framework-adapters.test.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/openapi-schema-rows.tspackages/leadtype/src/source/source.test.tspackages/leadtype/rollup.config.tspackages/leadtype/src/llm/llm.tsapps/tanstack/src/components/docs-mdx/api.tsxpackages/leadtype/src/mdx/index.tspackages/leadtype/src/markdown/plugins/openapi.tsapps/fumadocs-example/lib/mdx-components.tsxpackages/leadtype/src/openapi/openapi.test.tspackages/leadtype/src/source/index.tspackages/leadtype/src/openapi/index.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/framework-adapters.test.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/source/source.test.tspackages/leadtype/src/openapi/openapi.test.ts
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/source/index.tspackages/leadtype/src/openapi/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/tanstack/src/components/docs-mdx/api.tsxapps/fumadocs-example/lib/mdx-components.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/tanstack/src/components/docs-mdx/api.tsxapps/fumadocs-example/lib/mdx-components.tsx
🧠 Learnings (1)
📚 Learning: 2026-06-09T18:30:08.038Z
Learnt from: KayleeWilliams
Repo: inthhq/leadtype PR: 97
File: .changeset/search-prototype-safety-and-scaling.md:5-5
Timestamp: 2026-06-09T18:30:08.038Z
Learning: In this repo, `.changeset/*.md` files must not start the body with an H1/first-line heading (`#`) immediately after the YAML frontmatter. The changesets tool inlines the body as bullet entries into `CHANGELOG.md` during release, and a leading `#` heading would break the generated changelog format. As a result, MD041 (`first-line-heading`) warnings for files under `.changeset/` are expected false positives and should be ignored.
Applied to files:
.changeset/openapi-generated-docs.md
🪛 ast-grep (0.44.1)
packages/leadtype/src/openapi/openapi.test.ts
[warning] 706-708: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
${resolvedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}.*openapiCwd
)
Note: [CWE-1333] Inefficient Regular Expression Complexity
(regexp-from-variable)
packages/leadtype/src/openapi/index.ts
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🔇 Additional comments (27)
packages/leadtype/src/openapi/index.ts (3)
1-251: LGTM!
1084-1099: LGTM! Path-parameter substitution correctly skips unresolved placeholders and matches the new test coverage.
1238-1251: LGTM! Using aMapkeyed byin:namecorrectly gives operation-level parameters precedence over path-item parameters while preserving original insertion order, matching OpenAPI's override semantics.packages/leadtype/src/openapi/openapi.test.ts (1)
114-131: LGTM! New assertions correctly reflect the unquoted YAML frontmatter format and the path-parameter substitution/precedence behavior inindex.ts.Also applies to: 146-174, 522-631
packages/leadtype/src/mdx/openapi-schema-rows.ts (1)
1-75: LGTM! Clean extraction of the shared flattening logic with a sensible depth guard against runaway/cyclic schemas.packages/leadtype/src/markdown/plugins/openapi.ts (1)
80-113: LGTM! Delegating to the sharedflattenApiSchemaRowsremoves the duplicated local flattening logic while preserving the same table shape.docs/changelog/0-4.mdx (1)
12-19: LGTM!docs/reference/openapi.mdx (2)
135-139: 🎯 Functional CorrectnessVerify the helper is actually client-safe.
The docs promise
leadtype/mdx/openapihas no dependencies and is safe to import from client components. Please verify the published subpath is fully browser-compatible before documenting that guarantee; otherwise this guidance can cause client builds to break.
205-261: LGTM!.changeset/openapi-generated-docs.md (1)
5-9: LGTM!apps/fumadocs-example/lib/mdx-components.tsx (3)
170-248: 🩺 Stability & Availability | ⚡ Quick winVerify the previously-flagged duplicate-key issue in
ApiAuthrequirements is actually fixed.A prior review flagged that
key={names.join("+") || "anonymous"}for the requirements list can collide when multiple OR'd security requirements resolve to the same scope-name set. This range isn't included in the current diff snippet, and nothing in the change summary indicates the key generation was updated to include the array index. Please confirm the fix (index + label) is present.#!/bin/bash fd mdx-components.tsx apps/fumadocs-example --exec rg -n -B2 -A6 'requirements.map' {}
8-27: LGTM!
125-156: LGTM!Good move switching
SchemaRowsto the sharedflattenApiSchemaRowshelper — this resolves the bulk of the previously-flagged duplication with the TanStack implementation.Also applies to: 323-323
apps/tanstack/src/components/docs-mdx/api.tsx (2)
162-188: 🩺 Stability & Availability | ⚡ Quick winVerify the previously-flagged duplicate-key issue in
ApiAuthrequirements is actually fixed.A prior review flagged
key={label}colliding for repeated/identical anonymous or scope-based requirement labels, recommending the loop index be included. This range isn't shown in the current diff, and the change summary doesn't indicate the key generation changed. Please confirm the fix is present.#!/bin/bash fd api.tsx apps/tanstack/src/components/docs-mdx --exec rg -n -B2 -A6 'requirements.map' {}
33-72: LGTM!Nice consolidation onto the shared
flattenApiSchemaRowshelper forSchemaTable/MediaType.Also applies to: 102-118
packages/leadtype/src/mdx/index.ts (1)
45-50: LGTM!Also applies to: 61-69
packages/leadtype/package.json (2)
38-42: LGTM!
75-78: 🗄️ Data Integrity & IntegrationAdd a
defaultcondition to./openapiif this subpath should resolve outsideimport-only consumers.
The export currently exposes onlytypesandimport; adding"default": "./dist/openapi/index.js"would match the sibling./mdx/openapishape and avoid resolution failures for broader condition sets.packages/leadtype/src/internal/package-surface.test.ts (1)
86-86: LGTM!Also applies to: 95-95
packages/leadtype/src/llm/llm.ts (2)
33-33: LGTM!Also applies to: 477-481, 841-845
3404-3434: 🎯 Functional CorrectnessVerify
mountsshouldn't be scoped away from the OpenAPI overlay dir.Both the primary
readSourceDocscall and theextraDocsDirsoverlay loop reuse the sameconfig.mounts.mountspresumably encodes remap rules for the authored docs tree; applying it unmodified to a generated OpenAPI overlay directory (a completely different structure, e.g.api/index.mdx) could cause unintended remapping/exclusion of generated pages if any mount pattern happens to match the overlay's relative paths.Please confirm
readSourceDocs's mount-matching semantics tolerate being applied to an unrelated overlay tree, or scope mounts out of the overlay call.packages/leadtype/rollup.config.ts (1)
10-10: LGTM!Also applies to: 19-19
packages/leadtype/src/framework-adapters.test.ts (1)
65-65: LGTM!packages/leadtype/src/fumadocs/index.ts (1)
74-75: LGTM!Also applies to: 117-117
packages/leadtype/src/source/index.ts (2)
18-20: LGTM!Also applies to: 51-55, 71-92, 174-184, 211-213, 420-468
481-542: 🩺 Stability & AvailabilityRemove the overlay from
contentRootsand invalidate the file caches on cleanup
cleanup()should also drop the overlay path fromcontentRootsand clearcachedFilesByRoot/cachedFiles; otherwise a laterlistPages()/buildSearchIndex()call can still crawl the deleted temp dir and reuse stale paths.packages/leadtype/src/source/source.test.ts (1)
1-1: LGTM!Also applies to: 14-40, 419-506
- Encode JSX string attributes so spec quotes cannot inject MDX (mdxProp) - Collision-suffix overview pages when specs share an output dir and thread the real overview slug through nav, canonicalUrl, and Related - Accept OpenAPI-only docs configs in leadtype generate validation - Time out remote spec and $ref fetches after 30s - Clean up staging temp dir when stageOpenApiDocs generation fails - Skip OpenAPI generation under --include/--exclude with a warning - Fail loudly before overwriting pre-existing files in writeOpenApiPages - Wrap tanstack llm-generate staging in try/finally - De-duplicate auth requirement React keys in example renderers - Document the spec trust model and filter behavior

Summary
Native OpenAPI 3.x → API reference page generation, built for great docs DX and agent-friendliness. One
openapiblock indocs.config.tsturns a spec into MDX operation pages that render through your docs UI and flatten into agent-readable markdown — flowing intollms.txt, search, markdown mirrors,AGENTS.md, and Agent Readability artifacts.What each generated page carries
type: api-reference,method,path,operationId,server,apiVersion,tags,canonicalUrl(whenbaseUrlis set),lastModified(spec's git commit date), andsourcepointing at the spec itself — serialized through the shared frontmatter YAML serializerresults[].title), the dereferenced JSON Schema per media type (includeSchemas: falseto opt out), named or schema-synthesized JSON examples, and response headers. Operation-level parameters override path-item parameters per the OpenAPI specfetchwith example bodies, required query params, path parameters substituted from spec examples (/pages/{urlPath}→/pages/docs/quickstart), and auth headers derived from the security scheme; Redocly-stylex-codeSamplesoverride{/</}are escaped (code spans, fences, and autolinks preserved) so arbitrary specs can't break the buildIntegration shapes (works everywhere)
createDocsSource({ openapi })/fumadocsSource({ openapi })— generated pages live in a small temp overlay; authored docs are read live fromcontentDir(never copied or frozen). Sources exposecleanup(), with a best-effort process-exit sweep as fallback. Nav for generated pages resolves throughresolveDocsNavigation's newextraDocsDirsoptionleadtype generate— picks up the config automatically (next/nuxt/sveltekit examples)writeOpenApiPages()into an app-local dir (TanStack Start / Vite recipe)stageOpenApiDocs()— the full-copy staging primitive for custom pipelinesRendering follows the existing component naming contract: seven
Api*tags, prop types fromleadtype/mdx, built-in markdown flatteners registered in the native dispatcher, and copyable reference implementations in the Fumadocs and TanStack examples (both browser-verified). The dependency-freeleadtype/mdx/openapisubpath exportsflattenApiSchemaRows()so custom renderers derive the same nested property rows as the markdown mirrors — one flattener, no drift, safe to import from client components.DX guardrails
openapiCwd/cwdescape hatches (and notes that the CLI resolves relative inputs from the config file directory)outputdir, with nav/canonical/Related links following the real slug; generated pages colliding with authored routes or pre-existing files throw descriptive errors instead of overwriting&/"), closing the attribute-injection path alongside the existing prose escapingproduct+openapi) validate without dummy nav; remote spec/$reffetches time out after 30s;--include/--excluderuns skip OpenAPI generation with a warning; the spec trust model is documentedDogfooding
docs/openapi/leadtype-api.yaml(two endpoints, enums, formats, named examples, response headers, error schemas) generates into the package docs bundle, the Fumadocs example, and the TanStack example.Docs
docs/reference/openapi.mdx— full reference: quickstart, page anatomy, renderer wiring per integration shape (includingflattenApiSchemaRows), options table, library APIdocs/writing/components.mdx—Api*tags added to the naming contractdocs/changelog/0-4.mdx— release notes entryChecks
bun run --filter leadtype check-types/lint/test/build(601 tests via the pre-commitbun testrun)bun x tsgo --noEmitinapps/tanstackandapps/fumadocs-exampleleadtype lint docs --error-unknown --max-warnings 0(CI invocation)results[].titlerows, collapsible JSON Schema, sidebar/TOC,.mdcontent negotiation, and clean dev-server output (no client-bundle errors)Review
A multi-agent review pass (8 finder angles, adversarially verified) confirmed and fixed 7 findings before merge: inverted parameter precedence, literal
{param}placeholders in code samples, a temp-directory leak plus frozen-snapshot dev semantics increateDocsSource(replaced with the overlay design), a triplicated schema-row flattener (now one shared helper), divergent relative-path resolution between the CLI and source APIs (now fail-loud with guidance), and hand-rolled frontmatter YAML (now the shared serializer).A second round addressed all open bot-review findings (Pullfrog, Codex, CodeRabbit): the JSX attribute-injection vector in
mdxProp, the multi-spec shared-output overview drop, OpenAPI-only config validation, remote-fetch timeouts, failure-path staging cleanup (plus thellm-generatetry/finally), fail-loud overwrite protection, React key collisions in the example renderers, filtered-generate semantics, and the documented$reftrust model.