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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@node-core/doc-kit",
"type": "module",
"version": "1.2.1",
"version": "1.3.0",
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/doc-kit.git"
Expand Down
2 changes: 1 addition & 1 deletion scripts/update-type-map.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { writeFile } from 'node:fs/promises';

import { MDN_COMPAT_URL, MDN_TYPE_MAP } from './constants.mjs';
import { loadFromURL } from '../src/utils/url.mjs';
import { loadFromURL } from '../src/utils/loaders.mjs';

const compat = JSON.parse(await loadFromURL(MDN_COMPAT_URL));

Expand Down
10 changes: 4 additions & 6 deletions src/generators/jsx-ast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ The `jsx-ast` generator converts MDAST (Markdown Abstract Syntax Tree) to JSX AS

The `jsx-ast` generator accepts the following configuration options:

| Name | Type | Default | Description |
| --------- | -------- | --------------------------------------------- | ------------------------------------------------------------------------ |
| `ref` | `string` | `'main'` | Git reference/branch for linking to source files |
| `pageURL` | `string` | `'{baseURL}/latest-{version}/api{path}.html'` | URL template for documentation page links |
| `editURL` | `string` | `'${GITHUB_EDIT_URL}/doc/api{path}.md'` | URL template for "edit this page" links |
| `index` | `array` | - | Array of `{ section, api }` objects defining the documentation structure |
| Name | Type | Default | Description |
| ------- | -------- | -------- | ------------------------------------------------------------------------ |
| `ref` | `string` | `'main'` | Git reference/branch for linking to source files |
| `index` | `array` | - | Array of `{ section, api }` objects defining the documentation structure |
28 changes: 3 additions & 25 deletions src/generators/jsx-ast/generate.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { buildSideBarProps } from './utils/buildBarProps.mjs';
import buildContent from './utils/buildContent.mjs';
import { getSortedHeadNodes } from './utils/getSortedHeadNodes.mjs';
import { groupNodesByModule } from '../../utils/generators.mjs';
import { getRemarkRecma } from '../../utils/remark.mjs';
import { relative } from '../../utils/url.mjs';

const remarkRecma = getRemarkRecma();

