Skip to content

Commit 8fa1e7f

Browse files
xuyushun441-sysos-zhuangclaude
authored
fix(spec): escape unmatched </{ in generated MDX docs (unblock apps/docs build) (#1493)
* fix(spec): escape unmatched `<`/`{` in generated MDX docs (unblock apps/docs build) `build-docs.ts`'s `escapeMdxDescription` only backtick-wrapped *matched* `<…>` / `{…}` pairs. A lone `<` with no closing partner — e.g. a SemVer range in a `.describe()` like `">=4.0 <5"` — leaked into the generated MDX, where Turbopack/fumadocs read the `<` as the start of a JSX tag and failed the docs build: ./content/docs/references/kernel/manifest.mdx 82:97: Unexpected character `5` (U+0035) before name Now an unmatched `<` / `{` is replaced with its HTML entity (`&lt;` / `&#123;`) so it renders literally and never opens a JSX/expression node. Also routes union-variant descriptions through the same escaper (previously emitted raw). Verified: `pnpm --filter @objectstack/docs build` (gen:schema + gen:docs + next build) now completes; no remaining unmatched `<` outside code spans across the generated references. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: changeset for docs MDX escape fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5c4b93b commit 8fa1e7f

2 files changed

Lines changed: 15 additions & 1 deletion

File tree

‎.changeset/fix-docs-mdx-escape.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Fix the docs generator (`build-docs.ts`) leaking an unmatched `<` / `{` into generated MDX, which broke the `apps/docs` Turbopack build (e.g. a SemVer range `">=4.0 <5"` in a `.describe()` string was read as the start of a JSX tag). Unmatched openers are now emitted as HTML entities (`&lt;` / `&#123;`); union-variant descriptions also go through the escaper.

‎packages/spec/scripts/build-docs.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ function generateMarkdown(schemaName: string, schema: any, category: string, zod
167167
// span are left untouched. A naive two-pass replace double-wraps nested cases
168168
// like `{<id>}` into `` `{`<id>`}` `` — the inner backticks close the span
169169
// early and leak `<id>` as raw JSX (MDX: "Expected a closing tag for `<id>`").
170+
//
171+
// A matched `{…}` / `<…>` pair is wrapped in an inline-code span so it renders
172+
// literally. A *lone* `<` or `{` with no closing partner (e.g. a SemVer range
173+
// `">=4.0 <5"`, or prose like `count < 5`) can't be wrapped, so it is replaced
174+
// with its HTML entity — otherwise MDX reads the `<` as the start of a JSX tag
175+
// and the build dies ("Unexpected character `5` before name").
170176
const escapeMdxDescription = (raw: string): string => {
171177
let out = '';
172178
let inCode = false;
@@ -185,6 +191,9 @@ function generateMarkdown(schemaName: string, schema: any, category: string, zod
185191
i = end;
186192
continue;
187193
}
194+
// Unmatched: escape so MDX doesn't treat it as a JSX/expression opener.
195+
out += ch === '<' ? '&lt;' : '&#123;';
196+
continue;
188197
}
189198
out += ch;
190199
}
@@ -223,7 +232,7 @@ function generateMarkdown(schemaName: string, schema: any, category: string, zod
223232
variants.forEach((variant: any, index: number) => {
224233
const variantTitle = variant.title || `Option ${index + 1}`;
225234
md += `#### ${variantTitle}\n\n`;
226-
if (variant.description) md += `${variant.description}\n\n`;
235+
if (variant.description) md += `${escapeMdxDescription(variant.description)}\n\n`;
227236

228237
if (variant.type === 'object' && variant.properties) {
229238
if (variant.properties.type && variant.properties.type.const) {

0 commit comments

Comments
 (0)