From 4932c4e6d1a240f471ee1c5bd60df9151ed3589b Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Thu, 4 Dec 2025 16:14:57 +0100 Subject: [PATCH 1/9] feat: introduce parallelization worker for chunked generation processing --- bin/commands/generate.mjs | 18 +- src/generators.mjs | 39 +++- src/generators/ast-js/index.mjs | 39 +++- src/generators/jsx-ast/index.mjs | 96 +++----- src/generators/legacy-html-all/index.mjs | 4 +- src/generators/legacy-html/index.mjs | 218 ++++++++---------- src/generators/legacy-html/utils/template.mjs | 48 ++++ src/generators/legacy-json/index.mjs | 65 +++--- src/generators/metadata/index.mjs | 24 +- src/generators/orama-db/index.mjs | 2 + src/generators/types.d.ts | 52 ++++- src/generators/web/index.mjs | 45 ++-- src/generators/web/utils/bundle.mjs | 22 +- src/generators/web/utils/processing.mjs | 3 +- src/threading/chunk-worker.mjs | 12 + src/threading/generator-worker.mjs | 18 ++ src/threading/index.mjs | 78 ++++--- src/threading/parallel.mjs | 159 +++++++++++++ src/threading/worker.mjs | 12 - src/utils/generators.mjs | 54 +++++ 20 files changed, 701 insertions(+), 307 deletions(-) create mode 100644 src/generators/legacy-html/utils/template.mjs create mode 100644 src/threading/chunk-worker.mjs create mode 100644 src/threading/generator-worker.mjs create mode 100644 src/threading/parallel.mjs delete mode 100644 src/threading/worker.mjs diff --git a/bin/commands/generate.mjs b/bin/commands/generate.mjs index e51fa2b7..ff2afe23 100644 --- a/bin/commands/generate.mjs +++ b/bin/commands/generate.mjs @@ -16,6 +16,10 @@ import { loadAndParse } from '../utils.mjs'; const availableGenerators = Object.keys(publicGenerators); +// Half of available logical CPUs guarantees in general all physical CPUs are being used +// which in most scenarios is the best way to maximize performance +const optimalThreads = Math.floor(cpus().length / 2) - 1; + /** * @typedef {Object} Options * @property {Array|string} input - Specifies the glob/path for input files. @@ -26,6 +30,7 @@ const availableGenerators = Object.keys(publicGenerators); * @property {string} typeMap - Specifies the path to the Node.js Type Map. * @property {string} [gitRef] - Git ref/commit URL. * @property {number} [threads] - Number of threads to allow. + * @property {number} [chunkSize] - Number of items to process per worker thread. */ /** @@ -61,10 +66,20 @@ export default { }, threads: { flags: ['-p', '--threads '], + desc: 'Number of worker threads to use', prompt: { type: 'text', message: 'How many threads to allow', - initialValue: String(Math.max(cpus().length, 1)), + initialValue: String(Math.max(optimalThreads, 1)), + }, + }, + chunkSize: { + flags: ['--chunk-size '], + desc: 'Number of items to process per worker thread (default: auto)', + prompt: { + type: 'text', + message: 'Items per worker thread', + initialValue: '20', }, }, version: { @@ -149,6 +164,7 @@ export default { releases, gitRef: opts.gitRef, threads: parseInt(opts.threads, 10), + chunkSize: parseInt(opts.chunkSize, 10), index, typeMap, }); diff --git a/src/generators.mjs b/src/generators.mjs index 2b7a3048..eb76fa59 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -2,6 +2,7 @@ import { allGenerators } from './generators/index.mjs'; import WorkerPool from './threading/index.mjs'; +import createParallelWorker from './threading/parallel.mjs'; /** * This method creates a system that allows you to register generators @@ -31,14 +32,26 @@ const createGenerator = input => { */ const cachedGenerators = { ast: Promise.resolve(input) }; - const threadPool = new WorkerPool(); - /** * Runs the Generator engine with the provided top-level input and the given generator options * * @param {GeneratorOptions} options The options for the generator runtime */ - const runGenerators = async ({ generators, threads, ...extra }) => { + const runGenerators = async ({ + generators, + threads, + chunkSize, + ...extra + }) => { + // WorkerPool for running full generators in worker threads + const generatorPool = new WorkerPool('./generator-worker.mjs', threads); + + // WorkerPool for chunk-level parallelization within generators + const chunkPool = new WorkerPool('./chunk-worker.mjs', threads); + + // Options including threading config + const threadingOptions = { threads, chunkSize }; + // Note that this method is blocking, and will only execute one generator per-time // but it ensures all dependencies are resolved, and that multiple bottom-level generators // can reuse the already parsed content from the top-level/dependency generators @@ -50,20 +63,32 @@ const createGenerator = input => { if (dependsOn && dependsOn in cachedGenerators === false) { await runGenerators({ ...extra, - threads, + ...threadingOptions, generators: [dependsOn], }); } // Ensures that the dependency output gets resolved before we run the current // generator with its dependency output as the input - const dependencyOutput = await cachedGenerators[dependsOn]; + const input = await cachedGenerators[dependsOn]; + + // Create a ParallelWorker for this generator to use for item-level parallelization + const worker = createParallelWorker(generatorName, chunkPool, { + ...extra, + ...threadingOptions, + }); + + // Generator options with worker instance + const generatorOptions = { ...extra, ...threadingOptions, worker }; + + // Worker options for the worker thread + const workerOptions = { ...extra, ...threadingOptions }; // Adds the current generator execution Promise to the cache cachedGenerators[generatorName] = threads < 2 - ? generate(dependencyOutput, extra) // Run in main thread - : threadPool.run(generatorName, dependencyOutput, threads, extra); // Offload to worker thread + ? generate(input, generatorOptions) // Run in main thread + : generatorPool.run({ generatorName, input, options: workerOptions }); // Offload to worker thread } // Returns the value of the last generator of the current pipeline diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index 15497900..fef63b66 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -23,21 +23,42 @@ export default { dependsOn: 'metadata', /** - * @param {Input} _ + * Process a chunk of JavaScript files in a worker thread. + * Called by chunk-worker.mjs for parallel processing. + * + * @param {unknown} _ - Unused (we use options.input instead) + * @param {number[]} itemIndices - Indices of source files to process * @param {Partial} options */ - async generate(_, options) { + async processChunk(_, itemIndices, { input }) { const { loadFiles } = createJsLoader(); - // Load all of the Javascript sources into memory - const sourceFiles = loadFiles(options.input ?? []); + const sourceFiles = loadFiles(input ?? []); + + const { parseJsSource } = createJsParser(); + + const results = []; - const { parseJsSources } = createJsParser(); + for (const idx of itemIndices) { + results.push(await parseJsSource(sourceFiles[idx])); + } - // Parse the Javascript sources into ASTs - const parsedJsFiles = await parseJsSources(sourceFiles); + return results; + }, + + /** + * @param {Input} _ + * @param {Partial} options + */ + async generate(_, { input, worker }) { + const { loadFiles } = createJsLoader(); + + // Load all of the Javascript sources into memory + const sourceFiles = loadFiles(input ?? []); - // Return the ASTs so they can be used in another generator - return parsedJsFiles; + // Parse the Javascript sources into ASTs in parallel using worker threads + // Note: We pass sourceFiles as items but _ (empty) as fullInput since + // processChunk reloads files from options.input + return worker.map(sourceFiles, _, { input }); }, }; diff --git a/src/generators/jsx-ast/index.mjs b/src/generators/jsx-ast/index.mjs index 3e8e9a3c..839d3ccb 100644 --- a/src/generators/jsx-ast/index.mjs +++ b/src/generators/jsx-ast/index.mjs @@ -1,7 +1,10 @@ -import { OVERRIDDEN_POSITIONS } from './constants.mjs'; import { buildSideBarProps } from './utils/buildBarProps.mjs'; import buildContent from './utils/buildContent.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; +import { + buildDocPages, + getSortedHeadNodes, + groupNodesByModule, +} from '../../utils/generators.mjs'; import { getRemarkRecma } from '../../utils/remark.mjs'; /** @@ -10,34 +13,6 @@ import { getRemarkRecma } from '../../utils/remark.mjs'; * @typedef {Array} Input * @type {GeneratorMetadata} */ - -/** - * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. - * @param {Array} entries - */ -const getSortedHeadNodes = entries => { - return entries - .filter(node => node.heading.depth === 1) - .sort((a, b) => { - const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); - const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); - - if (ai !== -1 && bi !== -1) { - return ai - bi; - } - - if (ai !== -1) { - return -1; - } - - if (bi !== -1) { - return 1; - } - - return a.heading.data.name.localeCompare(b.heading.data.name); - }); -}; - export default { name: 'jsx-ast', version: '1.0.0', @@ -45,43 +20,46 @@ export default { dependsOn: 'metadata', /** - * Generates a JSX AST - * - * @param {Input} entries + * Process a chunk of items in a worker thread. + * @param {Input} fullInput + * @param {number[]} itemIndices * @param {Partial} options - * @returns {Promise>} Array of generated content */ - async generate(entries, { index, releases, version }) { - const remarkRecma = getRemarkRecma(); - const groupedModules = groupNodesByModule(entries); - const headNodes = getSortedHeadNodes(entries); - - // Generate table of contents - const docPages = index - ? index.map(({ section, api }) => [section, `${api}.html`]) - : headNodes.map(node => [node.heading.data.name, `${node.api}.html`]); + async processChunk(fullInput, itemIndices, { index, releases, version }) { + const processor = getRemarkRecma(); + const groupedModules = groupNodesByModule(fullInput); + const headNodes = getSortedHeadNodes(fullInput); + const docPages = buildDocPages(headNodes, index); - // Process each head node and build content - const results = []; + return Promise.all( + itemIndices.map(async idx => { + const entry = headNodes[idx]; - for (const entry of headNodes) { - const sideBarProps = buildSideBarProps( - entry, - releases, - version, - docPages - ); + const sideBarProps = buildSideBarProps( + entry, + releases, + version, + docPages + ); - results.push( - await buildContent( + return buildContent( groupedModules.get(entry.api), entry, sideBarProps, - remarkRecma - ) - ); - } + processor + ); + }) + ); + }, + + /** + * Generates a JSX AST + * @param {Input} entries + * @param {Partial} options + */ + async generate(entries, { index, releases, version, worker }) { + const headNodes = getSortedHeadNodes(entries); - return results; + return worker.map(headNodes, entries, { index, releases, version }); }, }; diff --git a/src/generators/legacy-html-all/index.mjs b/src/generators/legacy-html-all/index.mjs index ad6584d7..15d448a1 100644 --- a/src/generators/legacy-html-all/index.mjs +++ b/src/generators/legacy-html-all/index.mjs @@ -5,7 +5,7 @@ import { join, resolve } from 'node:path'; import HTMLMinifier from '@minify-html/node'; -import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; +import { getRemarkRehype } from '../../utils/remark.mjs'; import dropdowns from '../legacy-html/utils/buildDropdowns.mjs'; import tableOfContents from '../legacy-html/utils/tableOfContents.mjs'; @@ -49,7 +49,7 @@ export default { const inputWithoutIndex = input.filter(entry => entry.api !== 'index'); // Gets a Remark Processor that parses Markdown to minified HTML - const remarkWithRehype = getRemarkRehypeWithShiki(); + const remarkWithRehype = getRemarkRehype(); // Current directory path relative to the `index.mjs` file // from the `legacy-html` generator, as all the assets are there diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 123cee57..27e1b0a1 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -1,15 +1,15 @@ 'use strict'; -import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import { writeFile, mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import HTMLMinifier from '@minify-html/node'; import buildContent from './utils/buildContent.mjs'; -import dropdowns from './utils/buildDropdowns.mjs'; import { safeCopy } from './utils/safeCopy.mjs'; import tableOfContents from './utils/tableOfContents.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; +import { createTemplateReplacer, getTemplate } from './utils/template.mjs'; +import { getHeadNodes, groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; /** @@ -22,165 +22,133 @@ import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; * nav: string; * content: string; * }} TemplateValues - * + */ + +/** + * Processes a single head node into TemplateValues + * @param {ApiDocMetadataEntry} head + * @param {Object} ctx + * @returns {TemplateValues} + */ +const processNode = (head, ctx) => { + const { groupedModules, headNodes, processor, sideNav, version } = ctx; + + const nodes = groupedModules.get(head.api); + + const activeSideNav = String(sideNav).replace( + `class="nav-${head.api}`, + `class="nav-${head.api} active` + ); + + const toc = processor.processSync( + tableOfContents(nodes, { + maxDepth: 4, + parser: tableOfContents.parseToCNode, + }) + ); + + const content = buildContent(headNodes, nodes, processor); + + const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1); + + return { + api: head.api, + added: head.introduced_in ?? '', + section: head.heading.data.name || apiAsHeading, + version: `v${version.version}`, + toc: String(toc), + nav: activeSideNav, + content, + }; +}; + +/** * This generator generates the legacy HTML pages of the legacy API docs * for retro-compatibility and while we are implementing the new 'react' and 'html' generators. * - * This generator is a top-level generator, and it takes the raw AST tree of the API doc files - * and generates the HTML files to the specified output directory from the configuration settings - * * @typedef {Array} Input - * * @type {GeneratorMetadata>} */ export default { name: 'legacy-html', - version: '1.0.0', - description: 'Generates the legacy version of the API docs in HTML, with the assets and styles included as files', - dependsOn: 'metadata', /** - * Generates the legacy version of the API docs in HTML - * @param {Input} input + * Process a chunk of items in a worker thread. + * @param {Input} fullInput + * @param {number[]} itemIndices * @param {Partial} options */ - async generate(input, { index, releases, version, output }) { - // This array holds all the generated values for each module - const generatedValues = []; - - // Gets a Remark Processor that parses Markdown to minified HTML - const remarkRehypeProcessor = getRemarkRehypeWithShiki(); - - const groupedModules = groupNodesByModule(input); - - // Current directory path relative to the `index.mjs` file - const baseDir = import.meta.dirname; - - // Reads the API template.html file to be used as a base for the HTML files - const apiTemplate = await readFile(join(baseDir, 'template.html'), 'utf-8'); - - // Gets the first nodes of each module, which is considered the "head" of the module - // and sorts them alphabetically by the "name" property of the heading - const headNodes = input - .filter(node => node.heading.depth === 1) - .sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name)); + async processChunk( + fullInput, + itemIndices, + { index, releases, version, output } + ) { + const processor = getRemarkRehypeWithShiki(); + const template = await getTemplate(); + const headNodes = getHeadNodes(fullInput); + const groupedModules = groupNodesByModule(fullInput); const indexOfFiles = index - ? index.map(entry => ({ - api: entry.api, - heading: { data: { depth: 1, name: entry.section } }, + ? index.map(({ api, section: name }) => ({ + api, + heading: { data: { depth: 1, name } }, })) : headNodes; - // Generates the global Table of Contents (Sidebar Navigation) - const parsedSideNav = remarkRehypeProcessor.processSync( + const sideNav = processor.processSync( tableOfContents(indexOfFiles, { maxDepth: 1, parser: tableOfContents.parseNavigationNode, }) ); - /** - * Replaces the aggregated data from a node within a template - * - * @param {TemplateValues} values The values to be replaced in the template - */ - const replaceTemplateValues = values => { - const { api, added, section, version, toc, nav, content } = values; - return apiTemplate - .replace('__ID__', api) - .replace(/__FILENAME__/g, api) - .replace('__SECTION__', section) - .replace(/__VERSION__/g, version) - .replace(/__TOC__/g, tableOfContents.wrapToC(toc)) - .replace(/__GTOC__/g, nav) - .replace('__CONTENT__', content) - .replace(/__TOC_PICKER__/g, dropdowns.buildToC(toc)) - .replace(/__GTOC_PICKER__/g, dropdowns.buildNavigation(nav)) - .replace('__ALTDOCS__', dropdowns.buildVersions(api, added, releases)) - .replace('__EDIT_ON_GITHUB__', dropdowns.buildGitHub(api)); - }; - - /** - * Processes each module node to generate the HTML content - * - * @param {ApiDocMetadataEntry} head The name of the module to be generated - * @param {string} template The template to be used to generate the HTML content - */ - const processModuleNodes = head => { - const nodes = groupedModules.get(head.api); - - // Replaces the entry corresponding to the current module - // as an active entry within the side navigation - const activeSideNav = String(parsedSideNav).replace( - `class="nav-${head.api}`, - `class="nav-${head.api} active` - ); + const replaceTemplate = createTemplateReplacer(template, releases); + const ctx = { groupedModules, headNodes, processor, sideNav, version }; - // Generates the Table of Contents for the current module, which is appended - // to the top of the page and also to a dropdown - const parsedToC = remarkRehypeProcessor.processSync( - tableOfContents(nodes, { - maxDepth: 4, - parser: tableOfContents.parseToCNode, - }) - ); - - // Builds the content of the module, including all sections, - // stability indexes, and content for the current file - const parsedContent = buildContent( - headNodes, - nodes, - remarkRehypeProcessor - ); + const results = await Promise.all( + itemIndices.map(async idx => { + const values = processNode(headNodes[idx], ctx); - // In case there's no Heading, we make a little capitalization of the filename - const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1); + if (output) { + const html = replaceTemplate(values); + const minified = HTMLMinifier.minify(Buffer.from(html), {}); - const generatedTemplate = { - api: head.api, - added: head.introduced_in ?? '', - section: head.heading.data.name || apiAsHeading, - version: `v${version.version}`, - toc: String(parsedToC), - nav: String(activeSideNav), - content: parsedContent, - }; + await writeFile(join(output, `${values.api}.html`), minified); + } - // Adds the generated template to the list of generated values - generatedValues.push(generatedTemplate); - - // Replaces all the values within the template for the current doc - return replaceTemplateValues(generatedTemplate); - }; + return values; + }) + ); - for (const node of headNodes) { - const result = processModuleNodes(node); + return results; + }, - if (output) { - // We minify the html result to reduce the file size and keep it "clean" - const minified = HTMLMinifier.minify(Buffer.from(result), {}); + /** + * Generates the legacy version of the API docs in HTML + * @param {Input} input + * @param {Partial} options + */ + async generate(input, { index, releases, version, output, worker }) { + const headNodes = getHeadNodes(input); - await writeFile(join(output, `${node.api}.html`), minified); - } - } + const generatedValues = await worker.map(headNodes, input, { + index, + releases, + version, + output, + }); if (output) { - // Define the source folder for API docs assets - const srcAssets = join(baseDir, 'assets'); - - // Define the output folder for API docs assets - const assetsFolder = join(output, 'assets'); + await mkdir(join(output, 'assets'), { recursive: true }); - // Creates the assets folder if it does not exist - await mkdir(assetsFolder, { recursive: true }); - - // Copy all files from assets folder to output, skipping unchanged files - await safeCopy(srcAssets, assetsFolder); + await safeCopy( + join(import.meta.dirname, 'assets'), + join(output, 'assets') + ); } return generatedValues; diff --git a/src/generators/legacy-html/utils/template.mjs b/src/generators/legacy-html/utils/template.mjs new file mode 100644 index 00000000..2abd278f --- /dev/null +++ b/src/generators/legacy-html/utils/template.mjs @@ -0,0 +1,48 @@ +'use strict'; + +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import dropdowns from './buildDropdowns.mjs'; +import tableOfContents from './tableOfContents.mjs'; + +// Cached template (loaded once per worker) +let cachedTemplate; + +/** + * Gets the template for the legacy HTML pages (cached per process/worker) + * @returns {Promise} + */ +export const getTemplate = async () => { + if (!cachedTemplate) { + cachedTemplate = await readFile( + join(import.meta.dirname, '..', 'template.html'), + 'utf-8' + ); + } + + return cachedTemplate; +}; + +/** + * Creates a template replacer function + * @param {string} template + * @param {Array} releases + * @returns {(values: TemplateValues) => string} + */ +export const createTemplateReplacer = (template, releases) => values => { + const { api, added, section, version, toc, nav, content } = values; + + return template + .replace('__ID__', api) + .replace(/__FILENAME__/g, api) + .replace('__SECTION__', section) + .replace(/__VERSION__/g, version) + .replace(/__TOC__/g, tableOfContents.wrapToC(toc)) + .replace(/__GTOC__/g, nav) + .replace('__CONTENT__', content) + .replace(/__TOC_PICKER__/g, dropdowns.buildToC(toc)) + .replace(/__GTOC_PICKER__/g, dropdowns.buildNavigation(nav)) + .replace('__ALTDOCS__', dropdowns.buildVersions(api, added, releases)) + .replace('__EDIT_ON_GITHUB__', dropdowns.buildGitHub(api)); +}; diff --git a/src/generators/legacy-json/index.mjs b/src/generators/legacy-json/index.mjs index 1883b286..9457e8a2 100644 --- a/src/generators/legacy-json/index.mjs +++ b/src/generators/legacy-json/index.mjs @@ -4,7 +4,7 @@ import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { createSectionBuilder } from './utils/buildSection.mjs'; -import { groupNodesByModule } from '../../utils/generators.mjs'; +import { getHeadNodes, groupNodesByModule } from '../../utils/generators.mjs'; /** * This generator is responsible for generating the legacy JSON files for the @@ -29,51 +29,48 @@ export default { dependsOn: 'metadata', /** - * Generates a legacy JSON file. + * Process a chunk of items in a worker thread. + * Called by chunk-worker.mjs for parallel processing. * - * @param {Input} input + * @param {Input} fullInput - Full input to rebuild context + * @param {number[]} itemIndices - Indices of head nodes to process * @param {Partial} options + * @returns {Promise} */ - async generate(input, { output }) { + async processChunk(fullInput, itemIndices, { output }) { const buildSection = createSectionBuilder(); + const groupedModules = groupNodesByModule(fullInput); + const headNodes = getHeadNodes(fullInput); - // This array holds all the generated values for each module - const generatedValues = []; - - const groupedModules = groupNodesByModule(input); + const results = []; - // Gets the first nodes of each module, which is considered the "head" - const headNodes = input.filter(node => node.heading.depth === 1); - - /** - * @param {ApiDocMetadataEntry} head - * @returns {import('./types.d.ts').ModuleSection} - */ - const processModuleNodes = head => { + for (const idx of itemIndices) { + const head = headNodes[idx]; const nodes = groupedModules.get(head.api); - const section = buildSection(head, nodes); - generatedValues.push(section); + if (output) { + await writeFile( + join(output, `${head.api}.json`), + JSON.stringify(section) + ); + } - return section; - }; + results.push(section); + } - await Promise.all( - headNodes.map(async node => { - // Get the json for the node's section - const section = processModuleNodes(node); + return results; + }, - // Write it to the output file - if (output) { - await writeFile( - join(output, `${node.api}.json`), - JSON.stringify(section) - ); - } - }) - ); + /** + * Generates a legacy JSON file. + * + * @param {Input} input + * @param {Partial} options + */ + async generate(input, { output, worker }) { + const headNodes = getHeadNodes(input); - return generatedValues; + return worker.map(headNodes, input, { output }); }, }; diff --git a/src/generators/metadata/index.mjs b/src/generators/metadata/index.mjs index 37a7d0d0..750dd82c 100644 --- a/src/generators/metadata/index.mjs +++ b/src/generators/metadata/index.mjs @@ -18,12 +18,32 @@ export default { dependsOn: 'ast', + /** + * Process a chunk of API doc files in a worker thread. + * Called by chunk-worker.mjs for parallel processing. + * + * @param {Input} fullInput - Full input array + * @param {number[]} itemIndices - Indices of items to process + * @param {Partial} options + */ + async processChunk(fullInput, itemIndices, { typeMap }) { + const results = []; + + for (const idx of itemIndices) { + results.push(...parseApiDoc(fullInput[idx], typeMap)); + } + + return results; + }, + /** * @param {Input} inputs * @param {GeneratorOptions} options * @returns {Promise>} */ - async generate(inputs, { typeMap }) { - return inputs.flatMap(input => parseApiDoc(input, typeMap)); + async generate(inputs, { typeMap, worker }) { + const results = await worker.map(inputs, inputs, { typeMap }); + + return results.flat(); }, }; diff --git a/src/generators/orama-db/index.mjs b/src/generators/orama-db/index.mjs index 2b91375a..6d6b047a 100644 --- a/src/generators/orama-db/index.mjs +++ b/src/generators/orama-db/index.mjs @@ -65,12 +65,14 @@ export default { } const db = create({ schema: SCHEMA }); + const apiGroups = groupNodesByModule(input); // Process all API groups and flatten into a single document array const documents = Array.from(apiGroups.values()).flatMap(headings => headings.map((entry, index) => { const hierarchicalTitle = buildHierarchicalTitle(headings, index); + const paragraph = entry.content.children.find( child => child.type === 'paragraph' ); diff --git a/src/generators/types.d.ts b/src/generators/types.d.ts index df72fce3..6659e488 100644 --- a/src/generators/types.d.ts +++ b/src/generators/types.d.ts @@ -1,4 +1,3 @@ -import type { SemVer } from 'semver'; import type { ApiDocReleaseEntry } from '../types'; import type { publicGenerators } from './index.mjs'; @@ -7,6 +6,33 @@ declare global { // to be type complete and runtime friendly within `runGenerators` export type AvailableGenerators = typeof publicGenerators; + // ParallelWorker interface for item-level parallelization using real worker threads + export interface ParallelWorker { + /** + * Process items in parallel using real worker threads. + * Items are split into chunks, each chunk processed by a separate worker. + * + * @param items - Items to process (used to determine indices) + * @param fullInput - Full input data for context rebuilding in workers + * @param opts - Additional options to pass to workers + * @returns Results in same order as input items + */ + map( + items: T[], + fullInput: unknown, + opts?: Record + ): Promise; + + /** + * Process items in parallel, ignoring return values. + */ + forEach( + items: T[], + fullInput: unknown, + opts?: Record + ): Promise; + } + // This is the runtime config passed to the API doc generators export interface GeneratorOptions { // The path to the input source files. This parameter accepts globs and can @@ -43,8 +69,14 @@ declare global { // The number of threads the process is allowed to use threads: number; + // Number of items to process per worker thread + chunkSize: number; + // The type map typeMap: Record; + + // Parallel worker instance for generators to parallelize work on individual items + worker: ParallelWorker; } export interface GeneratorMetadata { @@ -91,5 +123,23 @@ declare global { * Hence you can combine different generators to achieve different outputs. */ generate: (input: I, options: Partial) => Promise; + + /** + * Optional method for chunk-level parallelization using real worker threads. + * Called by chunk-worker.mjs when processing items in parallel. + * + * Generators that implement this method can have their work distributed + * across multiple worker threads for true parallel processing. + * + * @param fullInput - Full input data (for rebuilding context in workers) + * @param itemIndices - Array of indices of items to process + * @param options - Generator options (without worker, which isn't serializable) + * @returns Array of results for the processed items + */ + processChunk?: ( + fullInput: I, + itemIndices: number[], + options: Partial> + ) => Promise; } } diff --git a/src/generators/web/index.mjs b/src/generators/web/index.mjs index 71f2f015..e9f62f1a 100644 --- a/src/generators/web/index.mjs +++ b/src/generators/web/index.mjs @@ -13,7 +13,13 @@ import { processJSXEntries } from './utils/processing.mjs'; * - Client-side JavaScript with code splitting * - Bundled CSS styles * - * @type {GeneratorMetadata} + * NOTE: This generator does NOT implement processChunk because: + * 1. Server-side bundling requires all entries together for code splitting + * 2. Client-side bundling requires all entries together for shared chunks + * 3. The parallelization benefit is in the upstream jsx-ast generator + * + * @typedef {import('../jsx-ast/utils/buildContent.mjs').JSXContent[]} Input + * @type {GeneratorMetadata>} */ export default { name: 'web', @@ -23,15 +29,11 @@ export default { /** * Main generation function that processes JSX AST entries into web bundles. - * - * @param {import('../jsx-ast/utils/buildContent.mjs').JSXContent[]} entries - JSX AST entries to process. - * @param {Partial} options - Generator options. - * @param {string} [options.output] - Output directory for generated files. - * @param {string} options.version - Documentation version string. - * @returns {Promise>} Generated HTML and CSS. + * @param {Input} entries + * @param {Partial} options */ async generate(entries, { output, version }) { - // Load the HTML template with placeholders + // Load template from file const template = await readFile( new URL('template.html', import.meta.url), 'utf-8' @@ -40,10 +42,11 @@ export default { // Create AST builders for server and client programs const astBuilders = createASTBuilder(); - // Create require function for resolving external packages in server code + // Create require function - must be created here, not at module level, + // to ensure correct resolution context in worker threads const requireFn = createRequire(import.meta.url); - // Process all entries: convert JSX to HTML/CSS/JS + // Process all entries together (required for code splitting optimization) const { results, css, chunks } = await processJSXEntries( entries, template, @@ -54,21 +57,17 @@ export default { // Write files to disk if output directory is specified if (output) { - // Write HTML files - for (const { html, api } of results) { - await writeFile(join(output, `${api}.html`), html, 'utf-8'); - } - - // Write code-split JavaScript chunks - for (const chunk of chunks) { - await writeFile(join(output, chunk.fileName), chunk.code, 'utf-8'); - } - - // Write CSS bundle - await writeFile(join(output, 'styles.css'), css, 'utf-8'); + await Promise.all([ + ...results.map(({ html, api }) => + writeFile(join(output, `${api}.html`), html, 'utf-8') + ), + ...chunks.map(chunk => + writeFile(join(output, chunk.fileName), chunk.code, 'utf-8') + ), + writeFile(join(output, 'styles.css'), css, 'utf-8'), + ]); } - // Return HTML and CSS for each entry return results.map(({ html }) => ({ html, css })); }, }; diff --git a/src/generators/web/utils/bundle.mjs b/src/generators/web/utils/bundle.mjs index ce492fc5..b92fe9a7 100644 --- a/src/generators/web/utils/bundle.mjs +++ b/src/generators/web/utils/bundle.mjs @@ -1,9 +1,18 @@ +import { join } from 'node:path'; + import virtual from '@rollup/plugin-virtual'; import { build } from 'rolldown'; import cssLoader from './css.mjs'; import staticData from './data.mjs'; +// Resolve node_modules relative to this package (doc-kit), not cwd. +// This ensures modules are found when running from external directories. +const DOC_KIT_NODE_MODULES = join( + import.meta.dirname, + '../../../../node_modules' +); + /** * Asynchronously bundles JavaScript source code (and its CSS imports), * targeting either browser (client) or server (Node.js) environments. @@ -71,14 +80,21 @@ export default async function bundleCode(codeMap, { server = false } = {}) { jsx: 'react-jsx', }, - // Module resolution aliases. - // This tells the bundler to use `preact/compat` wherever `react` or `react-dom` is imported. - // Allows you to write React-style code but ship much smaller Preact bundles. + // Module resolution configuration. resolve: { + // Alias react imports to preact/compat for smaller bundle sizes. + // Explicit jsx-runtime aliases are required for the automatic JSX transform. alias: { react: 'preact/compat', 'react-dom': 'preact/compat', + 'react/jsx-runtime': 'preact/jsx-runtime', + 'react/jsx-dev-runtime': 'preact/jsx-dev-runtime', }, + + // Tell the bundler where to find node_modules. + // This ensures packages are found when running doc-kit from external directories + // (e.g., running from the node repository via tools/doc/node_modules/.bin/doc-kit). + modules: [DOC_KIT_NODE_MODULES, 'node_modules'], }, // Array of plugins to apply during the build. diff --git a/src/generators/web/utils/processing.mjs b/src/generators/web/utils/processing.mjs index eeb5c091..2c61211f 100644 --- a/src/generators/web/utils/processing.mjs +++ b/src/generators/web/utils/processing.mjs @@ -49,8 +49,9 @@ export async function executeServerCode(serverCodeMap, requireFn) { * @param {Array} entries - The JSX AST entry to process. * @param {string} template - The HTML template string that serves as the base for the output page. * @param {ReturnType} astBuilders - The AST generators - * @param {version} version - The version to generator the documentation for * @param {ReturnType} requireFn - A Node.js `require` function. + * @param {Object} options - Processing options + * @param {Object} options.version - Version info */ export async function processJSXEntries( entries, diff --git a/src/threading/chunk-worker.mjs b/src/threading/chunk-worker.mjs new file mode 100644 index 00000000..9f21efe1 --- /dev/null +++ b/src/threading/chunk-worker.mjs @@ -0,0 +1,12 @@ +import { parentPort, workerData } from 'node:worker_threads'; + +import { allGenerators } from '../generators/index.mjs'; + +const { generatorName, fullInput, itemIndices, options } = workerData; +const generator = allGenerators[generatorName]; + +// Generators must implement processChunk for item-level parallelization +generator + .processChunk(fullInput, itemIndices, options) + .then(result => parentPort.postMessage(result)) + .catch(error => parentPort.postMessage({ error: error.message })); diff --git a/src/threading/generator-worker.mjs b/src/threading/generator-worker.mjs new file mode 100644 index 00000000..bd2d4e20 --- /dev/null +++ b/src/threading/generator-worker.mjs @@ -0,0 +1,18 @@ +import { parentPort, workerData } from 'node:worker_threads'; + +import WorkerPool from './index.mjs'; +import createParallelWorker from './parallel.mjs'; +import { allGenerators } from '../generators/index.mjs'; + +const { generatorName, input, options } = workerData; +const generator = allGenerators[generatorName]; + +// Create a ParallelWorker for the generator to use (for item-level parallelization) +const chunkPool = new WorkerPool('./chunk-worker.mjs', options.threads ?? 1); +const worker = createParallelWorker(generatorName, chunkPool, options); + +// Execute the generator and send the result or error back to the parent thread +generator + .generate(input, { ...options, worker }) + .then(result => parentPort.postMessage(result)) + .catch(error => parentPort.postMessage({ error: error.message })); diff --git a/src/threading/index.mjs b/src/threading/index.mjs index dc7872a6..b30945cb 100644 --- a/src/threading/index.mjs +++ b/src/threading/index.mjs @@ -1,9 +1,14 @@ import { Worker } from 'node:worker_threads'; /** - * WorkerPool class to manage a pool of worker threads + * WorkerPool class to manage a pool of worker threads. + * Can be configured with different worker scripts for different use cases. */ export default class WorkerPool { + /** @private {URL} - Path to the worker script */ + workerScript; + /** @private {number} - Maximum concurrent threads */ + maxThreads; /** @private {SharedArrayBuffer} - Shared memory for active thread count */ sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT); /** @private {Int32Array} - A typed array to access shared memory */ @@ -11,6 +16,19 @@ export default class WorkerPool { /** @private {Array} - Queue of pending tasks */ queue = []; + /** + * @param {string | URL} workerScript - Path to the worker script (relative to this file or absolute URL) + * @param {number} maxThreads - Maximum number of concurrent worker threads + */ + constructor(workerScript = './generator-worker.mjs', maxThreads = 1) { + this.workerScript = + workerScript instanceof URL + ? workerScript + : new URL(workerScript, import.meta.url); + + this.maxThreads = Math.max(1, maxThreads); + } + /** * Gets the current active thread count. * @returns {number} The current active thread count. @@ -28,64 +46,68 @@ export default class WorkerPool { } /** - * Runs a generator within a worker thread. - * @param {string} name - The name of the generator to execute - * @param {any} dependencyOutput - Input data for the generator - * @param {number} threads - Maximum number of threads to run concurrently - * @param {Object} extra - Additional options for the generator - * @returns {Promise} Resolves with the generator result, or rejects with an error + * Runs a task in a worker thread with the given data. + * @param {Object} workerData - Data to pass to the worker thread + * @returns {Promise} Resolves with the worker result, or rejects with an error */ - run(name, dependencyOutput, threads, extra) { + run(workerData) { return new Promise((resolve, reject) => { /** - * Function to run the generator in a worker thread + * Executes the worker thread by creating a new Worker instance and + * handling the message and error events. */ - const run = () => { + const execute = () => { this.changeActiveThreadCount(1); - // Create and start the worker thread - const worker = new Worker(new URL('./worker.mjs', import.meta.url), { - workerData: { name, dependencyOutput, extra }, - }); + const worker = new Worker(this.workerScript, { workerData }); - // Handle worker thread messages (result or error) worker.on('message', result => { this.changeActiveThreadCount(-1); - this.processQueue(threads); + this.processQueue(); if (result?.error) { - reject(result.error); + reject(new Error(result.error)); } else { resolve(result); } }); - // Handle worker thread errors worker.on('error', err => { this.changeActiveThreadCount(-1); - this.processQueue(threads); + this.processQueue(); reject(err); }); }; - // If the active thread count exceeds the limit, add the task to the queue - if (this.getActiveThreadCount() >= threads) { - this.queue.push(run); + if (this.getActiveThreadCount() >= this.maxThreads) { + this.queue.push(execute); } else { - run(); + execute(); } }); } /** - * Process the worker thread queue to start the next available task - * when there is room for more threads. - * @param {number} threads - Maximum number of threads to run concurrently + * Run multiple tasks in parallel, distributing across worker threads. + * @template T, R + * @param {T[]} tasks - Array of task data to process + * @returns {Promise} Results in same order as input tasks + */ + async runAll(tasks) { + return Promise.all(tasks.map(task => this.run(task))); + } + + /** + * Process the worker thread queue to start the next available task. * @private */ - processQueue(threads) { - if (this.queue.length > 0 && this.getActiveThreadCount() < threads) { + processQueue() { + while ( + this.queue.length > 0 && + this.getActiveThreadCount() < this.maxThreads + ) { const next = this.queue.shift(); + if (next) { next(); } diff --git a/src/threading/parallel.mjs b/src/threading/parallel.mjs new file mode 100644 index 00000000..9be48c9f --- /dev/null +++ b/src/threading/parallel.mjs @@ -0,0 +1,159 @@ +'use strict'; + +/** + * Calculates optimal chunk distribution for parallel processing. + * Balances work evenly across workers while respecting max chunk size. + * + * @param {number} itemCount - Total number of items + * @param {number} threads - Number of available worker threads + * @param {number} maxChunkSize - Maximum items per chunk + * @returns {{ chunkSize: number, numChunks: number }} + */ +function calculateChunkStrategy(itemCount, threads, maxChunkSize) { + // Determine how many chunks we want (ideally one per thread, but not more than items) + const targetChunks = Math.min(threads, itemCount); + + // Calculate base chunk size to distribute work evenly + const baseChunkSize = Math.ceil(itemCount / targetChunks); + + // Respect the max chunk size limit + const chunkSize = Math.min(baseChunkSize, maxChunkSize); + + // Calculate actual number of chunks needed + const numChunks = Math.ceil(itemCount / chunkSize); + + return { chunkSize, numChunks }; +} + +/** + * Splits indices into chunks of specified size. + * @param {number} count - Number of items + * @param {number} chunkSize - Items per chunk + * @returns {number[][]} Array of index arrays + */ +function createIndexChunks(count, chunkSize) { + const chunks = []; + + for (let i = 0; i < count; i += chunkSize) { + const end = Math.min(i + chunkSize, count); + chunks.push(Array.from({ length: end - i }, (_, j) => i + j)); + } + + return chunks; +} + +/** + * Creates a ParallelWorker that uses real Node.js Worker threads + * for parallel processing of items. + * + * @param {string} generatorName - Name of the generator (for chunk processing) + * @param {import('./index.mjs').default} pool - WorkerPool instance for spawning workers + * @param {object} options - Generator options + * @returns {ParallelWorker} + */ +export default function createParallelWorker(generatorName, pool, options) { + const { threads = 1, chunkSize: maxChunkSize = 20 } = options; + + // Cache for lazy-loaded generator reference + let cachedGenerator; + + /** + * Gets the generator (lazy-loaded and cached) + */ + const getGenerator = async () => { + if (!cachedGenerator) { + const { allGenerators } = await import('../generators/index.mjs'); + + cachedGenerator = allGenerators[generatorName]; + } + return cachedGenerator; + }; + + /** + * Strips non-serializable properties from options for worker transfer + * @param {object} opts - Options to serialize + */ + const serializeOptions = opts => { + const serialized = { ...options, ...opts }; + + delete serialized.worker; + + return serialized; + }; + + return { + /** + * Process items in parallel using real worker threads. + * Items are split into chunks, each chunk processed by a separate worker. + * + * @template T, R + * @param {T[]} items - Items to process (must be serializable) + * @param {T[]} fullInput - Full input data for context rebuilding in workers + * @param {object} opts - Additional options to pass to workers + * @returns {Promise} - Results in same order as input items + */ + async map(items, fullInput, opts = {}) { + const itemCount = items.length; + + if (itemCount === 0) { + return []; + } + + const generator = await getGenerator(); + + if (!generator.processChunk) { + throw new Error( + `Generator "${generatorName}" does not support chunk processing` + ); + } + + // For single thread, single item, or very small workloads - run in main thread + // Worker overhead isn't worth it for small tasks + const shouldUseMainThread = threads <= 1 || itemCount <= 2; + + if (shouldUseMainThread) { + const indices = Array.from({ length: itemCount }, (_, i) => i); + + return generator.processChunk(fullInput, indices, { + ...options, + ...opts, + }); + } + + // Calculate optimal chunk distribution + const { chunkSize } = calculateChunkStrategy( + itemCount, + threads, + maxChunkSize + ); + + // Create index chunks for parallel processing + const indexChunks = createIndexChunks(itemCount, chunkSize); + + // Process chunks in parallel using worker threads + const chunkResults = await pool.runAll( + indexChunks.map(indices => ({ + generatorName, + fullInput, + itemIndices: indices, + options: serializeOptions(opts), + })) + ); + + // Flatten results (each worker returns array of results for its chunk) + return chunkResults.flat(); + }, + + /** + * Process items in parallel, ignoring return values. + * @template T + * @param {T[]} items - Items to process + * @param {T[]} fullInput - Full input data for context rebuilding + * @param {object} opts - Additional options + * @returns {Promise} + */ + async forEach(items, fullInput, opts = {}) { + await this.map(items, fullInput, opts); + }, + }; +} diff --git a/src/threading/worker.mjs b/src/threading/worker.mjs deleted file mode 100644 index ab107eac..00000000 --- a/src/threading/worker.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import { parentPort, workerData } from 'node:worker_threads'; - -import { allGenerators } from '../generators/index.mjs'; - -const { name, dependencyOutput, extra } = workerData; -const generator = allGenerators[name]; - -// Execute the generator and send the result or error back to the parent thread -generator - .generate(dependencyOutput, extra) - .then(result => parentPort.postMessage(result)) - .catch(error => parentPort.postMessage({ error })); diff --git a/src/utils/generators.mjs b/src/utils/generators.mjs index 7dbf2590..82957d80 100644 --- a/src/utils/generators.mjs +++ b/src/utils/generators.mjs @@ -3,6 +3,60 @@ import { coerce, compare, major } from 'semver'; import { DOC_API_BASE_URL_VERSION } from '../constants.mjs'; +import { OVERRIDDEN_POSITIONS } from '../generators/jsx-ast/constants.mjs'; + +/** + * Gets head nodes (depth === 1) sorted alphabetically by name. + * Used by legacy-html and legacy-json generators. + * + * @param {Array} entries + * @returns {Array} + */ +export const getHeadNodes = entries => + entries + .filter(node => node.heading.depth === 1) + .sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name)); + +/** + * Gets head nodes (depth === 1) sorted with overridden positions first, + * then alphabetically. Used by jsx-ast and web generators. + * + * @param {Array} entries + * @returns {Array} + */ +export const getSortedHeadNodes = entries => + entries + .filter(node => node.heading.depth === 1) + .sort((a, b) => { + const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); + const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); + + if (ai !== -1 && bi !== -1) { + return ai - bi; + } + + if (ai !== -1) { + return -1; + } + + if (bi !== -1) { + return 1; + } + + return a.heading.data.name.localeCompare(b.heading.data.name); + }); + +/** + * Builds doc pages array for sidebar navigation. + * + * @param {Array} headNodes + * @param {Array<{section: string, api: string}>} [index] + * @returns {Array<[string, string]>} + */ +export const buildDocPages = (headNodes, index) => + index + ? index.map(({ section, api }) => [section, `${api}.html`]) + : headNodes.map(node => [node.heading.data.name, `${node.api}.html`]); /** * Groups all the API metadata nodes by module (`api` property) so that we can process each different file From 0261be7ca9ea882d6e2abb9407c0a6b35c2f4842 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 00:51:33 +0100 Subject: [PATCH 2/9] chore: self review and code review --- src/generators.mjs | 33 +-- src/generators/ast-js/index.mjs | 9 +- src/generators/jsx-ast/index.mjs | 76 +++++-- src/generators/legacy-html/index.mjs | 197 +++++++++++------- src/generators/legacy-html/utils/template.mjs | 48 ----- src/generators/legacy-json/index.mjs | 14 +- src/generators/web/index.mjs | 45 ++-- src/threading/generator-worker.mjs | 4 +- src/threading/index.mjs | 30 +-- src/threading/parallel.mjs | 115 ++++------ src/utils/generators.mjs | 54 ----- 11 files changed, 263 insertions(+), 362 deletions(-) delete mode 100644 src/generators/legacy-html/utils/template.mjs diff --git a/src/generators.mjs b/src/generators.mjs index eb76fa59..b0ccb538 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -37,21 +37,15 @@ const createGenerator = input => { * * @param {GeneratorOptions} options The options for the generator runtime */ - const runGenerators = async ({ - generators, - threads, - chunkSize, - ...extra - }) => { + const runGenerators = async options => { + const { generators, threads } = options; + // WorkerPool for running full generators in worker threads const generatorPool = new WorkerPool('./generator-worker.mjs', threads); // WorkerPool for chunk-level parallelization within generators const chunkPool = new WorkerPool('./chunk-worker.mjs', threads); - // Options including threading config - const threadingOptions = { threads, chunkSize }; - // Note that this method is blocking, and will only execute one generator per-time // but it ensures all dependencies are resolved, and that multiple bottom-level generators // can reuse the already parsed content from the top-level/dependency generators @@ -61,11 +55,7 @@ const createGenerator = input => { // If the generator dependency has not yet been resolved, we resolve // the dependency first before running the current generator if (dependsOn && dependsOn in cachedGenerators === false) { - await runGenerators({ - ...extra, - ...threadingOptions, - generators: [dependsOn], - }); + await runGenerators({ ...options, generators: [dependsOn] }); } // Ensures that the dependency output gets resolved before we run the current @@ -73,22 +63,13 @@ const createGenerator = input => { const input = await cachedGenerators[dependsOn]; // Create a ParallelWorker for this generator to use for item-level parallelization - const worker = createParallelWorker(generatorName, chunkPool, { - ...extra, - ...threadingOptions, - }); - - // Generator options with worker instance - const generatorOptions = { ...extra, ...threadingOptions, worker }; - - // Worker options for the worker thread - const workerOptions = { ...extra, ...threadingOptions }; + const worker = createParallelWorker(generatorName, chunkPool, options); // Adds the current generator execution Promise to the cache cachedGenerators[generatorName] = threads < 2 - ? generate(input, generatorOptions) // Run in main thread - : generatorPool.run({ generatorName, input, options: workerOptions }); // Offload to worker thread + ? generate(input, { ...options, worker }) + : generatorPool.run({ generatorName, input, options }); } // Returns the value of the last generator of the current pipeline diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index fef63b66..973f1b26 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -24,15 +24,12 @@ export default { /** * Process a chunk of JavaScript files in a worker thread. - * Called by chunk-worker.mjs for parallel processing. - * - * @param {unknown} _ - Unused (we use options.input instead) - * @param {number[]} itemIndices - Indices of source files to process + * @param {unknown} _ + * @param {number[]} itemIndices * @param {Partial} options */ async processChunk(_, itemIndices, { input }) { const { loadFiles } = createJsLoader(); - const sourceFiles = loadFiles(input ?? []); const { parseJsSource } = createJsParser(); @@ -57,8 +54,6 @@ export default { const sourceFiles = loadFiles(input ?? []); // Parse the Javascript sources into ASTs in parallel using worker threads - // Note: We pass sourceFiles as items but _ (empty) as fullInput since - // processChunk reloads files from options.input return worker.map(sourceFiles, _, { input }); }, }; diff --git a/src/generators/jsx-ast/index.mjs b/src/generators/jsx-ast/index.mjs index 839d3ccb..3fc357d9 100644 --- a/src/generators/jsx-ast/index.mjs +++ b/src/generators/jsx-ast/index.mjs @@ -1,10 +1,7 @@ +import { OVERRIDDEN_POSITIONS } from './constants.mjs'; import { buildSideBarProps } from './utils/buildBarProps.mjs'; import buildContent from './utils/buildContent.mjs'; -import { - buildDocPages, - getSortedHeadNodes, - groupNodesByModule, -} from '../../utils/generators.mjs'; +import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRecma } from '../../utils/remark.mjs'; /** @@ -13,6 +10,34 @@ import { getRemarkRecma } from '../../utils/remark.mjs'; * @typedef {Array} Input * @type {GeneratorMetadata} */ + +/** + * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. + * @param {Array} entries + */ +const getSortedHeadNodes = entries => { + return entries + .filter(node => node.heading.depth === 1) + .sort((a, b) => { + const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); + const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); + + if (ai !== -1 && bi !== -1) { + return ai - bi; + } + + if (ai !== -1) { + return -1; + } + + if (bi !== -1) { + return 1; + } + + return a.heading.data.name.localeCompare(b.heading.data.name); + }); +}; + export default { name: 'jsx-ast', version: '1.0.0', @@ -26,36 +51,45 @@ export default { * @param {Partial} options */ async processChunk(fullInput, itemIndices, { index, releases, version }) { - const processor = getRemarkRecma(); + const remarkRecma = getRemarkRecma(); const groupedModules = groupNodesByModule(fullInput); const headNodes = getSortedHeadNodes(fullInput); - const docPages = buildDocPages(headNodes, index); - return Promise.all( - itemIndices.map(async idx => { - const entry = headNodes[idx]; + const docPages = index + ? index.map(({ section, api }) => [section, `${api}.html`]) + : headNodes.map(node => [node.heading.data.name, `${node.api}.html`]); - const sideBarProps = buildSideBarProps( - entry, - releases, - version, - docPages - ); + const results = []; - return buildContent( + for (const idx of itemIndices) { + const entry = headNodes[idx]; + + const sideBarProps = buildSideBarProps( + entry, + releases, + version, + docPages + ); + + results.push( + await buildContent( groupedModules.get(entry.api), entry, sideBarProps, - processor - ); - }) - ); + remarkRecma + ) + ); + } + + return results; }, /** * Generates a JSX AST + * * @param {Input} entries * @param {Partial} options + * @returns {Promise>} Array of generated content */ async generate(entries, { index, releases, version, worker }) { const headNodes = getSortedHeadNodes(entries); diff --git a/src/generators/legacy-html/index.mjs b/src/generators/legacy-html/index.mjs index 27e1b0a1..525c7978 100644 --- a/src/generators/legacy-html/index.mjs +++ b/src/generators/legacy-html/index.mjs @@ -1,17 +1,24 @@ 'use strict'; -import { writeFile, mkdir } from 'node:fs/promises'; +import { readFile, writeFile, mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import HTMLMinifier from '@minify-html/node'; import buildContent from './utils/buildContent.mjs'; +import dropdowns from './utils/buildDropdowns.mjs'; import { safeCopy } from './utils/safeCopy.mjs'; import tableOfContents from './utils/tableOfContents.mjs'; -import { createTemplateReplacer, getTemplate } from './utils/template.mjs'; -import { getHeadNodes, groupNodesByModule } from '../../utils/generators.mjs'; +import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; +/** + * Creates a heading object with the given name. + * @param {string} name - The name of the heading + * @returns {HeadingMetadataEntry} The heading object + */ +const getHeading = name => ({ data: { depth: 1, name } }); + /** * @typedef {{ * api: string; @@ -22,58 +29,25 @@ import { getRemarkRehypeWithShiki } from '../../utils/remark.mjs'; * nav: string; * content: string; * }} TemplateValues - */ - -/** - * Processes a single head node into TemplateValues - * @param {ApiDocMetadataEntry} head - * @param {Object} ctx - * @returns {TemplateValues} - */ -const processNode = (head, ctx) => { - const { groupedModules, headNodes, processor, sideNav, version } = ctx; - - const nodes = groupedModules.get(head.api); - - const activeSideNav = String(sideNav).replace( - `class="nav-${head.api}`, - `class="nav-${head.api} active` - ); - - const toc = processor.processSync( - tableOfContents(nodes, { - maxDepth: 4, - parser: tableOfContents.parseToCNode, - }) - ); - - const content = buildContent(headNodes, nodes, processor); - - const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1); - - return { - api: head.api, - added: head.introduced_in ?? '', - section: head.heading.data.name || apiAsHeading, - version: `v${version.version}`, - toc: String(toc), - nav: activeSideNav, - content, - }; -}; - -/** + * * This generator generates the legacy HTML pages of the legacy API docs * for retro-compatibility and while we are implementing the new 'react' and 'html' generators. * + * This generator is a top-level generator, and it takes the raw AST tree of the API doc files + * and generates the HTML files to the specified output directory from the configuration settings + * * @typedef {Array} Input + * * @type {GeneratorMetadata>} */ export default { name: 'legacy-html', + version: '1.0.0', + description: 'Generates the legacy version of the API docs in HTML, with the assets and styles included as files', + dependsOn: 'metadata', /** @@ -85,44 +59,83 @@ export default { async processChunk( fullInput, itemIndices, - { index, releases, version, output } + { releases, version, output, apiTemplate, parsedSideNav } ) { - const processor = getRemarkRehypeWithShiki(); - const template = await getTemplate(); - const headNodes = getHeadNodes(fullInput); + const remarkRehypeProcessor = getRemarkRehypeWithShiki(); const groupedModules = groupNodesByModule(fullInput); - const indexOfFiles = index - ? index.map(({ api, section: name }) => ({ - api, - heading: { data: { depth: 1, name } }, - })) - : headNodes; + const headNodes = fullInput + .filter(node => node.heading.depth === 1) + .sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name)); + + /** + * Replaces the template values in the API template with the given values. + * @param {TemplateValues} values - The values to replace the template values with + * @returns {string} The replaced template values + */ + const replaceTemplateValues = values => { + const { api, added, section, version, toc, nav, content } = values; + + return apiTemplate + .replace('__ID__', api) + .replace(/__FILENAME__/g, api) + .replace('__SECTION__', section) + .replace(/__VERSION__/g, version) + .replace(/__TOC__/g, tableOfContents.wrapToC(toc)) + .replace(/__GTOC__/g, nav) + .replace('__CONTENT__', content) + .replace(/__TOC_PICKER__/g, dropdowns.buildToC(toc)) + .replace(/__GTOC_PICKER__/g, dropdowns.buildNavigation(nav)) + .replace('__ALTDOCS__', dropdowns.buildVersions(api, added, releases)) + .replace('__EDIT_ON_GITHUB__', dropdowns.buildGitHub(api)); + }; + + const results = []; + + for (const idx of itemIndices) { + const head = headNodes[idx]; + const nodes = groupedModules.get(head.api); + + const activeSideNav = String(parsedSideNav).replace( + `class="nav-${head.api}`, + `class="nav-${head.api} active` + ); - const sideNav = processor.processSync( - tableOfContents(indexOfFiles, { - maxDepth: 1, - parser: tableOfContents.parseNavigationNode, - }) - ); + const parsedToC = remarkRehypeProcessor.processSync( + tableOfContents(nodes, { + maxDepth: 4, + parser: tableOfContents.parseToCNode, + }) + ); - const replaceTemplate = createTemplateReplacer(template, releases); - const ctx = { groupedModules, headNodes, processor, sideNav, version }; + const parsedContent = buildContent( + headNodes, + nodes, + remarkRehypeProcessor + ); - const results = await Promise.all( - itemIndices.map(async idx => { - const values = processNode(headNodes[idx], ctx); + const apiAsHeading = head.api.charAt(0).toUpperCase() + head.api.slice(1); - if (output) { - const html = replaceTemplate(values); - const minified = HTMLMinifier.minify(Buffer.from(html), {}); + const generatedTemplate = { + api: head.api, + added: head.introduced_in ?? '', + section: head.heading.data.name || apiAsHeading, + version: `v${version.version}`, + toc: String(parsedToC), + nav: String(activeSideNav), + content: parsedContent, + }; - await writeFile(join(output, `${values.api}.html`), minified); - } + if (output) { + // We minify the html result to reduce the file size and keep it "clean" + const result = replaceTemplateValues(generatedTemplate); + const minified = HTMLMinifier.minify(Buffer.from(result), {}); - return values; - }) - ); + await writeFile(join(output, `${head.api}.html`), minified); + } + + results.push(generatedTemplate); + } return results; }, @@ -133,22 +146,48 @@ export default { * @param {Partial} options */ async generate(input, { index, releases, version, output, worker }) { - const headNodes = getHeadNodes(input); + const remarkRehypeProcessor = getRemarkRehypeWithShiki(); + + const baseDir = import.meta.dirname; + + const apiTemplate = await readFile(join(baseDir, 'template.html'), 'utf-8'); + + const headNodes = input + .filter(node => node.heading.depth === 1) + .sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name)); + + const indexOfFiles = index + ? index.map(({ api, section }) => ({ api, heading: getHeading(section) })) + : headNodes; + + const parsedSideNav = remarkRehypeProcessor.processSync( + tableOfContents(indexOfFiles, { + maxDepth: 1, + parser: tableOfContents.parseNavigationNode, + }) + ); const generatedValues = await worker.map(headNodes, input, { index, releases, version, output, + apiTemplate, + parsedSideNav: String(parsedSideNav), }); if (output) { - await mkdir(join(output, 'assets'), { recursive: true }); + // Define the source folder for API docs assets + const srcAssets = join(baseDir, 'assets'); - await safeCopy( - join(import.meta.dirname, 'assets'), - join(output, 'assets') - ); + // Define the output folder for API docs assets + const assetsFolder = join(output, 'assets'); + + // Creates the assets folder if it does not exist + await mkdir(assetsFolder, { recursive: true }); + + // Copy all files from assets folder to output, skipping unchanged files + await safeCopy(srcAssets, assetsFolder); } return generatedValues; diff --git a/src/generators/legacy-html/utils/template.mjs b/src/generators/legacy-html/utils/template.mjs deleted file mode 100644 index 2abd278f..00000000 --- a/src/generators/legacy-html/utils/template.mjs +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -import { readFile } from 'node:fs/promises'; -import { join } from 'node:path'; - -import dropdowns from './buildDropdowns.mjs'; -import tableOfContents from './tableOfContents.mjs'; - -// Cached template (loaded once per worker) -let cachedTemplate; - -/** - * Gets the template for the legacy HTML pages (cached per process/worker) - * @returns {Promise} - */ -export const getTemplate = async () => { - if (!cachedTemplate) { - cachedTemplate = await readFile( - join(import.meta.dirname, '..', 'template.html'), - 'utf-8' - ); - } - - return cachedTemplate; -}; - -/** - * Creates a template replacer function - * @param {string} template - * @param {Array} releases - * @returns {(values: TemplateValues) => string} - */ -export const createTemplateReplacer = (template, releases) => values => { - const { api, added, section, version, toc, nav, content } = values; - - return template - .replace('__ID__', api) - .replace(/__FILENAME__/g, api) - .replace('__SECTION__', section) - .replace(/__VERSION__/g, version) - .replace(/__TOC__/g, tableOfContents.wrapToC(toc)) - .replace(/__GTOC__/g, nav) - .replace('__CONTENT__', content) - .replace(/__TOC_PICKER__/g, dropdowns.buildToC(toc)) - .replace(/__GTOC_PICKER__/g, dropdowns.buildNavigation(nav)) - .replace('__ALTDOCS__', dropdowns.buildVersions(api, added, releases)) - .replace('__EDIT_ON_GITHUB__', dropdowns.buildGitHub(api)); -}; diff --git a/src/generators/legacy-json/index.mjs b/src/generators/legacy-json/index.mjs index 9457e8a2..9d468760 100644 --- a/src/generators/legacy-json/index.mjs +++ b/src/generators/legacy-json/index.mjs @@ -4,7 +4,7 @@ import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { createSectionBuilder } from './utils/buildSection.mjs'; -import { getHeadNodes, groupNodesByModule } from '../../utils/generators.mjs'; +import { groupNodesByModule } from '../../utils/generators.mjs'; /** * This generator is responsible for generating the legacy JSON files for the @@ -30,17 +30,15 @@ export default { /** * Process a chunk of items in a worker thread. - * Called by chunk-worker.mjs for parallel processing. - * - * @param {Input} fullInput - Full input to rebuild context - * @param {number[]} itemIndices - Indices of head nodes to process + * @param {Input} fullInput + * @param {number[]} itemIndices * @param {Partial} options - * @returns {Promise} */ async processChunk(fullInput, itemIndices, { output }) { const buildSection = createSectionBuilder(); const groupedModules = groupNodesByModule(fullInput); - const headNodes = getHeadNodes(fullInput); + + const headNodes = fullInput.filter(node => node.heading.depth === 1); const results = []; @@ -69,7 +67,7 @@ export default { * @param {Partial} options */ async generate(input, { output, worker }) { - const headNodes = getHeadNodes(input); + const headNodes = input.filter(node => node.heading.depth === 1); return worker.map(headNodes, input, { output }); }, diff --git a/src/generators/web/index.mjs b/src/generators/web/index.mjs index e9f62f1a..71f2f015 100644 --- a/src/generators/web/index.mjs +++ b/src/generators/web/index.mjs @@ -13,13 +13,7 @@ import { processJSXEntries } from './utils/processing.mjs'; * - Client-side JavaScript with code splitting * - Bundled CSS styles * - * NOTE: This generator does NOT implement processChunk because: - * 1. Server-side bundling requires all entries together for code splitting - * 2. Client-side bundling requires all entries together for shared chunks - * 3. The parallelization benefit is in the upstream jsx-ast generator - * - * @typedef {import('../jsx-ast/utils/buildContent.mjs').JSXContent[]} Input - * @type {GeneratorMetadata>} + * @type {GeneratorMetadata} */ export default { name: 'web', @@ -29,11 +23,15 @@ export default { /** * Main generation function that processes JSX AST entries into web bundles. - * @param {Input} entries - * @param {Partial} options + * + * @param {import('../jsx-ast/utils/buildContent.mjs').JSXContent[]} entries - JSX AST entries to process. + * @param {Partial} options - Generator options. + * @param {string} [options.output] - Output directory for generated files. + * @param {string} options.version - Documentation version string. + * @returns {Promise>} Generated HTML and CSS. */ async generate(entries, { output, version }) { - // Load template from file + // Load the HTML template with placeholders const template = await readFile( new URL('template.html', import.meta.url), 'utf-8' @@ -42,11 +40,10 @@ export default { // Create AST builders for server and client programs const astBuilders = createASTBuilder(); - // Create require function - must be created here, not at module level, - // to ensure correct resolution context in worker threads + // Create require function for resolving external packages in server code const requireFn = createRequire(import.meta.url); - // Process all entries together (required for code splitting optimization) + // Process all entries: convert JSX to HTML/CSS/JS const { results, css, chunks } = await processJSXEntries( entries, template, @@ -57,17 +54,21 @@ export default { // Write files to disk if output directory is specified if (output) { - await Promise.all([ - ...results.map(({ html, api }) => - writeFile(join(output, `${api}.html`), html, 'utf-8') - ), - ...chunks.map(chunk => - writeFile(join(output, chunk.fileName), chunk.code, 'utf-8') - ), - writeFile(join(output, 'styles.css'), css, 'utf-8'), - ]); + // Write HTML files + for (const { html, api } of results) { + await writeFile(join(output, `${api}.html`), html, 'utf-8'); + } + + // Write code-split JavaScript chunks + for (const chunk of chunks) { + await writeFile(join(output, chunk.fileName), chunk.code, 'utf-8'); + } + + // Write CSS bundle + await writeFile(join(output, 'styles.css'), css, 'utf-8'); } + // Return HTML and CSS for each entry return results.map(({ html }) => ({ html, css })); }, }; diff --git a/src/threading/generator-worker.mjs b/src/threading/generator-worker.mjs index bd2d4e20..9ac25c93 100644 --- a/src/threading/generator-worker.mjs +++ b/src/threading/generator-worker.mjs @@ -7,8 +7,8 @@ import { allGenerators } from '../generators/index.mjs'; const { generatorName, input, options } = workerData; const generator = allGenerators[generatorName]; -// Create a ParallelWorker for the generator to use (for item-level parallelization) -const chunkPool = new WorkerPool('./chunk-worker.mjs', options.threads ?? 1); +// Create a ParallelWorker for the generator to use for item-level parallelization +const chunkPool = new WorkerPool('./chunk-worker.mjs', options.threads); const worker = createParallelWorker(generatorName, chunkPool, options); // Execute the generator and send the result or error back to the parent thread diff --git a/src/threading/index.mjs b/src/threading/index.mjs index b30945cb..a6c19fd2 100644 --- a/src/threading/index.mjs +++ b/src/threading/index.mjs @@ -1,14 +1,9 @@ import { Worker } from 'node:worker_threads'; /** - * WorkerPool class to manage a pool of worker threads. - * Can be configured with different worker scripts for different use cases. + * WorkerPool class to manage a pool of worker threads */ export default class WorkerPool { - /** @private {URL} - Path to the worker script */ - workerScript; - /** @private {number} - Maximum concurrent threads */ - maxThreads; /** @private {SharedArrayBuffer} - Shared memory for active thread count */ sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT); /** @private {Int32Array} - A typed array to access shared memory */ @@ -18,15 +13,15 @@ export default class WorkerPool { /** * @param {string | URL} workerScript - Path to the worker script (relative to this file or absolute URL) - * @param {number} maxThreads - Maximum number of concurrent worker threads + * @param {number} threads - Maximum number of concurrent worker threads */ - constructor(workerScript = './generator-worker.mjs', maxThreads = 1) { + constructor(workerScript = './generator-worker.mjs', threads = 1) { this.workerScript = workerScript instanceof URL ? workerScript : new URL(workerScript, import.meta.url); - this.maxThreads = Math.max(1, maxThreads); + this.threads = threads; } /** @@ -53,10 +48,10 @@ export default class WorkerPool { run(workerData) { return new Promise((resolve, reject) => { /** - * Executes the worker thread by creating a new Worker instance and - * handling the message and error events. + * Runs the worker thread and handles the result or error. + * @private */ - const execute = () => { + const run = () => { this.changeActiveThreadCount(1); const worker = new Worker(this.workerScript, { workerData }); @@ -79,10 +74,10 @@ export default class WorkerPool { }); }; - if (this.getActiveThreadCount() >= this.maxThreads) { - this.queue.push(execute); + if (this.getActiveThreadCount() >= this.threads) { + this.queue.push(run); } else { - execute(); + run(); } }); } @@ -102,10 +97,7 @@ export default class WorkerPool { * @private */ processQueue() { - while ( - this.queue.length > 0 && - this.getActiveThreadCount() < this.maxThreads - ) { + if (this.queue.length > 0 && this.getActiveThreadCount() < this.threads) { const next = this.queue.shift(); if (next) { diff --git a/src/threading/parallel.mjs b/src/threading/parallel.mjs index 9be48c9f..6adbf6b9 100644 --- a/src/threading/parallel.mjs +++ b/src/threading/parallel.mjs @@ -1,46 +1,6 @@ 'use strict'; -/** - * Calculates optimal chunk distribution for parallel processing. - * Balances work evenly across workers while respecting max chunk size. - * - * @param {number} itemCount - Total number of items - * @param {number} threads - Number of available worker threads - * @param {number} maxChunkSize - Maximum items per chunk - * @returns {{ chunkSize: number, numChunks: number }} - */ -function calculateChunkStrategy(itemCount, threads, maxChunkSize) { - // Determine how many chunks we want (ideally one per thread, but not more than items) - const targetChunks = Math.min(threads, itemCount); - - // Calculate base chunk size to distribute work evenly - const baseChunkSize = Math.ceil(itemCount / targetChunks); - - // Respect the max chunk size limit - const chunkSize = Math.min(baseChunkSize, maxChunkSize); - - // Calculate actual number of chunks needed - const numChunks = Math.ceil(itemCount / chunkSize); - - return { chunkSize, numChunks }; -} - -/** - * Splits indices into chunks of specified size. - * @param {number} count - Number of items - * @param {number} chunkSize - Items per chunk - * @returns {number[][]} Array of index arrays - */ -function createIndexChunks(count, chunkSize) { - const chunks = []; - - for (let i = 0; i < count; i += chunkSize) { - const end = Math.min(i + chunkSize, count); - chunks.push(Array.from({ length: end - i }, (_, j) => i + j)); - } - - return chunks; -} +import { allGenerators } from '../generators/index.mjs'; /** * Creates a ParallelWorker that uses real Node.js Worker threads @@ -52,29 +12,40 @@ function createIndexChunks(count, chunkSize) { * @returns {ParallelWorker} */ export default function createParallelWorker(generatorName, pool, options) { - const { threads = 1, chunkSize: maxChunkSize = 20 } = options; + const { threads, chunkSize } = options; - // Cache for lazy-loaded generator reference - let cachedGenerator; + const generator = allGenerators[generatorName]; /** - * Gets the generator (lazy-loaded and cached) + * Splits items into chunks of specified size. + * @param {number} count - Number of items + * @param {number} size - Items per chunk + * @returns {number[][]} Array of index arrays */ - const getGenerator = async () => { - if (!cachedGenerator) { - const { allGenerators } = await import('../generators/index.mjs'); + const createIndexChunks = (count, size) => { + const chunks = []; - cachedGenerator = allGenerators[generatorName]; + for (let i = 0; i < count; i += size) { + const end = Math.min(i + size, count); + + const chunk = []; + + for (let j = i; j < end; j++) { + chunk.push(j); + } + + chunks.push(chunk); } - return cachedGenerator; + + return chunks; }; /** * Strips non-serializable properties from options for worker transfer - * @param {object} opts - Options to serialize + * @param {object} extra - Extra options to merge */ - const serializeOptions = opts => { - const serialized = { ...options, ...opts }; + const serializeOptions = extra => { + const serialized = { ...options, ...extra }; delete serialized.worker; @@ -89,45 +60,37 @@ export default function createParallelWorker(generatorName, pool, options) { * @template T, R * @param {T[]} items - Items to process (must be serializable) * @param {T[]} fullInput - Full input data for context rebuilding in workers - * @param {object} opts - Additional options to pass to workers + * @param {object} extra - Generator-specific context (e.g. apiTemplate, parsedSideNav) * @returns {Promise} - Results in same order as input items */ - async map(items, fullInput, opts = {}) { + async map(items, fullInput, extra) { const itemCount = items.length; if (itemCount === 0) { return []; } - const generator = await getGenerator(); - if (!generator.processChunk) { throw new Error( `Generator "${generatorName}" does not support chunk processing` ); } - // For single thread, single item, or very small workloads - run in main thread - // Worker overhead isn't worth it for small tasks - const shouldUseMainThread = threads <= 1 || itemCount <= 2; + // For single thread or small workloads - run in main thread + if (threads <= 1 || itemCount <= 2) { + const indices = []; - if (shouldUseMainThread) { - const indices = Array.from({ length: itemCount }, (_, i) => i); + for (let i = 0; i < itemCount; i++) { + indices.push(i); + } return generator.processChunk(fullInput, indices, { ...options, - ...opts, + ...extra, }); } - // Calculate optimal chunk distribution - const { chunkSize } = calculateChunkStrategy( - itemCount, - threads, - maxChunkSize - ); - - // Create index chunks for parallel processing + // Divide items into chunks based on chunkSize const indexChunks = createIndexChunks(itemCount, chunkSize); // Process chunks in parallel using worker threads @@ -136,11 +99,11 @@ export default function createParallelWorker(generatorName, pool, options) { generatorName, fullInput, itemIndices: indices, - options: serializeOptions(opts), + options: serializeOptions(extra), })) ); - // Flatten results (each worker returns array of results for its chunk) + // Flatten results return chunkResults.flat(); }, @@ -149,11 +112,11 @@ export default function createParallelWorker(generatorName, pool, options) { * @template T * @param {T[]} items - Items to process * @param {T[]} fullInput - Full input data for context rebuilding - * @param {object} opts - Additional options + * @param {object} extra - Generator-specific context * @returns {Promise} */ - async forEach(items, fullInput, opts = {}) { - await this.map(items, fullInput, opts); + async forEach(items, fullInput, extra) { + await this.map(items, fullInput, extra); }, }; } diff --git a/src/utils/generators.mjs b/src/utils/generators.mjs index 82957d80..7dbf2590 100644 --- a/src/utils/generators.mjs +++ b/src/utils/generators.mjs @@ -3,60 +3,6 @@ import { coerce, compare, major } from 'semver'; import { DOC_API_BASE_URL_VERSION } from '../constants.mjs'; -import { OVERRIDDEN_POSITIONS } from '../generators/jsx-ast/constants.mjs'; - -/** - * Gets head nodes (depth === 1) sorted alphabetically by name. - * Used by legacy-html and legacy-json generators. - * - * @param {Array} entries - * @returns {Array} - */ -export const getHeadNodes = entries => - entries - .filter(node => node.heading.depth === 1) - .sort((a, b) => a.heading.data.name.localeCompare(b.heading.data.name)); - -/** - * Gets head nodes (depth === 1) sorted with overridden positions first, - * then alphabetically. Used by jsx-ast and web generators. - * - * @param {Array} entries - * @returns {Array} - */ -export const getSortedHeadNodes = entries => - entries - .filter(node => node.heading.depth === 1) - .sort((a, b) => { - const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); - const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); - - if (ai !== -1 && bi !== -1) { - return ai - bi; - } - - if (ai !== -1) { - return -1; - } - - if (bi !== -1) { - return 1; - } - - return a.heading.data.name.localeCompare(b.heading.data.name); - }); - -/** - * Builds doc pages array for sidebar navigation. - * - * @param {Array} headNodes - * @param {Array<{section: string, api: string}>} [index] - * @returns {Array<[string, string]>} - */ -export const buildDocPages = (headNodes, index) => - index - ? index.map(({ section, api }) => [section, `${api}.html`]) - : headNodes.map(node => [node.heading.data.name, `${node.api}.html`]); /** * Groups all the API metadata nodes by module (`api` property) so that we can process each different file From f34eb76f182ddbc96994207af6d4d13d91076d1a Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 01:51:20 +0100 Subject: [PATCH 3/9] chore: more optimizations --- bin/commands/generate.mjs | 4 +-- src/generators.mjs | 43 ++++++++++++++++-------------- src/generators/ast-js/index.mjs | 15 ++++++----- src/parsers/javascript.mjs | 5 +--- src/threading/generator-worker.mjs | 18 ------------- 5 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 src/threading/generator-worker.mjs diff --git a/bin/commands/generate.mjs b/bin/commands/generate.mjs index ff2afe23..5b9dbfd6 100644 --- a/bin/commands/generate.mjs +++ b/bin/commands/generate.mjs @@ -18,7 +18,7 @@ const availableGenerators = Object.keys(publicGenerators); // Half of available logical CPUs guarantees in general all physical CPUs are being used // which in most scenarios is the best way to maximize performance -const optimalThreads = Math.floor(cpus().length / 2) - 1; +const optimalThreads = Math.floor(cpus().length / 2) + 1; /** * @typedef {Object} Options @@ -79,7 +79,7 @@ export default { prompt: { type: 'text', message: 'Items per worker thread', - initialValue: '20', + initialValue: '10', }, }, version: { diff --git a/src/generators.mjs b/src/generators.mjs index b0ccb538..3d985b8f 100644 --- a/src/generators.mjs +++ b/src/generators.mjs @@ -40,40 +40,43 @@ const createGenerator = input => { const runGenerators = async options => { const { generators, threads } = options; - // WorkerPool for running full generators in worker threads - const generatorPool = new WorkerPool('./generator-worker.mjs', threads); - // WorkerPool for chunk-level parallelization within generators const chunkPool = new WorkerPool('./chunk-worker.mjs', threads); - // Note that this method is blocking, and will only execute one generator per-time - // but it ensures all dependencies are resolved, and that multiple bottom-level generators - // can reuse the already parsed content from the top-level/dependency generators + // Schedule all generators, allowing independent ones to run in parallel. + // Each generator awaits its own dependency internally, so generators + // with the same dependency (e.g. legacy-html and legacy-json both depend + // on metadata) will run concurrently once metadata resolves. for (const generatorName of generators) { + // Skip if already scheduled + if (generatorName in cachedGenerators) { + continue; + } + const { dependsOn, generate } = allGenerators[generatorName]; - // If the generator dependency has not yet been resolved, we resolve - // the dependency first before running the current generator - if (dependsOn && dependsOn in cachedGenerators === false) { + // Ensure dependency is scheduled (but don't await its result yet) + if (dependsOn && !(dependsOn in cachedGenerators)) { await runGenerators({ ...options, generators: [dependsOn] }); } - // Ensures that the dependency output gets resolved before we run the current - // generator with its dependency output as the input - const input = await cachedGenerators[dependsOn]; - - // Create a ParallelWorker for this generator to use for item-level parallelization + // Create a ParallelWorker for this generator const worker = createParallelWorker(generatorName, chunkPool, options); - // Adds the current generator execution Promise to the cache - cachedGenerators[generatorName] = - threads < 2 - ? generate(input, { ...options, worker }) - : generatorPool.run({ generatorName, input, options }); + /** + * Schedule the generator - it awaits its dependency internally + * his allows multiple generators with the same dependency to run in parallel + */ + const scheduledGenerator = async () => { + const input = await cachedGenerators[dependsOn]; + + return generate(input, { ...options, worker }); + }; + + cachedGenerators[generatorName] = scheduledGenerator(); } // Returns the value of the last generator of the current pipeline - // Note that dependencies will be awaited (as shown on line 48) return cachedGenerators[generators[generators.length - 1]]; }; diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index 973f1b26..47603e52 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -30,14 +30,16 @@ export default { */ async processChunk(_, itemIndices, { input }) { const { loadFiles } = createJsLoader(); - const sourceFiles = loadFiles(input ?? []); - const { parseJsSource } = createJsParser(); const results = []; for (const idx of itemIndices) { - results.push(await parseJsSource(sourceFiles[idx])); + const [file] = loadFiles(input[idx]); + + const parsedFile = await parseJsSource(file); + + results.push(parsedFile); } return results; @@ -48,12 +50,11 @@ export default { * @param {Partial} options */ async generate(_, { input, worker }) { - const { loadFiles } = createJsLoader(); - // Load all of the Javascript sources into memory - const sourceFiles = loadFiles(input ?? []); + const sourceFiles = + input?.filter(filename => filename.endsWith('.js')) ?? []; // Parse the Javascript sources into ASTs in parallel using worker threads - return worker.map(sourceFiles, _, { input }); + return worker.map(sourceFiles, _, { input: sourceFiles }); }, }; diff --git a/src/parsers/javascript.mjs b/src/parsers/javascript.mjs index e68e9d9f..d5a7714d 100644 --- a/src/parsers/javascript.mjs +++ b/src/parsers/javascript.mjs @@ -29,10 +29,7 @@ const createParser = () => { locations: true, }); - return { - ...res, - path: resolvedSourceFile.path, - }; + return { ...res, path: resolvedSourceFile.path }; }; /** diff --git a/src/threading/generator-worker.mjs b/src/threading/generator-worker.mjs deleted file mode 100644 index 9ac25c93..00000000 --- a/src/threading/generator-worker.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { parentPort, workerData } from 'node:worker_threads'; - -import WorkerPool from './index.mjs'; -import createParallelWorker from './parallel.mjs'; -import { allGenerators } from '../generators/index.mjs'; - -const { generatorName, input, options } = workerData; -const generator = allGenerators[generatorName]; - -// Create a ParallelWorker for the generator to use for item-level parallelization -const chunkPool = new WorkerPool('./chunk-worker.mjs', options.threads); -const worker = createParallelWorker(generatorName, chunkPool, options); - -// Execute the generator and send the result or error back to the parent thread -generator - .generate(input, { ...options, worker }) - .then(result => parentPort.postMessage(result)) - .catch(error => parentPort.postMessage({ error: error.message })); From a05fb3835dd8692e72f9ef87a1a6af0e50fe13dd Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 02:05:19 +0100 Subject: [PATCH 4/9] chore: more self review --- src/generators/ast-js/index.mjs | 12 +++-- src/generators/jsx-ast/index.mjs | 78 ++++++++++++++++++-------------- src/threading/chunk-worker.mjs | 1 + 3 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/generators/ast-js/index.mjs b/src/generators/ast-js/index.mjs index 47603e52..7967a5b3 100644 --- a/src/generators/ast-js/index.mjs +++ b/src/generators/ast-js/index.mjs @@ -1,3 +1,7 @@ +import { extname } from 'node:path'; + +import { globSync } from 'glob'; + import createJsLoader from '../../loaders/javascript.mjs'; import createJsParser from '../../parsers/javascript.mjs'; @@ -49,10 +53,10 @@ export default { * @param {Input} _ * @param {Partial} options */ - async generate(_, { input, worker }) { - // Load all of the Javascript sources into memory - const sourceFiles = - input?.filter(filename => filename.endsWith('.js')) ?? []; + async generate(_, { input = [], worker }) { + const sourceFiles = globSync(input).filter( + filePath => extname(filePath) === '.js' + ); // Parse the Javascript sources into ASTs in parallel using worker threads return worker.map(sourceFiles, _, { input: sourceFiles }); diff --git a/src/generators/jsx-ast/index.mjs b/src/generators/jsx-ast/index.mjs index 3fc357d9..629646ea 100644 --- a/src/generators/jsx-ast/index.mjs +++ b/src/generators/jsx-ast/index.mjs @@ -4,44 +4,52 @@ import buildContent from './utils/buildContent.mjs'; import { groupNodesByModule } from '../../utils/generators.mjs'; import { getRemarkRecma } from '../../utils/remark.mjs'; -/** - * Generator for converting MDAST to JSX AST. - * - * @typedef {Array} Input - * @type {GeneratorMetadata} - */ - /** * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. * @param {Array} entries */ const getSortedHeadNodes = entries => { - return entries - .filter(node => node.heading.depth === 1) - .sort((a, b) => { - const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); - const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); - - if (ai !== -1 && bi !== -1) { - return ai - bi; - } - - if (ai !== -1) { - return -1; - } - - if (bi !== -1) { - return 1; - } - - return a.heading.data.name.localeCompare(b.heading.data.name); - }); + /** + * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. + * @param {ApiDocMetadataEntry} a + * @param {ApiDocMetadataEntry} b + * @returns {number} + */ + const headingSortFn = (a, b) => { + const ai = OVERRIDDEN_POSITIONS.indexOf(a.api); + const bi = OVERRIDDEN_POSITIONS.indexOf(b.api); + + if (ai !== -1 && bi !== -1) { + return ai - bi; + } + + if (ai !== -1) { + return -1; + } + + if (bi !== -1) { + return 1; + } + + return a.heading.data.name.localeCompare(b.heading.data.name); + }; + + return entries.filter(node => node.heading.depth === 1).sort(headingSortFn); }; +/** + * Generator for converting MDAST to JSX AST. + * + * @typedef {Array} Input + * @type {GeneratorMetadata} + */ export default { name: 'jsx-ast', + version: '1.0.0', + description: 'Generates JSX AST from the input MDAST', + dependsOn: 'metadata', /** @@ -71,14 +79,14 @@ export default { docPages ); - results.push( - await buildContent( - groupedModules.get(entry.api), - entry, - sideBarProps, - remarkRecma - ) + const content = await buildContent( + groupedModules.get(entry.api), + entry, + sideBarProps, + remarkRecma ); + + results.push(content); } return results; @@ -92,7 +100,7 @@ export default { * @returns {Promise>} Array of generated content */ async generate(entries, { index, releases, version, worker }) { - const headNodes = getSortedHeadNodes(entries); + const headNodes = entries.filter(node => node.heading.depth === 1); return worker.map(headNodes, entries, { index, releases, version }); }, diff --git a/src/threading/chunk-worker.mjs b/src/threading/chunk-worker.mjs index 9f21efe1..31112109 100644 --- a/src/threading/chunk-worker.mjs +++ b/src/threading/chunk-worker.mjs @@ -3,6 +3,7 @@ import { parentPort, workerData } from 'node:worker_threads'; import { allGenerators } from '../generators/index.mjs'; const { generatorName, fullInput, itemIndices, options } = workerData; + const generator = allGenerators[generatorName]; // Generators must implement processChunk for item-level parallelization From cdbf816e6808175bbc6dc03d9ebb75f9044320af Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 02:17:41 +0100 Subject: [PATCH 5/9] chore: added oxc instead of acorn for faster parsing of javascript --- .nvmrc | 2 +- npm-shrinkwrap.json | 935 +++++++++++++++++++++++++------------ package.json | 30 +- src/parsers/javascript.mjs | 16 +- 4 files changed, 656 insertions(+), 327 deletions(-) diff --git a/.nvmrc b/.nvmrc index 2bd5a0a9..a45fd52c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +24 diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 207228f9..e56d0141 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,16 +9,15 @@ "@actions/core": "^1.11.1", "@clack/prompts": "^0.11.0", "@heroicons/react": "^2.2.0", - "@minify-html/node": "^0.16.4", + "@minify-html/node": "^0.18.1", "@node-core/rehype-shiki": "1.3.0", "@node-core/ui-components": "1.4.0", "@orama/orama": "^3.1.16", "@orama/react-components": "^0.8.1", "@rollup/plugin-virtual": "^3.0.2", - "acorn": "^8.15.0", "commander": "^14.0.2", "dedent": "^1.7.0", - "eslint-plugin-react-x": "^2.3.1", + "eslint-plugin-react-x": "^2.3.12", "estree-util-to-js": "^2.0.0", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", @@ -26,9 +25,10 @@ "globals": "^16.5.0", "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", - "lightningcss": "^1.30.1", + "lightningcss": "^1.30.2", "mdast-util-slice-markdown": "^2.0.1", - "preact": "^10.27.2", + "oxc-parser": "^0.101.0", + "preact": "^10.28.0", "preact-render-to-string": "^6.6.3", "reading-time": "^1.5.0", "recma-jsx": "^1.0.1", @@ -39,9 +39,9 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", - "rolldown": "^1.0.0-beta.47", - "semver": "^7.7.2", - "shiki": "^3.17.0", + "rolldown": "^1.0.0-beta.53", + "semver": "^7.7.3", + "shiki": "^3.19.0", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-find-after": "^5.0.0", @@ -50,24 +50,24 @@ "unist-util-select": "^5.1.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", - "yaml": "^2.8.1" + "yaml": "^2.8.2" }, "bin": { "doc-kit": "bin/cli.mjs" }, "devDependencies": { - "@eslint/js": "^9.36.0", + "@eslint/js": "^9.39.1", "@reporters/github": "^1.11.0", "@types/mdast": "^4.0.4", - "@types/node": "^22.18.6", + "@types/node": "^24.10.1", "c8": "^10.1.3", - "eslint": "^9.36.0", + "eslint": "^9.39.1", "eslint-import-resolver-node": "^0.3.9", "eslint-plugin-import-x": "^4.16.1", - "eslint-plugin-jsdoc": "^61.1.12", + "eslint-plugin-jsdoc": "^61.4.1", "husky": "^9.1.7", - "lint-staged": "^16.2.6", - "prettier": "3.6.2" + "lint-staged": "^16.2.7", + "prettier": "3.7.4" } }, "node_modules/@actions/core": { @@ -149,9 +149,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "license": "MIT", "optional": true, "dependencies": { @@ -160,9 +160,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "license": "MIT", "optional": true, "dependencies": { @@ -246,89 +246,105 @@ } }, "node_modules/@eslint-react/ast": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.1.tgz", - "integrity": "sha512-jB/P72HVbZcC7DtUvjna8tjPSageAS6L9x5muMsBRQxEXkfv2J6CPX47sSpaPu1mMJn1Zzpn9m5z4aTPbfV6Ug==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-2.3.12.tgz", + "integrity": "sha512-2wlRvqS4dxleGlL4Gp3Bh5PNb47wnAEa99CsGppzWCFXSPvm3d/bM5nJPvOwQOF53+PGa6xq1ZqwGh70zL7+zw==", "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.1", - "@typescript-eslint/types": "^8.46.2", - "@typescript-eslint/typescript-estree": "^8.46.2", - "@typescript-eslint/utils": "^8.46.2", - "string-ts": "^2.2.1" + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/typescript-estree": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", + "string-ts": "^2.3.1" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/core": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.1.tgz", - "integrity": "sha512-R0gXjIqHqqYSeHxNMbXblnlwzmZ2gD32aVPmrJB+SrLP0rItzo/WgVSvstjOK5+N5KExdM87hopFcqnlZS3ONg==", - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.1", - "@eslint-react/eff": "2.3.1", - "@eslint-react/shared": "2.3.1", - "@eslint-react/var": "2.3.1", - "@typescript-eslint/scope-manager": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "@typescript-eslint/utils": "^8.46.2", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-2.3.12.tgz", + "integrity": "sha512-Q3w6f0WfVyIJriJa+tYHS4rmVQ3nwnubCH7o/VYlBCR3qczpvpvkCi2XK4clU/7vpVwHbbaXGICAbJu7tNZqoQ==", + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "birecord": "^0.1.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/eff": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.1.tgz", - "integrity": "sha512-k58lxHmhzatRZXVFzWdLZwfRwPgI5Thhf7BNVJ9F+NI2G1fFypclSVFRPqjGmI5jQ8bqB+5UVt9Rh49rZGZPzw==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-2.3.12.tgz", + "integrity": "sha512-QjFENG1VGVrD67YFc0yiLm9zef2kTeXZGux4hlMjGLnxTHnn0tPx4T/xGzh5C1WRmolcNeIzjVWMqSngFrTphQ==", "license": "MIT", "engines": { "node": ">=20.19.0" } }, "node_modules/@eslint-react/shared": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.1.tgz", - "integrity": "sha512-UiTbPi1i7UPdsIT2Z7mKZ3zzrgAm1GLeexkKe4QwvZJ1LLeEJmgMwHUw852+VzlDeV8stcQmZ9zWqFX2L0CmGg==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-2.3.12.tgz", + "integrity": "sha512-mIgxjEwKOknJabbQs/bxvkEhKitJnET0QDc0a89pFx36DBLJIEvdcGMCDEXFgtgjDV/WwMxIava/+coE6T3Dyw==", "license": "MIT", "dependencies": { - "@eslint-react/eff": "2.3.1", - "@typescript-eslint/utils": "^8.46.2", + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/utils": "^8.48.1", "ts-pattern": "^5.9.0", - "zod": "^4.1.12" + "zod": "^4.1.13" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint-react/shared/node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/@eslint-react/var": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.1.tgz", - "integrity": "sha512-1rC9dbuKKMq77pPoODGT91VTA3ReivIAfdFJePEjscPSRAUhCy7QPA/yK8MPe9nTsG89IDV+hilCGKiLZW8vNQ==", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-2.3.12.tgz", + "integrity": "sha512-jjgeRcop74NTzWzCF8rBN1H5avdSDLEOALJjwmYWOdxoSUNGO7OIeM/pZvHZ7G36kHDuD619P2JauCVM2/c+7A==", "license": "MIT", "dependencies": { - "@eslint-react/ast": "2.3.1", - "@eslint-react/eff": "2.3.1", - "@typescript-eslint/scope-manager": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "@typescript-eslint/utils": "^8.46.2", + "@eslint-react/ast": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "ts-pattern": "^5.9.0" }, "engines": { "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@eslint/config-array": { @@ -674,9 +690,9 @@ } }, "node_modules/@minify-html/node": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node/-/node-0.16.4.tgz", - "integrity": "sha512-ykQgl6xcQQDE1shUExeObPSNwAf00DVUt/GrxdjiqFNCVGu7DXK9nuH29sNTyKKYnJJLZAi6OEib2bDfxW3udg==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node/-/node-0.18.1.tgz", + "integrity": "sha512-pNOv6z9zUDrjTU1+VcPOnDzGeaaqd6m9kbyHU+sg9O35450uzfke8ahx8gKlFdp/kQFrRn2rFIGAoANUZ62nSw==", "license": "MIT", "bin": { "minify-html": "cli.js" @@ -685,17 +701,17 @@ "node": ">= 8.6.0" }, "optionalDependencies": { - "@minify-html/node-darwin-arm64": "0.16.4", - "@minify-html/node-darwin-x64": "0.16.4", - "@minify-html/node-linux-arm64": "0.16.4", - "@minify-html/node-linux-x64": "0.16.4", - "@minify-html/node-win32-x64": "0.16.4" + "@minify-html/node-darwin-arm64": "0.18.1", + "@minify-html/node-darwin-x64": "0.18.1", + "@minify-html/node-linux-arm64": "0.18.1", + "@minify-html/node-linux-x64": "0.18.1", + "@minify-html/node-win32-x64": "0.18.1" } }, "node_modules/@minify-html/node-darwin-arm64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-arm64/-/node-darwin-arm64-0.16.4.tgz", - "integrity": "sha512-9H8hcywDb8zo2jEJfaIAibgsKjMqE+XF7SyqTtJ5H8lVXHxffOkawH4TQtphf9V/x7zXeb/nByAvHe1orJ/RHA==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-arm64/-/node-darwin-arm64-0.18.1.tgz", + "integrity": "sha512-sc/VJHRcHtEk0IQP2hRA/TWkAQntaYH3RCw8n750NbfzliQqkyrDqNAGiKBOli7UlhFtG/H2aWqGjtOjIM/dZA==", "cpu": [ "arm64" ], @@ -705,9 +721,9 @@ ] }, "node_modules/@minify-html/node-darwin-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-x64/-/node-darwin-x64-0.16.4.tgz", - "integrity": "sha512-P0Krf5nwXbccMrC7ragKAIVOENHFoVRQi+v/8k5pmfjrNlxgXGVILacG0FbUZXsH2Z2XaIo39HxuMf70L6wQlA==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-x64/-/node-darwin-x64-0.18.1.tgz", + "integrity": "sha512-0bff2ng4Vgp3+kdC8zgjd5dMN04zkM4nGuYEVP4dLR7J5NCD/Ka4XD4CKNSDZ9goLYQ8BjtKD6AeDTfJ8KBjrA==", "cpu": [ "x64" ], @@ -717,9 +733,9 @@ ] }, "node_modules/@minify-html/node-linux-arm64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node-linux-arm64/-/node-linux-arm64-0.16.4.tgz", - "integrity": "sha512-GDRExKf7AmyAdBTdhMkMyzFhJu5VeyJTu0OnNH2ekp69JrhQTrrrt9UYqnjen+7qLIaZB/R8urDRAYNk0HZi5w==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node-linux-arm64/-/node-linux-arm64-0.18.1.tgz", + "integrity": "sha512-CVGu5XVmmDrGFuA9/b/695rJBFvfTZFwKJJYA1ZXAsBZrqlKVO4bfXPq9DVNmhQ76ky3LFtVZ/+UsU7xpyK5Qw==", "cpu": [ "arm64" ], @@ -729,9 +745,9 @@ ] }, "node_modules/@minify-html/node-linux-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node-linux-x64/-/node-linux-x64-0.16.4.tgz", - "integrity": "sha512-MS/gF1gxJoeHqEGcb1xoUIRv6gVin4cGJszgHPYSikzkK8Yg0p6rVOZdDAE4AAnp/NW0DYNq7fwYgw3igmppFw==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node-linux-x64/-/node-linux-x64-0.18.1.tgz", + "integrity": "sha512-qmIv4kRyhZTKO3nXDXHlFrhnmwIKTM57ZRomlYRYdZTWiFW3CbJ4Ggg57qIgx7D/t4dgyS7/O8OTetrWvmEg9w==", "cpu": [ "x64" ], @@ -741,9 +757,9 @@ ] }, "node_modules/@minify-html/node-win32-x64": { - "version": "0.16.4", - "resolved": "https://registry.npmjs.org/@minify-html/node-win32-x64/-/node-win32-x64-0.16.4.tgz", - "integrity": "sha512-SCY7hzIqG1RclU0QzU2MlGtPOujPu6dvaPYqDvhAHpkvRXtX0hnyOrrfqf7GcBdDbASxV8LDlBWpY46JO2cjAA==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@minify-html/node-win32-x64/-/node-win32-x64-0.18.1.tgz", + "integrity": "sha512-SNRE/NvcKXvh4UwXTRrujNSWaCj/3E+FFN+FLOBaN4FY+dZG83h6nWxGHJ5+LAoJxVTU7Al1e8toi4JjHpzv2w==", "cpu": [ "x64" ], @@ -921,6 +937,7 @@ "resolved": "https://registry.npmjs.org/@orama/core/-/core-0.0.10.tgz", "integrity": "sha512-rZ4AHeHoFTxOXMhM0An2coO3OfR+FpL0ejXc1PPrNsGB4p6VNlky7FAGeuqOvS5gUYB5ywJsmDzCxeflPtgk4w==", "license": "AGPL-3.0", + "peer": true, "dependencies": { "@orama/cuid2": "2.2.3", "dedent": "1.5.3" @@ -931,6 +948,7 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "license": "MIT", + "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -960,7 +978,6 @@ "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.16.tgz", "integrity": "sha512-scSmQBD8eANlMUOglxHrN1JdSW8tDghsPuS83otqealBiIeMukCQMOf/wc0JJjDXomqwNdEQFLXLGHrU6PGxuA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 20.0.0" } @@ -969,7 +986,8 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/@orama/oramacore-events-parser/-/oramacore-events-parser-0.0.5.tgz", "integrity": "sha512-yAuSwog+HQBAXgZ60TNKEwu04y81/09mpbYBCmz1RCxnr4ObNY2JnPZI7HmALbjAhLJ8t5p+wc2JHRK93ubO4w==", - "license": "AGPL-3.0" + "license": "AGPL-3.0", + "peer": true }, "node_modules/@orama/react-components": { "version": "0.8.1", @@ -1191,17 +1209,268 @@ "resolved": "https://registry.npmjs.org/@oramacloud/client/-/client-2.1.4.tgz", "integrity": "sha512-uNPFs4wq/iOPbggCwTkVNbIr64Vfd7ZS/h+cricXVnzXWocjDTfJ3wLL4lr0qiSu41g8z+eCAGBqJ30RO2O4AA==", "license": "ISC", - "peer": true, "dependencies": { "@orama/cuid2": "^2.2.3", "@orama/orama": "^3.0.0", "lodash": "^4.17.21" } }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.101.0.tgz", + "integrity": "sha512-dh1thtigwmLtJTF3BbgC+5lucGYdBAsnLE02scOSOZpiaEcsl5acMwwPBlhjHrHGWS/xBRz53Z178ilO0q+sWg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.101.0.tgz", + "integrity": "sha512-DCy7zJDxHo7iT9Y8eDSvBt4HN5pOSb+8y+eJv5Kq8plMQF5oatcu5ZvHvP6Hij3jRNBgpwTC4vWLdND7l/zsCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.101.0.tgz", + "integrity": "sha512-M5oPJFQ/B7wXOjL8r/qezIngLdypH8aCsJx2cb94Eo/gGix0AgEr9ojVF1P/3kJu2Oi/prZf5Cgf0XfbRfm9Gw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.101.0.tgz", + "integrity": "sha512-IG9smLrG7jh/VjKR7haW07+cC0cxq9i74iTNmS73cKo43VrfFxce6f+qXPaZj8EDizoFDqn5imWOb8tc2dBxTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.101.0.tgz", + "integrity": "sha512-AW8JrXyf2e9y4KlF5cFFYyD1+eKp1PSUKeg5FUevAn5QBFjr/IO2iZ+bLkK66M4z/oRma62pFjo3ubVEggenVw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.101.0.tgz", + "integrity": "sha512-c7myby84UFxRqGPM0wEhdIqz0Ta4GZHoj0IVUSYNNar4j0Cmll1H/f/43cJGj2EwL4sDVDPRrF526JwJIHOZYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.101.0.tgz", + "integrity": "sha512-LZ7o9sFafyIVOwpHQkEPyF3EfZYzGWXNkzznSSASlHxoyo/Uk3EIqL1B2UG0bWxHsz7nNIhv9ItyfGm+/7QHXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.101.0.tgz", + "integrity": "sha512-3LyKucFn9Yu9IggO4FPkbaghcMvr+fWO3krdcQBm6MDZiRsx8c+xcqmGji8l4evaAA6oHFg3eYNKsFgjQoHnkA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.101.0.tgz", + "integrity": "sha512-TCJhU5WTdvua4IMXz67CUESbxYZT9Adyt9KhKC+7H6hcjCJd111kTMG5AIqegeaZjxs7tDCyDCtymvKtD6BvCg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.101.0.tgz", + "integrity": "sha512-owQQTlvFDn496Rcx+aHvxcaVHeX/iQX2zNYB9mh8XywIyO1QLhOVDxNHrFYnbMoXGNnwXnN4CPtpYXPuMS338g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.101.0.tgz", + "integrity": "sha512-NcGgxNoVM/cjTqbMsq8olHWV0obfCnTYic/d12c49e0p8CV412xOrB1C9dXO8POd1evrrIIXCeMaroliRgl9/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.101.0.tgz", + "integrity": "sha512-pLTLWauhjrNq7dn+l1316Q98k4SCSlLFfhor0evbA+e0pPDrxQvCL0K4Jfn+zLTV086f9SD3/XJ3rHVE91UiJw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.101.0.tgz", + "integrity": "sha512-6jZ5fDUOlHICoTpcz7oHKyy3mF7RfM/hmSMnY1/b99Z+7hFql4yNlyHJ0RS1lS11H3V2qzxXXWXocGlOz3qmWw==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.101.0.tgz", + "integrity": "sha512-BRcLSzo0NZUSB5vJTW9NEnnIHOYLfiOVgXl+a0Hbv7sr/3xl3E4arkx/btNL441uDSEPFtrM1rcclpICDuYhlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.101.0.tgz", + "integrity": "sha512-HF6deX1VgbzVXl+v/7j02uQKXJtUtMhIQQMbmTg1wZVDbSOPgIVdwrOqUhSdaCt7gnbiD4KR3TAI1tJgqY8LxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-project/types": { - "version": "0.96.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", - "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", + "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -2084,9 +2353,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", - "integrity": "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==", "cpu": [ "arm64" ], @@ -2100,9 +2369,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.47.tgz", - "integrity": "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==", "cpu": [ "arm64" ], @@ -2116,9 +2385,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.47.tgz", - "integrity": "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz", + "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==", "cpu": [ "x64" ], @@ -2132,9 +2401,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.47.tgz", - "integrity": "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz", + "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==", "cpu": [ "x64" ], @@ -2148,9 +2417,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.47.tgz", - "integrity": "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz", + "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==", "cpu": [ "arm" ], @@ -2164,9 +2433,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.47.tgz", - "integrity": "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz", + "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==", "cpu": [ "arm64" ], @@ -2180,9 +2449,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.47.tgz", - "integrity": "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz", + "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==", "cpu": [ "arm64" ], @@ -2196,9 +2465,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.47.tgz", - "integrity": "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz", + "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==", "cpu": [ "x64" ], @@ -2212,9 +2481,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.47.tgz", - "integrity": "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz", + "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==", "cpu": [ "x64" ], @@ -2228,9 +2497,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.47.tgz", - "integrity": "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==", "cpu": [ "arm64" ], @@ -2244,37 +2513,37 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.47.tgz", - "integrity": "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz", + "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" + "@napi-rs/wasm-runtime": "^1.1.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.47.tgz", - "integrity": "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz", + "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==", "cpu": [ "arm64" ], @@ -2287,26 +2556,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.47.tgz", - "integrity": "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.47.tgz", - "integrity": "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz", + "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==", "cpu": [ "x64" ], @@ -2320,9 +2573,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", - "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", "license": "MIT" }, "node_modules/@rollup/plugin-virtual": { @@ -2480,18 +2733,18 @@ } }, "node_modules/@shikijs/langs": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.17.0.tgz", - "integrity": "sha512-icmur2n5Ojb+HAiQu6NEcIIJ8oWDFGGEpiqSCe43539Sabpx7Y829WR3QuUW2zjTM4l6V8Sazgb3rrHO2orEAw==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.19.0.tgz", + "integrity": "sha512-dBMFzzg1QiXqCVQ5ONc0z2ebyoi5BKz+MtfByLm0o5/nbUu3Iz8uaTCa5uzGiscQKm7lVShfZHU1+OG3t5hgwg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.17.0" + "@shikijs/types": "3.19.0" } }, "node_modules/@shikijs/langs/node_modules/@shikijs/types": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.17.0.tgz", - "integrity": "sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.19.0.tgz", + "integrity": "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -2499,18 +2752,18 @@ } }, "node_modules/@shikijs/themes": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.17.0.tgz", - "integrity": "sha512-/xEizMHLBmMHwtx4JuOkRf3zwhWD2bmG5BRr0IPjpcWpaq4C3mYEuTk/USAEglN0qPrTwEHwKVpSu/y2jhferA==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.19.0.tgz", + "integrity": "sha512-H36qw+oh91Y0s6OlFfdSuQ0Ld+5CgB/VE6gNPK+Hk4VRbVG/XQgkjnt4KzfnnoO6tZPtKJKHPjwebOCfjd6F8A==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.17.0" + "@shikijs/types": "3.19.0" } }, "node_modules/@shikijs/themes/node_modules/@shikijs/types": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.17.0.tgz", - "integrity": "sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.19.0.tgz", + "integrity": "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -3076,13 +3329,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", - "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/react": { @@ -3108,13 +3361,13 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz", - "integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", + "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.46.3", - "@typescript-eslint/types": "^8.46.3", + "@typescript-eslint/tsconfig-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", "debug": "^4.3.4" }, "engines": { @@ -3129,13 +3382,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz", - "integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", + "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3" + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3146,9 +3399,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz", - "integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", + "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3162,14 +3415,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz", - "integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", + "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3", - "@typescript-eslint/utils": "8.46.3", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1", + "@typescript-eslint/utils": "8.48.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3186,9 +3439,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz", - "integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", + "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3199,20 +3452,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz", - "integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", + "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.46.3", - "@typescript-eslint/tsconfig-utils": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/visitor-keys": "8.46.3", + "@typescript-eslint/project-service": "8.48.1", + "@typescript-eslint/tsconfig-utils": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/visitor-keys": "8.48.1", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -3251,15 +3503,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz", - "integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", + "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.46.3", - "@typescript-eslint/types": "8.46.3", - "@typescript-eslint/typescript-estree": "8.46.3" + "@typescript-eslint/scope-manager": "8.48.1", + "@typescript-eslint/types": "8.48.1", + "@typescript-eslint/typescript-estree": "8.48.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3274,12 +3526,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.46.3", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz", - "integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==", + "version": "8.48.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", + "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.46.3", + "@typescript-eslint/types": "8.48.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3642,7 +3894,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4183,7 +4434,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "4.4.3", @@ -4465,7 +4717,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4629,9 +4880,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "61.1.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.1.12.tgz", - "integrity": "sha512-CGJTnltz7ovwOW33xYhvA4fMuriPZpR5OnJf09SV28iU2IUpJwMd6P7zvUK8Sl56u5YzO+1F9m46wpSs2dufEw==", + "version": "61.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-61.4.1.tgz", + "integrity": "sha512-3c1QW/bV25sJ1MsIvsvW+EtLtN6yZMduw7LVQNVt72y2/5BbV5Pg5b//TE5T48LRUxoEQGaZJejCmcj3wCxBzw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4658,23 +4909,23 @@ } }, "node_modules/eslint-plugin-react-x": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.1.tgz", - "integrity": "sha512-7zfi297NfkoEtqaz2W953gdK4J9nJD5okVhJVxgrcrP+9FVertkGqpbWtMZLpQuWJ216FncY8P6t1U+af8KNOA==", - "license": "MIT", - "dependencies": { - "@eslint-react/ast": "2.3.1", - "@eslint-react/core": "2.3.1", - "@eslint-react/eff": "2.3.1", - "@eslint-react/shared": "2.3.1", - "@eslint-react/var": "2.3.1", - "@typescript-eslint/scope-manager": "^8.46.2", - "@typescript-eslint/type-utils": "^8.46.2", - "@typescript-eslint/types": "^8.46.2", - "@typescript-eslint/utils": "^8.46.2", + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-x/-/eslint-plugin-react-x-2.3.12.tgz", + "integrity": "sha512-G9ThX5LZQun3243JN/UchMbGPra9ZL1D7Wi4dwaIgqh26nRK8W6LBqRTJC+jlrmOanosg+flcxpUyFS/N+Ch7A==", + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "2.3.12", + "@eslint-react/core": "2.3.12", + "@eslint-react/eff": "2.3.12", + "@eslint-react/shared": "2.3.12", + "@eslint-react/var": "2.3.12", + "@typescript-eslint/scope-manager": "^8.48.1", + "@typescript-eslint/type-utils": "^8.48.1", + "@typescript-eslint/types": "^8.48.1", + "@typescript-eslint/utils": "^8.48.1", "compare-versions": "^6.1.1", "is-immutable-type": "^5.0.1", - "string-ts": "^2.2.1", + "string-ts": "^2.3.1", "ts-api-utils": "^2.1.0", "ts-pattern": "^5.9.0" }, @@ -4682,8 +4933,8 @@ "node": ">=20.19.0" }, "peerDependencies": { - "eslint": "^9.38.0", - "typescript": "^5.9.3" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/eslint-scope": { @@ -5991,13 +6242,13 @@ } }, "node_modules/lint-staged": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", - "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.1", + "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", @@ -6311,7 +6562,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7381,6 +7631,38 @@ "node": ">= 0.8.0" } }, + "node_modules/oxc-parser": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.101.0.tgz", + "integrity": "sha512-Njg0KoSisH57AWzKTImV0JpjUBu0riCwbMTnnSH8H/deHpJaVpcbmwsiKkSd7ViX6lxaXiOiBVZH2quWPUFtUg==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.101.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm64": "0.101.0", + "@oxc-parser/binding-darwin-arm64": "0.101.0", + "@oxc-parser/binding-darwin-x64": "0.101.0", + "@oxc-parser/binding-freebsd-x64": "0.101.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.101.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.101.0", + "@oxc-parser/binding-linux-arm64-musl": "0.101.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.101.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.101.0", + "@oxc-parser/binding-linux-x64-gnu": "0.101.0", + "@oxc-parser/binding-linux-x64-musl": "0.101.0", + "@oxc-parser/binding-openharmony-arm64": "0.101.0", + "@oxc-parser/binding-wasm32-wasi": "0.101.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.101.0", + "@oxc-parser/binding-win32-x64-msvc": "0.101.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7581,7 +7863,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7627,11 +7908,10 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.27.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", - "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "version": "10.28.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", + "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -7656,10 +7936,11 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -8141,13 +8422,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.47.tgz", - "integrity": "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==", + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.53.tgz", + "integrity": "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.96.0", - "@rolldown/pluginutils": "1.0.0-beta.47" + "@oxc-project/types": "=0.101.0", + "@rolldown/pluginutils": "1.0.0-beta.53" }, "bin": { "rolldown": "bin/cli.mjs" @@ -8156,20 +8437,19 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-beta.47", - "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", - "@rolldown/binding-darwin-x64": "1.0.0-beta.47", - "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", - "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", - "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", - "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", - "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", - "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", - "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", - "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" + "@rolldown/binding-android-arm64": "1.0.0-beta.53", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", + "@rolldown/binding-darwin-x64": "1.0.0-beta.53", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" } }, "node_modules/run-parallel": { @@ -8199,7 +8479,8 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "7.7.3", @@ -8235,58 +8516,58 @@ } }, "node_modules/shiki": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.17.0.tgz", - "integrity": "sha512-lUZfWsyW7czITYTdo/Tb6ZM4VfyXlzmKYBQBjTz+pBzPPkP08RgIt00Ls1Z50Cl3SfwJsue6WbJeF3UgqLVI9Q==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.19.0.tgz", + "integrity": "sha512-77VJr3OR/VUZzPiStyRhADmO2jApMM0V2b1qf0RpfWya8Zr1PeZev5AEpPGAAKWdiYUtcZGBE4F5QvJml1PvWA==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.17.0", - "@shikijs/engine-javascript": "3.17.0", - "@shikijs/engine-oniguruma": "3.17.0", - "@shikijs/langs": "3.17.0", - "@shikijs/themes": "3.17.0", - "@shikijs/types": "3.17.0", + "@shikijs/core": "3.19.0", + "@shikijs/engine-javascript": "3.19.0", + "@shikijs/engine-oniguruma": "3.19.0", + "@shikijs/langs": "3.19.0", + "@shikijs/themes": "3.19.0", + "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "node_modules/shiki/node_modules/@shikijs/core": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.17.0.tgz", - "integrity": "sha512-/HjeOnbc62C+n33QFNFrAhUlIADKwfuoS50Ht0pxujxP4QjZAlFp5Q+OkDo531SCTzivx5T18khwyBdKoPdkuw==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.19.0.tgz", + "integrity": "sha512-L7SrRibU7ZoYi1/TrZsJOFAnnHyLTE1SwHG1yNWjZIVCqjOEmCSuK2ZO9thnRbJG6TOkPp+Z963JmpCNw5nzvA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.17.0", + "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/shiki/node_modules/@shikijs/engine-javascript": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.17.0.tgz", - "integrity": "sha512-WwF99xdP8KfuDrIbT4wxyypfhoIxMeeOCp1AiuvzzZ6JT5B3vIuoclL8xOuuydA6LBeeNXUF/XV5zlwwex1jlA==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.19.0.tgz", + "integrity": "sha512-ZfWJNm2VMhKkQIKT9qXbs76RRcT0SF/CAvEz0+RkpUDAoDaCx0uFdCGzSRiD9gSlhm6AHkjdieOBJMaO2eC1rQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.17.0", + "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "node_modules/shiki/node_modules/@shikijs/engine-oniguruma": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.17.0.tgz", - "integrity": "sha512-flSbHZAiOZDNTrEbULY8DLWavu/TyVu/E7RChpLB4WvKX4iHMfj80C6Hi3TjIWaQtHOW0KC6kzMcuB5TO1hZ8Q==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.19.0.tgz", + "integrity": "sha512-1hRxtYIJfJSZeM5ivbUXv9hcJP3PWRo5prG/V2sWwiubUKTa+7P62d2qxCW8jiVFX4pgRHhnHNp+qeR7Xl+6kg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.17.0", + "@shikijs/types": "3.19.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/shiki/node_modules/@shikijs/types": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.17.0.tgz", - "integrity": "sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.19.0.tgz", + "integrity": "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -8435,9 +8716,9 @@ } }, "node_modules/string-ts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.2.1.tgz", - "integrity": "sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.3.1.tgz", + "integrity": "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==", "license": "MIT" }, "node_modules/string-width": { @@ -8762,6 +9043,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8951,9 +9277,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -9122,7 +9448,6 @@ "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, - "peer": true, "dependencies": { "napi-postinstall": "^0.2.4" }, @@ -9424,15 +9749,18 @@ } }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { @@ -9536,7 +9864,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 0fad739d..afb1c116 100644 --- a/package.json +++ b/package.json @@ -24,33 +24,33 @@ "doc-kit": "./bin/cli.mjs" }, "devDependencies": { - "@eslint/js": "^9.36.0", + "@eslint/js": "^9.39.1", "@reporters/github": "^1.11.0", "@types/mdast": "^4.0.4", - "@types/node": "^22.18.6", + "@types/node": "^24.10.1", "c8": "^10.1.3", - "eslint": "^9.36.0", + "eslint": "^9.39.1", "eslint-import-resolver-node": "^0.3.9", "eslint-plugin-import-x": "^4.16.1", - "eslint-plugin-jsdoc": "^61.1.12", + "eslint-plugin-jsdoc": "^61.4.1", "husky": "^9.1.7", - "lint-staged": "^16.2.6", - "prettier": "3.6.2" + "lint-staged": "^16.2.7", + "prettier": "3.7.4" }, "dependencies": { "@actions/core": "^1.11.1", "@clack/prompts": "^0.11.0", "@heroicons/react": "^2.2.0", - "@minify-html/node": "^0.16.4", + "@minify-html/node": "^0.18.1", "@node-core/rehype-shiki": "1.3.0", "@node-core/ui-components": "1.4.0", "@orama/orama": "^3.1.16", "@orama/react-components": "^0.8.1", "@rollup/plugin-virtual": "^3.0.2", - "acorn": "^8.15.0", + "oxc-parser": "^0.101.0", "commander": "^14.0.2", "dedent": "^1.7.0", - "eslint-plugin-react-x": "^2.3.1", + "eslint-plugin-react-x": "^2.3.12", "estree-util-to-js": "^2.0.0", "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", @@ -58,9 +58,9 @@ "globals": "^16.5.0", "hast-util-to-string": "^3.0.1", "hastscript": "^9.0.1", - "lightningcss": "^1.30.1", + "lightningcss": "^1.30.2", "mdast-util-slice-markdown": "^2.0.1", - "preact": "^10.27.2", + "preact": "^10.28.0", "preact-render-to-string": "^6.6.3", "reading-time": "^1.5.0", "recma-jsx": "^1.0.1", @@ -71,9 +71,9 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", - "rolldown": "^1.0.0-beta.47", - "semver": "^7.7.2", - "shiki": "^3.17.0", + "rolldown": "^1.0.0-beta.53", + "semver": "^7.7.3", + "shiki": "^3.19.0", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-find-after": "^5.0.0", @@ -82,6 +82,6 @@ "unist-util-select": "^5.1.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", - "yaml": "^2.8.1" + "yaml": "^2.8.2" } } diff --git a/src/parsers/javascript.mjs b/src/parsers/javascript.mjs index d5a7714d..8832996f 100644 --- a/src/parsers/javascript.mjs +++ b/src/parsers/javascript.mjs @@ -1,6 +1,6 @@ 'use strict'; -import * as acorn from 'acorn'; +import { parseSync } from 'oxc-parser'; /** * Creates a Javascript source parser for a given source file @@ -23,13 +23,15 @@ const createParser = () => { ); } - const res = acorn.parse(resolvedSourceFile.value, { - allowReturnOutsideFunction: true, - ecmaVersion: 'latest', - locations: true, - }); + const result = parseSync(resolvedSourceFile.path, resolvedSourceFile.value); - return { ...res, path: resolvedSourceFile.path }; + if (result.errors.length > 0) { + throw new Error( + `Failed to parse ${resolvedSourceFile.path}: ${result.errors[0].message}` + ); + } + + return { ...result.program, path: resolvedSourceFile.path }; }; /** From 4edb5645907c2a5c3dbf378e5c8396d58bd2d6ad Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 02:21:56 +0100 Subject: [PATCH 6/9] chore: fix version --- npm-shrinkwrap.json | 48 ++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e56d0141..51ed7c97 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,7 +9,7 @@ "@actions/core": "^1.11.1", "@clack/prompts": "^0.11.0", "@heroicons/react": "^2.2.0", - "@minify-html/node": "^0.18.1", + "@minify-html/node": "^0.16.4", "@node-core/rehype-shiki": "1.3.0", "@node-core/ui-components": "1.4.0", "@orama/orama": "^3.1.16", @@ -690,9 +690,9 @@ } }, "node_modules/@minify-html/node": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node/-/node-0.18.1.tgz", - "integrity": "sha512-pNOv6z9zUDrjTU1+VcPOnDzGeaaqd6m9kbyHU+sg9O35450uzfke8ahx8gKlFdp/kQFrRn2rFIGAoANUZ62nSw==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node/-/node-0.16.4.tgz", + "integrity": "sha512-ykQgl6xcQQDE1shUExeObPSNwAf00DVUt/GrxdjiqFNCVGu7DXK9nuH29sNTyKKYnJJLZAi6OEib2bDfxW3udg==", "license": "MIT", "bin": { "minify-html": "cli.js" @@ -701,17 +701,17 @@ "node": ">= 8.6.0" }, "optionalDependencies": { - "@minify-html/node-darwin-arm64": "0.18.1", - "@minify-html/node-darwin-x64": "0.18.1", - "@minify-html/node-linux-arm64": "0.18.1", - "@minify-html/node-linux-x64": "0.18.1", - "@minify-html/node-win32-x64": "0.18.1" + "@minify-html/node-darwin-arm64": "0.16.4", + "@minify-html/node-darwin-x64": "0.16.4", + "@minify-html/node-linux-arm64": "0.16.4", + "@minify-html/node-linux-x64": "0.16.4", + "@minify-html/node-win32-x64": "0.16.4" } }, "node_modules/@minify-html/node-darwin-arm64": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-arm64/-/node-darwin-arm64-0.18.1.tgz", - "integrity": "sha512-sc/VJHRcHtEk0IQP2hRA/TWkAQntaYH3RCw8n750NbfzliQqkyrDqNAGiKBOli7UlhFtG/H2aWqGjtOjIM/dZA==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-arm64/-/node-darwin-arm64-0.16.4.tgz", + "integrity": "sha512-9H8hcywDb8zo2jEJfaIAibgsKjMqE+XF7SyqTtJ5H8lVXHxffOkawH4TQtphf9V/x7zXeb/nByAvHe1orJ/RHA==", "cpu": [ "arm64" ], @@ -721,9 +721,9 @@ ] }, "node_modules/@minify-html/node-darwin-x64": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-x64/-/node-darwin-x64-0.18.1.tgz", - "integrity": "sha512-0bff2ng4Vgp3+kdC8zgjd5dMN04zkM4nGuYEVP4dLR7J5NCD/Ka4XD4CKNSDZ9goLYQ8BjtKD6AeDTfJ8KBjrA==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node-darwin-x64/-/node-darwin-x64-0.16.4.tgz", + "integrity": "sha512-P0Krf5nwXbccMrC7ragKAIVOENHFoVRQi+v/8k5pmfjrNlxgXGVILacG0FbUZXsH2Z2XaIo39HxuMf70L6wQlA==", "cpu": [ "x64" ], @@ -733,9 +733,9 @@ ] }, "node_modules/@minify-html/node-linux-arm64": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node-linux-arm64/-/node-linux-arm64-0.18.1.tgz", - "integrity": "sha512-CVGu5XVmmDrGFuA9/b/695rJBFvfTZFwKJJYA1ZXAsBZrqlKVO4bfXPq9DVNmhQ76ky3LFtVZ/+UsU7xpyK5Qw==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node-linux-arm64/-/node-linux-arm64-0.16.4.tgz", + "integrity": "sha512-GDRExKf7AmyAdBTdhMkMyzFhJu5VeyJTu0OnNH2ekp69JrhQTrrrt9UYqnjen+7qLIaZB/R8urDRAYNk0HZi5w==", "cpu": [ "arm64" ], @@ -745,9 +745,9 @@ ] }, "node_modules/@minify-html/node-linux-x64": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node-linux-x64/-/node-linux-x64-0.18.1.tgz", - "integrity": "sha512-qmIv4kRyhZTKO3nXDXHlFrhnmwIKTM57ZRomlYRYdZTWiFW3CbJ4Ggg57qIgx7D/t4dgyS7/O8OTetrWvmEg9w==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node-linux-x64/-/node-linux-x64-0.16.4.tgz", + "integrity": "sha512-MS/gF1gxJoeHqEGcb1xoUIRv6gVin4cGJszgHPYSikzkK8Yg0p6rVOZdDAE4AAnp/NW0DYNq7fwYgw3igmppFw==", "cpu": [ "x64" ], @@ -757,9 +757,9 @@ ] }, "node_modules/@minify-html/node-win32-x64": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@minify-html/node-win32-x64/-/node-win32-x64-0.18.1.tgz", - "integrity": "sha512-SNRE/NvcKXvh4UwXTRrujNSWaCj/3E+FFN+FLOBaN4FY+dZG83h6nWxGHJ5+LAoJxVTU7Al1e8toi4JjHpzv2w==", + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/@minify-html/node-win32-x64/-/node-win32-x64-0.16.4.tgz", + "integrity": "sha512-SCY7hzIqG1RclU0QzU2MlGtPOujPu6dvaPYqDvhAHpkvRXtX0hnyOrrfqf7GcBdDbASxV8LDlBWpY46JO2cjAA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index afb1c116..97454fcd 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@actions/core": "^1.11.1", "@clack/prompts": "^0.11.0", "@heroicons/react": "^2.2.0", - "@minify-html/node": "^0.18.1", + "@minify-html/node": "^0.16.4", "@node-core/rehype-shiki": "1.3.0", "@node-core/ui-components": "1.4.0", "@orama/orama": "^3.1.16", From ad3accbad0e9be0f3113089dd2f627a1af0ed834 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 02:25:34 +0100 Subject: [PATCH 7/9] chore: fix linting --- src/types.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/types.d.ts b/src/types.d.ts index ca1bfaa6..3e311761 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -42,8 +42,10 @@ declare global { slug: string; } - export interface HeadingMetadataParent - extends NodeWithData {} + export interface HeadingMetadataParent extends NodeWithData< + Heading, + HeadingMetadataEntry + > {} export interface ApiDocMetadataChange { // The Node.js version or versions where said change was introduced simultaneously From e0695936e4d3dd3b6c4450482a2c1bc4f0804d69 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 13:08:30 +0100 Subject: [PATCH 8/9] chore: code review --- src/generators/types.d.ts | 9 --------- src/threading/parallel.mjs | 12 ------------ 2 files changed, 21 deletions(-) diff --git a/src/generators/types.d.ts b/src/generators/types.d.ts index 6659e488..201d8e39 100644 --- a/src/generators/types.d.ts +++ b/src/generators/types.d.ts @@ -22,15 +22,6 @@ declare global { fullInput: unknown, opts?: Record ): Promise; - - /** - * Process items in parallel, ignoring return values. - */ - forEach( - items: T[], - fullInput: unknown, - opts?: Record - ): Promise; } // This is the runtime config passed to the API doc generators diff --git a/src/threading/parallel.mjs b/src/threading/parallel.mjs index 6adbf6b9..25333cb8 100644 --- a/src/threading/parallel.mjs +++ b/src/threading/parallel.mjs @@ -106,17 +106,5 @@ export default function createParallelWorker(generatorName, pool, options) { // Flatten results return chunkResults.flat(); }, - - /** - * Process items in parallel, ignoring return values. - * @template T - * @param {T[]} items - Items to process - * @param {T[]} fullInput - Full input data for context rebuilding - * @param {object} extra - Generator-specific context - * @returns {Promise} - */ - async forEach(items, fullInput, extra) { - await this.map(items, fullInput, extra); - }, }; } From b933b0a366497cf3eecb594e383dcb50611d1592 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Fri, 5 Dec 2025 14:26:14 +0100 Subject: [PATCH 9/9] chore: fix tests and back to acorn --- npm-shrinkwrap.json | 308 +----------------- package.json | 2 +- .../api-links/__tests__/fixtures.test.mjs | 11 + src/generators/web/utils/bundle.mjs | 7 +- .../__tests__/transports/console.test.mjs | 12 + .../__tests__/transports/github.test.mjs | 12 + src/parsers/javascript.mjs | 16 +- src/threading/__tests__/WorkerPool.test.mjs | 90 +++++ src/threading/__tests__/parallel.test.mjs | 85 +++++ 9 files changed, 234 insertions(+), 309 deletions(-) create mode 100644 src/threading/__tests__/WorkerPool.test.mjs create mode 100644 src/threading/__tests__/parallel.test.mjs diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 51ed7c97..30894767 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -15,6 +15,7 @@ "@orama/orama": "^3.1.16", "@orama/react-components": "^0.8.1", "@rollup/plugin-virtual": "^3.0.2", + "acorn": "^8.15.0", "commander": "^14.0.2", "dedent": "^1.7.0", "eslint-plugin-react-x": "^2.3.12", @@ -27,7 +28,6 @@ "hastscript": "^9.0.1", "lightningcss": "^1.30.2", "mdast-util-slice-markdown": "^2.0.1", - "oxc-parser": "^0.101.0", "preact": "^10.28.0", "preact-render-to-string": "^6.6.3", "reading-time": "^1.5.0", @@ -937,7 +937,6 @@ "resolved": "https://registry.npmjs.org/@orama/core/-/core-0.0.10.tgz", "integrity": "sha512-rZ4AHeHoFTxOXMhM0An2coO3OfR+FpL0ejXc1PPrNsGB4p6VNlky7FAGeuqOvS5gUYB5ywJsmDzCxeflPtgk4w==", "license": "AGPL-3.0", - "peer": true, "dependencies": { "@orama/cuid2": "2.2.3", "dedent": "1.5.3" @@ -948,7 +947,6 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "license": "MIT", - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -978,6 +976,7 @@ "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-3.1.16.tgz", "integrity": "sha512-scSmQBD8eANlMUOglxHrN1JdSW8tDghsPuS83otqealBiIeMukCQMOf/wc0JJjDXomqwNdEQFLXLGHrU6PGxuA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 20.0.0" } @@ -986,8 +985,7 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/@orama/oramacore-events-parser/-/oramacore-events-parser-0.0.5.tgz", "integrity": "sha512-yAuSwog+HQBAXgZ60TNKEwu04y81/09mpbYBCmz1RCxnr4ObNY2JnPZI7HmALbjAhLJ8t5p+wc2JHRK93ubO4w==", - "license": "AGPL-3.0", - "peer": true + "license": "AGPL-3.0" }, "node_modules/@orama/react-components": { "version": "0.8.1", @@ -1209,264 +1207,13 @@ "resolved": "https://registry.npmjs.org/@oramacloud/client/-/client-2.1.4.tgz", "integrity": "sha512-uNPFs4wq/iOPbggCwTkVNbIr64Vfd7ZS/h+cricXVnzXWocjDTfJ3wLL4lr0qiSu41g8z+eCAGBqJ30RO2O4AA==", "license": "ISC", + "peer": true, "dependencies": { "@orama/cuid2": "^2.2.3", "@orama/orama": "^3.0.0", "lodash": "^4.17.21" } }, - "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.101.0.tgz", - "integrity": "sha512-dh1thtigwmLtJTF3BbgC+5lucGYdBAsnLE02scOSOZpiaEcsl5acMwwPBlhjHrHGWS/xBRz53Z178ilO0q+sWg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.101.0.tgz", - "integrity": "sha512-DCy7zJDxHo7iT9Y8eDSvBt4HN5pOSb+8y+eJv5Kq8plMQF5oatcu5ZvHvP6Hij3jRNBgpwTC4vWLdND7l/zsCA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.101.0.tgz", - "integrity": "sha512-M5oPJFQ/B7wXOjL8r/qezIngLdypH8aCsJx2cb94Eo/gGix0AgEr9ojVF1P/3kJu2Oi/prZf5Cgf0XfbRfm9Gw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.101.0.tgz", - "integrity": "sha512-IG9smLrG7jh/VjKR7haW07+cC0cxq9i74iTNmS73cKo43VrfFxce6f+qXPaZj8EDizoFDqn5imWOb8tc2dBxTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.101.0.tgz", - "integrity": "sha512-AW8JrXyf2e9y4KlF5cFFYyD1+eKp1PSUKeg5FUevAn5QBFjr/IO2iZ+bLkK66M4z/oRma62pFjo3ubVEggenVw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.101.0.tgz", - "integrity": "sha512-c7myby84UFxRqGPM0wEhdIqz0Ta4GZHoj0IVUSYNNar4j0Cmll1H/f/43cJGj2EwL4sDVDPRrF526JwJIHOZYg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.101.0.tgz", - "integrity": "sha512-LZ7o9sFafyIVOwpHQkEPyF3EfZYzGWXNkzznSSASlHxoyo/Uk3EIqL1B2UG0bWxHsz7nNIhv9ItyfGm+/7QHXQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.101.0.tgz", - "integrity": "sha512-3LyKucFn9Yu9IggO4FPkbaghcMvr+fWO3krdcQBm6MDZiRsx8c+xcqmGji8l4evaAA6oHFg3eYNKsFgjQoHnkA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.101.0.tgz", - "integrity": "sha512-TCJhU5WTdvua4IMXz67CUESbxYZT9Adyt9KhKC+7H6hcjCJd111kTMG5AIqegeaZjxs7tDCyDCtymvKtD6BvCg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.101.0.tgz", - "integrity": "sha512-owQQTlvFDn496Rcx+aHvxcaVHeX/iQX2zNYB9mh8XywIyO1QLhOVDxNHrFYnbMoXGNnwXnN4CPtpYXPuMS338g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.101.0.tgz", - "integrity": "sha512-NcGgxNoVM/cjTqbMsq8olHWV0obfCnTYic/d12c49e0p8CV412xOrB1C9dXO8POd1evrrIIXCeMaroliRgl9/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.101.0.tgz", - "integrity": "sha512-pLTLWauhjrNq7dn+l1316Q98k4SCSlLFfhor0evbA+e0pPDrxQvCL0K4Jfn+zLTV086f9SD3/XJ3rHVE91UiJw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.101.0.tgz", - "integrity": "sha512-6jZ5fDUOlHICoTpcz7oHKyy3mF7RfM/hmSMnY1/b99Z+7hFql4yNlyHJ0RS1lS11H3V2qzxXXWXocGlOz3qmWw==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", - "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - } - }, - "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.101.0.tgz", - "integrity": "sha512-BRcLSzo0NZUSB5vJTW9NEnnIHOYLfiOVgXl+a0Hbv7sr/3xl3E4arkx/btNL441uDSEPFtrM1rcclpICDuYhlA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.101.0.tgz", - "integrity": "sha512-HF6deX1VgbzVXl+v/7j02uQKXJtUtMhIQQMbmTg1wZVDbSOPgIVdwrOqUhSdaCt7gnbiD4KR3TAI1tJgqY8LxQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@oxc-project/types": { "version": "0.101.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", @@ -3894,6 +3641,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4434,8 +4183,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -4717,6 +4465,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6562,6 +6311,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -7631,38 +7381,6 @@ "node": ">= 0.8.0" } }, - "node_modules/oxc-parser": { - "version": "0.101.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.101.0.tgz", - "integrity": "sha512-Njg0KoSisH57AWzKTImV0JpjUBu0riCwbMTnnSH8H/deHpJaVpcbmwsiKkSd7ViX6lxaXiOiBVZH2quWPUFtUg==", - "license": "MIT", - "dependencies": { - "@oxc-project/types": "^0.101.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm64": "0.101.0", - "@oxc-parser/binding-darwin-arm64": "0.101.0", - "@oxc-parser/binding-darwin-x64": "0.101.0", - "@oxc-parser/binding-freebsd-x64": "0.101.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.101.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.101.0", - "@oxc-parser/binding-linux-arm64-musl": "0.101.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.101.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.101.0", - "@oxc-parser/binding-linux-x64-gnu": "0.101.0", - "@oxc-parser/binding-linux-x64-musl": "0.101.0", - "@oxc-parser/binding-openharmony-arm64": "0.101.0", - "@oxc-parser/binding-wasm32-wasi": "0.101.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.101.0", - "@oxc-parser/binding-win32-x64-msvc": "0.101.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7863,6 +7581,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7912,6 +7631,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -8479,8 +8199,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "7.7.3", @@ -9081,6 +8800,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9448,6 +9168,7 @@ "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, + "peer": true, "dependencies": { "napi-postinstall": "^0.2.4" }, @@ -9864,6 +9585,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 97454fcd..7d9c2970 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@orama/orama": "^3.1.16", "@orama/react-components": "^0.8.1", "@rollup/plugin-virtual": "^3.0.2", - "oxc-parser": "^0.101.0", + "acorn": "^8.15.0", "commander": "^14.0.2", "dedent": "^1.7.0", "eslint-plugin-react-x": "^2.3.12", diff --git a/src/generators/api-links/__tests__/fixtures.test.mjs b/src/generators/api-links/__tests__/fixtures.test.mjs index 2284249a..fc6b204f 100644 --- a/src/generators/api-links/__tests__/fixtures.test.mjs +++ b/src/generators/api-links/__tests__/fixtures.test.mjs @@ -1,7 +1,10 @@ import { readdir } from 'node:fs/promises'; +import { cpus } from 'node:os'; import { basename, extname, join } from 'node:path'; import { describe, it } from 'node:test'; +import WorkerPool from '../../../threading/index.mjs'; +import createParallelWorker from '../../../threading/parallel.mjs'; import astJs from '../../ast-js/index.mjs'; import apiLinks from '../index.mjs'; @@ -16,8 +19,16 @@ describe('api links', () => { describe('should work correctly for all fixtures', () => { sourceFiles.forEach(sourceFile => { it(`${basename(sourceFile)}`, async t => { + const pool = new WorkerPool('../chunk-worker.mjs', cpus().length); + + const worker = createParallelWorker('ast-js', pool, { + threads: 1, + chunkSize: 10, + }); + const astJsResult = await astJs.generate(undefined, { input: [sourceFile], + worker, }); const actualOutput = await apiLinks.generate(astJsResult, { diff --git a/src/generators/web/utils/bundle.mjs b/src/generators/web/utils/bundle.mjs index b92fe9a7..441b184d 100644 --- a/src/generators/web/utils/bundle.mjs +++ b/src/generators/web/utils/bundle.mjs @@ -84,12 +84,7 @@ export default async function bundleCode(codeMap, { server = false } = {}) { resolve: { // Alias react imports to preact/compat for smaller bundle sizes. // Explicit jsx-runtime aliases are required for the automatic JSX transform. - alias: { - react: 'preact/compat', - 'react-dom': 'preact/compat', - 'react/jsx-runtime': 'preact/jsx-runtime', - 'react/jsx-dev-runtime': 'preact/jsx-dev-runtime', - }, + alias: { react: 'preact/compat' }, // Tell the bundler where to find node_modules. // This ensures packages are found when running doc-kit from external directories diff --git a/src/logger/__tests__/transports/console.test.mjs b/src/logger/__tests__/transports/console.test.mjs index 71d1268b..cf346707 100644 --- a/src/logger/__tests__/transports/console.test.mjs +++ b/src/logger/__tests__/transports/console.test.mjs @@ -6,6 +6,8 @@ import console from '../../transports/console.mjs'; describe('console', () => { it('should print debug messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -33,6 +35,8 @@ describe('console', () => { }); it('should print info messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -59,6 +63,8 @@ describe('console', () => { }); it('should print error messages ', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -86,6 +92,8 @@ describe('console', () => { }); it('should print fatal messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -113,6 +121,8 @@ describe('console', () => { }); it('should print messages with file', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -151,6 +161,8 @@ describe('console', () => { }); it('should print child logger name', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); diff --git a/src/logger/__tests__/transports/github.test.mjs b/src/logger/__tests__/transports/github.test.mjs index 44674de8..ed7d7dfa 100644 --- a/src/logger/__tests__/transports/github.test.mjs +++ b/src/logger/__tests__/transports/github.test.mjs @@ -6,6 +6,8 @@ import github from '../../transports/github.mjs'; describe('github', () => { it('should print debug messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -31,6 +33,8 @@ describe('github', () => { }); it('should print info messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -55,6 +59,8 @@ describe('github', () => { }); it('should print error messages ', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -80,6 +86,8 @@ describe('github', () => { }); it('should print fatal messages', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -105,6 +113,8 @@ describe('github', () => { }); it('should print messages with file', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); @@ -139,6 +149,8 @@ describe('github', () => { }); it('should print child logger name', t => { + process.env.FORCE_COLOR = '1'; + t.mock.timers.enable({ apis: ['Date'] }); const fn = t.mock.method(process.stdout, 'write'); diff --git a/src/parsers/javascript.mjs b/src/parsers/javascript.mjs index 8832996f..d5a7714d 100644 --- a/src/parsers/javascript.mjs +++ b/src/parsers/javascript.mjs @@ -1,6 +1,6 @@ 'use strict'; -import { parseSync } from 'oxc-parser'; +import * as acorn from 'acorn'; /** * Creates a Javascript source parser for a given source file @@ -23,15 +23,13 @@ const createParser = () => { ); } - const result = parseSync(resolvedSourceFile.path, resolvedSourceFile.value); + const res = acorn.parse(resolvedSourceFile.value, { + allowReturnOutsideFunction: true, + ecmaVersion: 'latest', + locations: true, + }); - if (result.errors.length > 0) { - throw new Error( - `Failed to parse ${resolvedSourceFile.path}: ${result.errors[0].message}` - ); - } - - return { ...result.program, path: resolvedSourceFile.path }; + return { ...res, path: resolvedSourceFile.path }; }; /** diff --git a/src/threading/__tests__/WorkerPool.test.mjs b/src/threading/__tests__/WorkerPool.test.mjs new file mode 100644 index 00000000..c878fe48 --- /dev/null +++ b/src/threading/__tests__/WorkerPool.test.mjs @@ -0,0 +1,90 @@ +import { deepStrictEqual, ok, strictEqual } from 'node:assert'; +import { describe, it } from 'node:test'; + +import WorkerPool from '../index.mjs'; + +describe('WorkerPool', () => { + // Use relative path from WorkerPool's location (src/threading/) + const workerPath = './chunk-worker.mjs'; + + it('should create a worker pool with specified thread count', () => { + const pool = new WorkerPool(workerPath, 4); + + strictEqual(pool.threads, 4); + strictEqual(pool.getActiveThreadCount(), 0); + }); + + it('should initialize with zero active threads', () => { + const pool = new WorkerPool(workerPath, 2); + + strictEqual(pool.getActiveThreadCount(), 0); + }); + + it('should change active thread count atomically', () => { + const pool = new WorkerPool(workerPath, 2); + + pool.changeActiveThreadCount(1); + strictEqual(pool.getActiveThreadCount(), 1); + + pool.changeActiveThreadCount(2); + strictEqual(pool.getActiveThreadCount(), 3); + + pool.changeActiveThreadCount(-1); + strictEqual(pool.getActiveThreadCount(), 2); + }); + + it('should queue tasks when thread limit is reached', async () => { + const pool = new WorkerPool(workerPath, 1); + + const task1 = pool.run({ + generatorName: 'ast-js', + fullInput: [], + itemIndices: [], + options: {}, + }); + + const task2 = pool.run({ + generatorName: 'ast-js', + fullInput: [], + itemIndices: [], + options: {}, + }); + + const results = await Promise.all([task1, task2]); + + ok(Array.isArray(results)); + strictEqual(results.length, 2); + }); + + it('should run multiple tasks in parallel with runAll', async () => { + const pool = new WorkerPool(workerPath, 2); + + const tasks = [ + { + generatorName: 'ast-js', + fullInput: [], + itemIndices: [], + options: {}, + }, + { + generatorName: 'ast-js', + fullInput: [], + itemIndices: [], + options: {}, + }, + ]; + + const results = await pool.runAll(tasks); + + ok(Array.isArray(results)); + strictEqual(results.length, 2); + }); + + it('should handle empty task array', async () => { + const pool = new WorkerPool(workerPath, 2); + + const results = await pool.runAll([]); + + deepStrictEqual(results, []); + }); +}); diff --git a/src/threading/__tests__/parallel.test.mjs b/src/threading/__tests__/parallel.test.mjs new file mode 100644 index 00000000..3090e234 --- /dev/null +++ b/src/threading/__tests__/parallel.test.mjs @@ -0,0 +1,85 @@ +import { deepStrictEqual, ok, strictEqual } from 'node:assert'; +import { describe, it } from 'node:test'; + +import WorkerPool from '../index.mjs'; +import createParallelWorker from '../parallel.mjs'; + +describe('createParallelWorker', () => { + // Use relative path from WorkerPool's location (src/threading/) + const workerPath = './chunk-worker.mjs'; + + it('should create a ParallelWorker with map method', () => { + const pool = new WorkerPool(workerPath, 2); + + const worker = createParallelWorker('metadata', pool, { threads: 2 }); + + ok(worker); + strictEqual(typeof worker.map, 'function'); + }); + + it('should use main thread for single-threaded execution', async () => { + const pool = new WorkerPool(workerPath, 1); + + const worker = createParallelWorker('ast-js', pool, { threads: 1 }); + const items = []; + const results = await worker.map(items, items, {}); + + ok(Array.isArray(results)); + strictEqual(results.length, 0); + }); + + it('should use main thread for small item counts', async () => { + const pool = new WorkerPool(workerPath, 4); + + const worker = createParallelWorker('ast-js', pool, { threads: 4 }); + const items = []; + const results = await worker.map(items, items, {}); + + ok(Array.isArray(results)); + strictEqual(results.length, 0); + }); + + it('should chunk items for parallel processing', async () => { + const pool = new WorkerPool(workerPath, 2); + + const worker = createParallelWorker('ast-js', pool, { threads: 2 }); + const items = []; + + const results = await worker.map(items, items, {}); + + strictEqual(results.length, 0); + ok(Array.isArray(results)); + }); + + it('should pass extra options to worker', async () => { + const pool = new WorkerPool(workerPath, 1); + + const worker = createParallelWorker('ast-js', pool, { threads: 1 }); + const extra = { gitRef: 'main', customOption: 'value' }; + const items = []; + + const results = await worker.map(items, items, extra); + + ok(Array.isArray(results)); + }); + + it('should serialize and deserialize data correctly', async () => { + const pool = new WorkerPool(workerPath, 2); + + const worker = createParallelWorker('ast-js', pool, { threads: 2 }); + const items = []; + + const results = await worker.map(items, items, {}); + + ok(Array.isArray(results)); + }); + + it('should handle empty items array', async () => { + const pool = new WorkerPool(workerPath, 2); + + const worker = createParallelWorker('ast-js', pool, { threads: 2 }); + const results = await worker.map([], [], {}); + + deepStrictEqual(results, []); + }); +});