import { readdirSync, readFileSync, writeFileSync, statSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const storeDir = join(__dirname, 'src', 'store'); const outputPath = join(__dirname, 'API.md'); // Modules that are exposed on the LibDashStore instance (from src/index.js). // Order here controls the order of sections in the generated document. const MODULE_ORDER = [ 'companyuser', 'company', 'category', 'article', 'lang', 'tax', 'unit', 'companysetting', 'brand', 'customergroup', 'entity', 'entityaddress', 'entitycontact', 'supplier', 'delivery', 'quote', 'order', 'posterminal', 'google_auth', 'pic', ]; // Files that are internal helpers and should not appear as public API entries. const INTERNAL_FILES = new Set([ 'request.js', 'slugify.js', 'initialize.js', 'purge.js', 'helpers.js', 'writeLines.js', 'shapeSetting.js', 'shapeUnit.js', 'ensureDefault.js', 'ensureLieferantenGroup.js', 'resolveCompanyAccess.js', 'resolveCategoryAccess.js', 'resolveCustomerGroupAccess.js', 'resolveEntityAccess.js', 'resolvePicAccess.js', 'resolveArticleBySku.js', 'queryBySku.js', 'queryCategoryArticles.js', 'getCategoryPosterminalIds.js', 'nextSku.js', 'createStore.js', 'index.js', ]); /** * Extract the leading JSDoc block from a source string. * @param {string} source * @returns {{ description: string, example: string } | null} */ function extractJsDoc(source) { const match = source.match(/\/\*\*([\s\S]*?)\*\//); if (!match) return null; const block = match[1]; const lines = block .split('\n') .map((l) => l.replace(/^\s*\*\s?/, '').replace(/^\s*\*/, '')); const description = []; const example = []; let inExample = false; for (const line of lines) { if (line.trim() === '@example') { inExample = true; continue; } if (line.trim().startsWith('@')) { inExample = false; continue; } if (inExample) { example.push(line); } else if (line.trim()) { description.push(line.trim()); } } return { description: description.join(' ').trim(), example: example.join('\n').trim(), }; } /** * Recursively collect all .js files under a directory. * @param {string} dir * @returns {string[]} absolute file paths */ function collectFiles(dir) { const out = []; for (const entry of readdirSync(dir)) { const full = join(dir, entry); if (statSync(full).isDirectory()) { out.push(...collectFiles(full)); } else if (entry.endsWith('.js')) { out.push(full); } } return out; } /** * Build a map of module name -> array of { name, file, description, example }. * @returns {Map>} */ function buildModules() { const modules = new Map(); for (const file of collectFiles(storeDir)) { const rel = file.slice(storeDir.length + 1).split('/'); const moduleName = rel[0]; const fileName = rel[rel.length - 1]; if (INTERNAL_FILES.has(fileName)) continue; const source = readFileSync(file, 'utf8'); const doc = extractJsDoc(source); if (!doc) continue; const fnName = fileName.replace(/\.js$/, ''); if (!modules.has(moduleName)) { modules.set(moduleName, []); } modules.get(moduleName).push({ name: fnName, file: rel.join('/'), description: doc.description, example: doc.example, }); } return modules; } function renderSection(moduleName, entries) { const lines = []; lines.push(`# ${moduleName}`); lines.push(''); lines.push( 'This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.', ); lines.push(''); for (const entry of entries) { lines.push(`## \`${moduleName}.${entry.name}\``); lines.push(''); lines.push(`*Source: \`src/store/${entry.file}\`*`); lines.push(''); if (entry.description) { lines.push(entry.description); lines.push(''); } if (entry.example) { lines.push('```js'); lines.push(entry.example); lines.push('```'); lines.push(''); } } return lines.join('\n'); } function render() { const modules = buildModules(); const ordered = MODULE_ORDER.filter((m) => modules.has(m)); const extra = [...modules.keys()].filter((m) => !MODULE_ORDER.includes(m)).sort(); const moduleNames = [...ordered, ...extra]; // Write one file per section: API.
.md const written = []; for (const moduleName of moduleNames) { const sectionPath = join(__dirname, `API.${moduleName}.md`); const markdown = renderSection(moduleName, modules.get(moduleName)); writeFileSync(sectionPath, markdown, 'utf8'); written.push({ moduleName, sectionPath, bytes: markdown.length }); } // Write the index API.md linking to each section file. const lines = []; lines.push('# LibDashStore API'); lines.push(''); lines.push( 'This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.', ); lines.push(''); lines.push( 'The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each method is an async function that takes a `pool` (mssql connection pool) at construction time and returns a callable function. Methods are grouped by the namespace they are exposed under.', ); lines.push(''); lines.push('## Table of Contents'); lines.push(''); for (const { moduleName } of written) { lines.push(`- [${moduleName}](API.${moduleName}.md)`); } lines.push(''); return { index: lines.join('\n'), written }; } const { index, written } = render(); writeFileSync(outputPath, index, 'utf8'); console.log(`Wrote ${outputPath} (${index.length} bytes)`); for (const { moduleName, sectionPath, bytes } of written) { console.log(`Wrote ${sectionPath} (${bytes} bytes)`); }