From 84803a1e8ac487e1bfde70898e95fd09e47b4e13 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 13 Aug 2026 10:48:28 -0400 Subject: [PATCH 1/2] feat(react): add stability index --- .changeset/index-page-from-input.md | 5 ++ packages/react/src/jsx-ast/README.md | 9 ++- .../src/jsx-ast/__tests__/generate.test.mjs | 47 ++++++++++++++- packages/react/src/jsx-ast/generate.mjs | 11 +++- packages/react/src/jsx-ast/index.mjs | 1 - packages/react/src/jsx-ast/types.d.ts | 1 - .../documentationIndex.test.mjs} | 60 ++++++++++++++----- .../index.mjs => documentationIndex.mjs} | 39 ++++++------ scripts/vercel-build.sh | 1 - scripts/vercel-prepare.sh | 2 + www/doc-kit.config.mjs | 7 --- 11 files changed, 135 insertions(+), 48 deletions(-) create mode 100644 .changeset/index-page-from-input.md rename packages/react/src/jsx-ast/utils/{synthetic/__tests__/index.test.mjs => __tests__/documentationIndex.test.mjs} (67%) rename packages/react/src/jsx-ast/utils/{synthetic/index.mjs => documentationIndex.mjs} (57%) diff --git a/.changeset/index-page-from-input.md b/.changeset/index-page-from-input.md new file mode 100644 index 00000000..4a08607c --- /dev/null +++ b/.changeset/index-page-from-input.md @@ -0,0 +1,5 @@ +--- +'@doc-kit/generator-react': patch +--- + +Generate `index.html` from the input `index` document instead of a synthetic page diff --git a/packages/react/src/jsx-ast/README.md b/packages/react/src/jsx-ast/README.md index e4f95dba..a8fbd040 100644 --- a/packages/react/src/jsx-ast/README.md +++ b/packages/react/src/jsx-ast/README.md @@ -10,7 +10,12 @@ The `jsx-ast` generator converts MDAST (Markdown Abstract Syntax Tree) to JSX AS documentation structure. - `generateAllPage` {boolean} When `true`, creates a synthetic JSX AST entry for `all.html`. **Default:** `true`. -- `generateIndexPage` {boolean} When `true`, creates a synthetic JSX AST entry - for `index.html`. **Default:** `true`. - `generateNotFoundPage` {boolean} When `true`, creates a synthetic JSX AST entry for `404.html`. **Default:** `true`. + +## Index page + +`index.html` is generated when an `index` document is part of the input, and +is rendered from that document like any other page. A section containing a +`` comment additionally receives the Stability +Overview table of all modules. diff --git a/packages/react/src/jsx-ast/__tests__/generate.test.mjs b/packages/react/src/jsx-ast/__tests__/generate.test.mjs index 7a893a8c..a3050b09 100644 --- a/packages/react/src/jsx-ast/__tests__/generate.test.mjs +++ b/packages/react/src/jsx-ast/__tests__/generate.test.mjs @@ -75,7 +75,6 @@ describe('jsx-ast generate', () => { const jsxAstConfig = getConfig('jsx-ast'); jsxAstConfig.generateAllPage = false; - jsxAstConfig.generateIndexPage = false; jsxAstConfig.generateNotFoundPage = false; const seenItems = []; @@ -95,4 +94,50 @@ describe('jsx-ast generate', () => { ['index', 'fs'] ); }); + + it('only generates an index page when an index document is an input', async () => { + await setConfig({ target: ['jsx-ast'] }); + + const jsxAstConfig = getConfig('jsx-ast'); + jsxAstConfig.generateAllPage = false; + jsxAstConfig.generateNotFoundPage = false; + + const seenItems = []; + await collect( + generate([createEntry('fs', 'File system')], createWorker(seenItems)) + ); + + assert.deepEqual( + seenItems.map(({ head }) => head.api), + ['fs'] + ); + }); + + it('places the stability overview at the DOCUMENTATION_INDEX comment', async () => { + await setConfig({ target: ['jsx-ast'] }); + + const jsxAstConfig = getConfig('jsx-ast'); + jsxAstConfig.generateAllPage = false; + jsxAstConfig.generateNotFoundPage = false; + + const index = createEntry('index', 'Index', { stabilityIndex: null }); + // The metadata parser turns a `` comment into + // this tag on the entry of the section containing it. + index.tags = ['DOCUMENTATION_INDEX']; + + const seenItems = []; + await collect( + generate( + [index, createEntry('fs', 'File system')], + createWorker(seenItems) + ) + ); + + const [{ entries }] = seenItems; + const table = entries[0].content.children.at(-1); + + assert.equal(table.tagName, 'table'); + const [row] = table.children.at(-1).children; + assert.equal(row.children[0].children[0].properties.href, 'fs.html'); + }); }); diff --git a/packages/react/src/jsx-ast/generate.mjs b/packages/react/src/jsx-ast/generate.mjs index ba62a876..9f696954 100644 --- a/packages/react/src/jsx-ast/generate.mjs +++ b/packages/react/src/jsx-ast/generate.mjs @@ -3,10 +3,10 @@ import { groupNodesByModule } from '@doc-kit/core/utils/generators.mjs'; import { jsx, toJs } from 'estree-util-to-js'; import buildContent from './utils/buildContent.mjs'; +import { injectDocumentationIndex } from './utils/documentationIndex.mjs'; import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs'; import { buildNotFoundPage } from './utils/synthetic/404.mjs'; import { buildAllPage } from './utils/synthetic/all.mjs'; -import { buildIndexPage } from './utils/synthetic/index.mjs'; /** * Builds the `{ head, entries }` page descriptors for all configured synthetic @@ -21,7 +21,6 @@ const buildSyntheticDescriptors = input => { return [ config.generateAllPage && buildAllPage(input), - config.generateIndexPage && buildIndexPage(input), config.generateNotFoundPage && buildNotFoundPage(), ].filter(Boolean); }; @@ -60,9 +59,15 @@ export async function processChunk(slicedInput, itemIndices) { * @type {import('./types').Generator['generate']} */ export async function* generate(input, worker) { - // The synthetic `index` page replaces the Core `index` document. + // The `index` page is only generated when an `index` document is part of + // the input; the module list for the synthetic pages and the stability + // overview excludes it. const moduleInput = input.filter(entry => entry.api !== 'index'); + // Sections tagged with a `` comment (e.g. in + // the `index` document) receive the Stability Overview of all modules. + injectDocumentationIndex(input, moduleInput); + // Create sliced input: each item contains head + its module's entries // This avoids sending all 4700+ entries to every worker const groupedModules = groupNodesByModule(input); diff --git a/packages/react/src/jsx-ast/index.mjs b/packages/react/src/jsx-ast/index.mjs index 54272b2a..31c23f09 100644 --- a/packages/react/src/jsx-ast/index.mjs +++ b/packages/react/src/jsx-ast/index.mjs @@ -17,7 +17,6 @@ export default { defaultConfiguration: { ref: 'main', generateAllPage: true, - generateIndexPage: true, generateNotFoundPage: true, }, diff --git a/packages/react/src/jsx-ast/types.d.ts b/packages/react/src/jsx-ast/types.d.ts index 2413cd42..97c52182 100644 --- a/packages/react/src/jsx-ast/types.d.ts +++ b/packages/react/src/jsx-ast/types.d.ts @@ -5,7 +5,6 @@ export type Generator = GeneratorMetadata< { ref: string; generateAllPage: boolean; - generateIndexPage: boolean; generateNotFoundPage: boolean; }, Generate, AsyncGenerator>, diff --git a/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/documentationIndex.test.mjs similarity index 67% rename from packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs rename to packages/react/src/jsx-ast/utils/__tests__/documentationIndex.test.mjs index f083e905..6817f634 100644 --- a/packages/react/src/jsx-ast/utils/synthetic/__tests__/index.test.mjs +++ b/packages/react/src/jsx-ast/utils/__tests__/documentationIndex.test.mjs @@ -1,7 +1,10 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { buildIndexPage, buildStabilityOverview } from '../index.mjs'; +import { + buildStabilityOverview, + injectDocumentationIndex, +} from '../documentationIndex.mjs'; const fakeHead = (api, name, stabilityIndex, depth = 1) => ({ api, @@ -20,25 +23,40 @@ const fakeHead = (api, name, stabilityIndex, depth = 1) => ({ const findChild = (node, tagName) => node.children.find(child => child.tagName === tagName); -describe('buildIndexPage', () => { - it('returns a synthetic `index` head with an "Index" heading', () => { - const { head } = buildIndexPage([]); +describe('injectDocumentationIndex', () => { + const createEntry = tags => ({ + ...fakeHead('index', 'Index', null), + tags, + content: { type: 'root', children: [] }, + }); + + it('appends the overview to entries tagged DOCUMENTATION_INDEX', () => { + const tagged = createEntry(['DOCUMENTATION_INDEX']); + const untagged = createEntry(undefined); + + injectDocumentationIndex( + [tagged, untagged], + [fakeHead('fs', 'fs', 2), fakeHead('assert', 'assert', 2)] + ); - assert.equal(head.api, 'index'); - assert.equal(head.path, '/index'); - assert.equal(head.basename, 'index'); - assert.equal(head.heading.data.name, 'Index'); - assert.equal(head.synthetic, true); + const table = findChild(tagged.content, 'table'); + assert.equal(findChild(table, 'tbody').children.length, 2); + assert.equal(untagged.content.children.length, 0); }); it('sorts the stability overview rows alphabetically by API name', () => { - const { entries } = buildIndexPage([ - fakeHead('fs', 'fs', 2), - fakeHead('assert', 'assert', 2), - fakeHead('crypto', 'crypto', 2), - ]); + const entry = createEntry(['DOCUMENTATION_INDEX']); + + injectDocumentationIndex( + [entry], + [ + fakeHead('fs', 'fs', 2), + fakeHead('assert', 'assert', 2), + fakeHead('crypto', 'crypto', 2), + ] + ); - const table = findChild(entries[0].content, 'table'); + const table = findChild(entry.content, 'table'); const rows = findChild(table, 'tbody').children; const names = rows.map( row => row.children[0].children[0].children[0].value @@ -46,6 +64,18 @@ describe('buildIndexPage', () => { assert.deepEqual(names, ['assert', 'crypto', 'fs']); }); + + it('excludes module heads without a stability index', () => { + const entry = createEntry(['DOCUMENTATION_INDEX']); + + injectDocumentationIndex( + [entry], + [fakeHead('fs', 'fs', 2), fakeHead('synopsis', 'Usage', null)] + ); + + const table = findChild(entry.content, 'table'); + assert.equal(findChild(table, 'tbody').children.length, 1); + }); }); describe('buildStabilityOverview', () => { diff --git a/packages/react/src/jsx-ast/utils/synthetic/index.mjs b/packages/react/src/jsx-ast/utils/documentationIndex.mjs similarity index 57% rename from packages/react/src/jsx-ast/utils/synthetic/index.mjs rename to packages/react/src/jsx-ast/utils/documentationIndex.mjs index a3278202..c12ce0bf 100644 --- a/packages/react/src/jsx-ast/utils/synthetic/index.mjs +++ b/packages/react/src/jsx-ast/utils/documentationIndex.mjs @@ -2,10 +2,14 @@ import { h as createElement } from 'hastscript'; -import { createSyntheticHead, wrapAsEntry } from './synthetic.mjs'; -import { JSX_IMPORTS } from '../../../html/constants.mjs'; -import { createJSXElement } from '../ast.mjs'; -import { getSortedHeadNodes } from '../getSortedHeadNodes.mjs'; +import { createJSXElement } from './ast.mjs'; +import { getSortedHeadNodes } from './getSortedHeadNodes.mjs'; +import { JSX_IMPORTS } from '../../html/constants.mjs'; + +// The metadata parser turns bare HTML comments into entry tags, so a +// `` comment in a source document surfaces as +// this tag on the entry for the section containing it. +export const DOCUMENTATION_INDEX_TAG = 'DOCUMENTATION_INDEX'; const STABILITY_BADGE_KINDS = [ 'error', @@ -62,20 +66,21 @@ export const buildStabilityOverview = headEntries => ]); /** - * Builds the page descriptor for `index.html` + * Places the Stability Overview into every entry whose source section + * contains a `` comment. The parser strips the + * comment itself, so the table lands at the end of the tagged section. * - * @param {Array} entries + * @param {Array} entries - Entries to scan for the tag + * @param {Array} moduleEntries - Entries providing the module heads for the overview */ -export const buildIndexPage = entries => { - const head = createSyntheticHead('index', 'Index'); - const moduleEntries = getSortedHeadNodes(entries); +export const injectDocumentationIndex = (entries, moduleEntries) => { + const headEntries = getSortedHeadNodes(moduleEntries).filter( + entry => entry.stability + ); - return { - head, - entries: [ - wrapAsEntry(head, [ - buildStabilityOverview(moduleEntries.filter(entry => entry.stability)), - ]), - ], - }; + for (const entry of entries) { + if (entry.tags?.includes(DOCUMENTATION_INDEX_TAG)) { + entry.content.children.push(buildStabilityOverview(headEntries)); + } + } }; diff --git a/scripts/vercel-build.sh b/scripts/vercel-build.sh index b42fa0b6..62e7f679 100755 --- a/scripts/vercel-build.sh +++ b/scripts/vercel-build.sh @@ -16,7 +16,6 @@ node packages/cli/bin/cli.mjs generate \ -c "./node/CHANGELOG.md" \ -v "$NODE_VERSION" \ --type-map "./node/doc/type-map.json" \ - --index "./node/doc/api/index.md" \ --config-file "./beta/doc-kit.config.mjs" \ --log-level debug diff --git a/scripts/vercel-prepare.sh b/scripts/vercel-prepare.sh index ecb33759..c536f8c4 100755 --- a/scripts/vercel-prepare.sh +++ b/scripts/vercel-prepare.sh @@ -26,6 +26,8 @@ cd node # Enable sparse checkout and specify the folder git sparse-checkout set lib doc . +sed 's/STABILITY_OVERVIEW_SLOT_BEGIN/DOCUMENTATION_INDEX/g' ./doc/api/documentation.md > ./doc/api/index.md + # Move back out cd .. diff --git a/www/doc-kit.config.mjs b/www/doc-kit.config.mjs index 8d86db88..726d34f0 100644 --- a/www/doc-kit.config.mjs +++ b/www/doc-kit.config.mjs @@ -57,13 +57,6 @@ export default { minify: true, }, - 'jsx-ast': { - // `jsx-ast` otherwise synthesizes an `index.html` holding the Node.js API - // stability overview, and it silently overrides an authored `index.md`. - // This site has no stability metadata, so that page would render empty. - generateIndexPage: false, - }, - html: { title: '{project} documentation', From e72e64211961386e7c3ec39f007993b8cee531ed Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Thu, 13 Aug 2026 11:04:41 -0400 Subject: [PATCH 2/2] fixup! --- scripts/vercel-prepare.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/vercel-prepare.sh b/scripts/vercel-prepare.sh index c536f8c4..2fa3e1b5 100755 --- a/scripts/vercel-prepare.sh +++ b/scripts/vercel-prepare.sh @@ -27,6 +27,7 @@ cd node git sparse-checkout set lib doc . sed 's/STABILITY_OVERVIEW_SLOT_BEGIN/DOCUMENTATION_INDEX/g' ./doc/api/documentation.md > ./doc/api/index.md +rm ./doc/api/documentation.md # Move back out cd ..