/**
* Process a chunk of items in a worker thread.
Expand All @@ -16,28 +11,13 @@ const remarkRecma = getRemarkRecma();
*
* @type {import('./types').Generator['processChunk']}
*/
export async function processChunk(slicedInput, itemIndices, docPages) {
export async function processChunk(slicedInput, itemIndices) {
const results = [];

for (const idx of itemIndices) {
const { head, entries } = slicedInput[idx];

const sideBarProps = buildSideBarProps(
head,
docPages.map(([heading, path]) => [
heading,
head.path === path
? `${head.basename}.html`
: `${relative(path, head.path)}.html`,
])
);

const content = await buildContent(
entries,
head,
sideBarProps,
remarkRecma
);
const content = await buildContent(entries, head);

results.push(content);
}
Expand All @@ -55,16 +35,14 @@ export async function* generate(input, worker) {

const headNodes = getSortedHeadNodes(input);

const docPages = headNodes.map(node => [node.heading.data.name, node.path]);

// Create sliced input: each item contains head + its module's entries
// This avoids sending all 4700+ entries to every worker
const entries = headNodes.map(head => ({
head,
entries: groupedModules.get(head.api),
}));

for await (const chunkResult of worker.stream(entries, docPages)) {
for await (const chunkResult of worker.stream(entries)) {
yield chunkResult;
}
}
3 changes: 0 additions & 3 deletions src/generators/jsx-ast/index.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

import { GITHUB_EDIT_URL } from '../../utils/configuration/templates.mjs';
import { createLazyGenerator } from '../../utils/generators.mjs';

/**
Expand All @@ -19,8 +18,6 @@ export default createLazyGenerator({

defaultConfiguration: {
ref: 'main',
pageURL: '{baseURL}/latest-{version}/api{path}.html',
editURL: `${GITHUB_EDIT_URL}/doc/api{path}.md`,
},

hasParallelProcessor: true,
Expand Down
3 changes: 1 addition & 2 deletions src/generators/jsx-ast/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export type Generator = GeneratorMetadata<
Generate<Array<MetadataEntry>, AsyncGenerator<JSXContent>>,
ProcessChunk<
{ head: MetadataEntry; entries: Array<MetadataEntry> },
JSXContent,
Array<[string, string]>
JSXContent
>
>;
153 changes: 35 additions & 118 deletions src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs
Original file line number Diff line number Diff line change
@@ -1,42 +1,8 @@
import assert from 'node:assert/strict';
import { describe, it, mock } from 'node:test';
import { describe, it } from 'node:test';

import { SemVer } from 'semver';

import { setConfig } from '../../../../utils/configuration/index.mjs';
import * as generatorsExports from '../../../../utils/generators.mjs';

mock.module('reading-time', {
defaultExport: () => ({ text: '5 min read' }),
});

mock.module('../../../../utils/generators.mjs', {
namedExports: {
...generatorsExports,
getCompatibleVersions: () => [
{ version: '18.0.0', isLts: true, isCurrent: false },
{ version: '19.0.0', isLts: false, isCurrent: true },
],
leftHandAssign: Object.assign,
getVersionFromSemVer: version => `${version.major}.x`,
getVersionURL: (version, api) => `/api/${version}/${api}`,
},
});

const {
extractTextContent,
buildMetaBarProps,
formatVersionOptions,
buildSideBarProps,
} = await import('../buildBarProps.mjs');

await setConfig({
version: 'v17.0.0',
changelog: [
{ version: new SemVer('16.0.0'), isLts: true, isCurrent: false },
{ version: new SemVer('17.0.0'), isLts: false, isCurrent: true },
],
});
const { extractTextContent, extractHeadings } =
await import('../buildBarProps.mjs');

describe('extractTextContent', () => {
it('combines text and code node values from entries', () => {
Expand Down Expand Up @@ -66,104 +32,55 @@ describe('extractTextContent', () => {
});
});

describe('buildMetaBarProps', () => {
it('creates meta bar properties from entries', () => {
const head = {
basename: 'fs',
path: '/fs',
added: 'v1.0.0',
};

describe('extractHeadings', () => {
it('extracts headings from entries that qualify for ToC', () => {
const entries = [
{
content: {
type: 'root',
children: [{ type: 'text', value: 'Content' }],
heading: {
depth: 2,
data: {
text: 'fs.readFile(path)',
name: 'readFile',
slug: 'fs-readfile',
type: 'method',
},
},
stability: { data: { index: '2' } },
},
{
heading: {
depth: 2,
data: {
text: 'Heading',
name: 'Heading',
slug: 'heading',
depth: 2,
text: 'fs.writeFile(path)',
name: 'writeFile',
slug: 'fs-writefile',
type: 'method',
},
},
stability: null,
},
];

const result = buildMetaBarProps(head, entries);

assert.equal(result.addedIn, 'v1.0.0');
assert.equal(result.readingTime, '5 min read');
assert.deepEqual(result.viewAs, [
['JSON', 'fs.json'],
['MD', 'fs.md'],
]);
assert.equal(
result.editThisPage,
'https://github.com/nodejs/node/edit/main/doc/api/fs.md'
);
assert.ok(Array.isArray(result.headings));
});

it('falls back to introduced_in if added is missing', () => {
const head = {
api: 'fs',
introduced_in: 'v2.0.0',
};

const entries = [];
const result = extractHeadings(entries);

const result = buildMetaBarProps(head, entries);
assert.equal(result.addedIn, 'v2.0.0');
assert.equal(result.length, 2);
assert.equal(result[0].slug, 'fs-readfile');
assert.equal(result[0].depth, 2);
assert.equal(result[0].stability, 2);
assert.equal(result[1].stability, 2);
});
});

describe('formatVersionOptions', () => {
it('formats version options with proper labels', () => {
const versions = [
{ version: new SemVer('16.0.0'), isLts: true, isCurrent: false },
{ version: new SemVer('17.0.0'), isLts: false, isCurrent: true },
{ version: new SemVer('18.0.0'), isLts: false, isCurrent: false },
];

const result = formatVersionOptions(versions, '/http');

assert.deepStrictEqual(result, [
{
value: 'https://nodejs.org/docs/latest-v16.x/api/http.html',
label: 'v16.x (LTS)',
},
{
value: 'https://nodejs.org/docs/latest-v17.x/api/http.html',
label: 'v17.x (Current)',
},
it('filters out entries with empty heading text', () => {
const entries = [
{
value: 'https://nodejs.org/docs/latest-v18.x/api/http.html',
label: 'v18.x',
heading: {
depth: 2,
data: { text: '', name: '', slug: '', type: 'method' },
},
},
]);
});
});

describe('buildSideBarProps', () => {
it('creates sidebar properties with versions and navigation', () => {
const entry = {
path: 'http',
basename: 'http',
introduced_in: 'v0.10.0',
};

const docPages = [
['HTTP', 'http.html'],
['HTTPS', 'https.html'],
];

const result = buildSideBarProps(entry, docPages);

assert.equal(result.currentVersion, 'v17.0.0');
assert.equal(result.pathname, 'http.html');
assert.deepEqual(result.docPages, docPages);
const result = extractHeadings(entries);
assert.equal(result.length, 0);
});
});
Loading
Loading