From 7ca2ccf2a965e0fd13486bcbd05d9ab7aebeae3c Mon Sep 17 00:00:00 2001 From: Parv Ahuja <17094219+parvahuja@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:51:52 -0700 Subject: [PATCH] fix: keep generated Markdown content-only --- .github/workflows/verify.yml | 2 +- package.json | 2 +- pnpm-lock.yaml | 10 +++--- scripts/check-markdown-components.mjs | 25 +++++++++++-- src/lib/markdown-output.test.ts | 51 +++++++++++++++++++++++++++ src/lib/markdown-output.ts | 5 ++- 6 files changed, 84 insertions(+), 11 deletions(-) diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 25feda19..da6bd7b8 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -58,7 +58,7 @@ jobs: NODE_OPTIONS: --max-old-space-size=4096 VERCEL_ENV: production - - name: Audit Markdown components + - name: Audit generated Markdown run: pnpm run check:markdown - name: Check generated public links diff --git a/package.json b/package.json index 96ff2007..6e476e51 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "unplugin-auto-import": "^21.0.0", "unplugin-icons": "^23.0.1", "viem": "^2.54.6", - "vocs": "https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748", + "vocs": "https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028", "wagmi": "3.6.20", "waku": "^1.0.0-beta.6", "webauthx": "~0.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0737404d..30e72a17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,8 +139,8 @@ importers: specifier: ^2.54.6 version: 2.54.6(typescript@6.0.3)(zod@4.4.3) vocs: - specifier: https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748 - version: https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748(@cfworker/json-schema@4.1.1)(@types/react@19.2.17)(@vue/compiler-sfc@3.5.38)(change-case@5.4.4)(esbuild@0.28.1)(idb-keyval@6.2.2)(mermaid@11.14.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(rolldown@1.1.4)(rollup@4.60.1)(typescript@6.0.3)(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(waku@1.0.0-beta.6(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)) + specifier: https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028 + version: https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028(@cfworker/json-schema@4.1.1)(@types/react@19.2.17)(@vue/compiler-sfc@3.5.38)(change-case@5.4.4)(esbuild@0.28.1)(idb-keyval@6.2.2)(mermaid@11.14.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(rolldown@1.1.4)(rollup@4.60.1)(typescript@6.0.3)(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(waku@1.0.0-beta.6(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)) wagmi: specifier: 3.6.20 version: 3.6.20(@tanstack/query-core@5.101.2)(@tanstack/react-query@5.101.2(react@19.2.7))(@types/react@19.2.17)(accounts@0.14.11)(react@19.2.7)(typescript@6.0.3)(viem@2.54.6(typescript@6.0.3)(zod@4.4.3)) @@ -4980,8 +4980,8 @@ packages: waku: optional: true - vocs@https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748: - resolution: {integrity: sha512-uMzPryXH0HfyTiOlTom5aeMcYXgVZGlJFjKz+gq+XRLhxCkqf8PUiSyU5EBqTx+TRx6NkXdUaFlOuP53E0aN7g==, tarball: https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748} + vocs@https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028: + resolution: {integrity: sha512-wPhE/6Uc1zYq38lt9mgs3Z6gYCYFx1cOeZY+BOAcfFqPkm782P7aEKu5a7p9TzRtoG+wRZUqM2fqAksqPp/aKw==, tarball: https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028} version: 2.6.2 hasBin: true peerDependencies: @@ -10548,7 +10548,7 @@ snapshots: - vue-template-es2015-compiler - webpack - vocs@https://pkg.pr.new/wevm/vocs/vocs@e1a70dc00507fade986b2f9b033f26906d037748(@cfworker/json-schema@4.1.1)(@types/react@19.2.17)(@vue/compiler-sfc@3.5.38)(change-case@5.4.4)(esbuild@0.28.1)(idb-keyval@6.2.2)(mermaid@11.14.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(rolldown@1.1.4)(rollup@4.60.1)(typescript@6.0.3)(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(waku@1.0.0-beta.6(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)): + vocs@https://pkg.pr.new/wevm/vocs/vocs@60cc0b17cd78f4cf8da0eb20f47e64f4d701e028(@cfworker/json-schema@4.1.1)(@types/react@19.2.17)(@vue/compiler-sfc@3.5.38)(change-case@5.4.4)(esbuild@0.28.1)(idb-keyval@6.2.2)(mermaid@11.14.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(rolldown@1.1.4)(rollup@4.60.1)(typescript@6.0.3)(vite@8.1.0(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(waku@1.0.0-beta.6(@types/node@26.0.1)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.7(react@19.2.7))(react-server-dom-webpack@19.2.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(webpack@5.104.1(esbuild@0.28.1)))(react@19.2.7)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(webpack@5.104.1(esbuild@0.28.1)): dependencies: '@base-ui/react': 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@codesandbox/sandpack-react': 2.20.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) diff --git a/scripts/check-markdown-components.mjs b/scripts/check-markdown-components.mjs index 9e10e7c6..cf4ecfdd 100644 --- a/scripts/check-markdown-components.mjs +++ b/scripts/check-markdown-components.mjs @@ -57,24 +57,43 @@ if (unresolvedIncludes.length > 0) { } const generatedComponents = new Set() +const generatedEsmFiles = new Set() +const generatedExpressions = new Set() +const generatedPresentationElements = [] for (const file of generatedFiles) { const content = await readFile(file, 'utf8') const parseableContent = maskHtmlComments(content) const tree = unified().use(remarkParse).use(remarkMdx).parse(parseableContent) visit(tree, (node) => { + if (node.type === 'mdxjsEsm') generatedEsmFiles.add(file) + if (node.type === 'mdxFlowExpression' || node.type === 'mdxTextExpression') + generatedExpressions.add(file) if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement') return + if (/^(?:meta|script|style|title)$/.test(node.name ?? '')) + generatedPresentationElements.push({ file, name: node.name }) if (!/^[A-Z][A-Za-z0-9]*(?:\.[A-Za-z0-9]+)*$/.test(node.name ?? '')) return generatedComponents.add(node.name) }) } -if (generatedComponents.size > 0) { - console.error('Generated Markdown component audit failed.') +if ( + generatedComponents.size > 0 || + generatedEsmFiles.size > 0 || + generatedExpressions.size > 0 || + generatedPresentationElements.length > 0 +) { + console.error('Generated Markdown syntax audit failed.') for (const name of generatedComponents) console.error(`- ${name}: unresolved component`) + for (const file of generatedEsmFiles) console.error(`- ${file}: executable import or export`) + for (const file of generatedExpressions) console.error(`- ${file}: executable expression`) + for (const { file, name } of generatedPresentationElements) + console.error(`- ${file}: presentation-only <${name}> element`) process.exit(1) } -console.log('Markdown output audit passed (no unresolved component types or includes).') +console.log( + 'Markdown output audit passed (no unresolved components, includes, executable MDX, or presentation-only elements).', +) function maskHtmlComments(content) { const ranges = [] diff --git a/src/lib/markdown-output.test.ts b/src/lib/markdown-output.test.ts index a64daa27..85fa2347 100644 --- a/src/lib/markdown-output.test.ts +++ b/src/lib/markdown-output.test.ts @@ -95,6 +95,57 @@ Status: Required expect(output).not.toMatch(/<\/?[A-Z]/) }) + test('removes executable and presentation-only MDX without dropping later content', async () => { + const output = await render(` +import { Demo } from './Demo' +export const data = [{ label: 'Example' }] + + + + +Browser title + +# Agent guide + +The machine-readable content remains available. + +| URL | Contents | +| --- | --- | +| /llms.txt | Documentation index | +`) + + expect(output).not.toContain('import { Demo }') + expect(output).not.toContain('export const data') + expect(output).not.toContain('.tabs') + expect(output).not.toMatch(/<(?:meta|script|style|title)\b/) + expect(output).toContain('# Agent guide') + expect(output).toContain('The machine-readable content remains available.') + expect(output).toContain('/llms.txt') + expect(output).toContain('Documentation index') + }) + + test('keeps MDX-like syntax inside fenced examples', async () => { + const output = await render(` +\`\`\`mdx +import { Demo } from './Demo' +export const data = [{ label: 'Example' }] + + + +Browser title +\`\`\` +`) + + expect(output).toContain("import { Demo } from './Demo'") + expect(output).toContain("export const data = [{ label: 'Example' }]") + expect(output).toContain('') + expect(output).toContain('') + expect(output).toContain('') + expect(output).toContain('Browser title') + }) + test('expands code includes and removes region markers', async () => { const output = await render(` \`\`\`ts diff --git a/src/lib/markdown-output.ts b/src/lib/markdown-output.ts index 08c571dc..32bbe35a 100644 --- a/src/lib/markdown-output.ts +++ b/src/lib/markdown-output.ts @@ -41,6 +41,7 @@ type MarkdownNode = { } const openApiSpecUrl = 'https://api.tempo.xyz/openapi.json' +const presentationOnlyElements = new Set(['meta', 'script', 'style', 'title']) const interactiveDescriptions: Record = { ConnectWallet: 'Connect a wallet in the interactive web page.', @@ -131,12 +132,15 @@ function rewriteNode( headingDepth: number, getSnippet: (fileName: string) => string | undefined, ): MarkdownNode[] { + if (node.type === 'mdxjsEsm') return [] + if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement') { if (node.type === 'code' && node.value) node.value = inlineCodeSnippets(node.value, getSnippet) rewriteChildren(node, headingDepth, getSnippet) return [node] } + if (node.name && presentationOnlyElements.has(node.name)) return [] if (node.name === 'Cards') return renderCards(node, headingDepth, getSnippet) if (node.name === 'Card') return [paragraph(cardContent(node))] if (node.name === 'Tabs') return renderTabs(node, headingDepth, getSnippet) @@ -152,7 +156,6 @@ function rewriteNode( if (node.name && interactiveDescriptions[node.name]) return [paragraph([text(interactiveDescriptions[node.name])])] - if (node.name === 'title') return [] if (isLayoutElement(node)) { rewriteChildren(node, headingDepth, getSnippet) return node.children ?? []