docs
This commit is contained in:
225
generate-api-doc.js
Normal file
225
generate-api-doc.js
Normal file
@@ -0,0 +1,225 @@
|
||||
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<string, Array<object>>}
|
||||
*/
|
||||
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.<section>.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)`);
|
||||
}
|
||||
Reference in New Issue
Block a user