226 lines
5.8 KiB
JavaScript
226 lines
5.8 KiB
JavaScript
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)`);
|
|
}
|