Files
ISA-Frontend/tools/generate-library-reference.js
Lorenz Hilpert 743d6c1ee9 docs: comprehensive CLAUDE.md overhaul with library reference system
- Restructure CLAUDE.md with clearer sections and updated metadata
- Add research guidelines emphasizing subagent usage and documentation-first approach
- Create library reference guide covering all 61 libraries across 12 domains
- Add automated library reference generation tool
- Complete test coverage for reward order confirmation feature (6 new spec files)
- Refine product info components and adapters with improved documentation
- Update workflows documentation for checkout service
- Fix ESLint issues: case declarations, unused imports, and unused variables
2025-10-22 11:55:04 +02:00

331 lines
11 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Generate Library Reference Documentation
*
* This script scans the monorepo's libs/ directory, reads project.json files,
* and generates the docs/library-reference.md file with organized library information.
*
* Usage: node tools/generate-library-reference.js
*/
const fs = require('fs');
const path = require('path');
// Configuration
const LIBS_DIR = path.join(__dirname, '..', 'libs');
const OUTPUT_FILE = path.join(__dirname, '..', 'docs', 'library-reference.md');
const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
const TSCONFIG_PATH = path.join(__dirname, '..', 'tsconfig.base.json');
// Domain configuration with display order
const DOMAIN_CONFIG = {
availability: { order: 1, name: 'Availability Domain' },
catalogue: { order: 2, name: 'Catalogue Domain' },
checkout: { order: 3, name: 'Checkout Domain' },
common: { order: 4, name: 'Common Libraries' },
core: { order: 5, name: 'Core Libraries' },
crm: { order: 6, name: 'CRM Domain' },
icons: { order: 7, name: 'Icons' },
oms: { order: 8, name: 'OMS Domain' },
remission: { order: 9, name: 'Remission Domain' },
shared: { order: 10, name: 'Shared Component Libraries' },
ui: { order: 11, name: 'UI Component Libraries' },
utils: { order: 12, name: 'Utility Libraries' },
};
/**
* Recursively find all project.json files in the libs directory
*/
function findProjectFiles(dir) {
const projectFiles = [];
function traverse(currentPath) {
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
if (entry.isDirectory()) {
traverse(fullPath);
} else if (entry.name === 'project.json') {
projectFiles.push(fullPath);
}
}
}
traverse(dir);
return projectFiles;
}
/**
* Parse a project.json file and extract relevant metadata
*/
function parseProjectFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const project = JSON.parse(content);
// Extract library path relative to workspace root
const relativePath = path.relative(path.join(__dirname, '..'), path.dirname(filePath));
// Parse domain from path (e.g., libs/oms/feature/return-search -> oms)
const pathParts = relativePath.split(path.sep);
const domain = pathParts[1]; // First part after 'libs'
// Try to extract description from README if available
let description = project.description || '';
const readmePath = path.join(path.dirname(filePath), 'README.md');
if (!description && fs.existsSync(readmePath)) {
const readme = fs.readFileSync(readmePath, 'utf-8');
// Extract first paragraph or heading after title
const match = readme.match(/^#[^\n]+\n+([^\n]+)/m);
if (match) {
description = match[1].replace(/^>?\s*/, '').trim();
}
}
return {
name: project.name,
domain,
path: relativePath,
description,
sourceRoot: project.sourceRoot,
};
} catch (error) {
console.warn(`Warning: Failed to parse ${filePath}: ${error.message}`);
return null;
}
}
/**
* Load path aliases from tsconfig.base.json
*/
function loadPathAliases() {
try {
const tsconfig = JSON.parse(fs.readFileSync(TSCONFIG_PATH, 'utf-8'));
const paths = tsconfig.compilerOptions?.paths || {};
const aliases = {};
// Build a map from library path to path alias
for (const [alias, pathArray] of Object.entries(paths)) {
if (!alias.startsWith('@isa/')) continue;
const libPath = pathArray[0]; // e.g., "libs/oms/feature/return-search/src/index.ts"
if (libPath.startsWith('libs/')) {
// Extract the library directory path
const match = libPath.match(/^libs\/(.+?)\/src\//);
if (match) {
const libDir = match[1]; // e.g., "oms/feature/return-search"
aliases[libDir] = alias;
}
}
}
return aliases;
} catch (error) {
console.warn('Warning: Failed to load path aliases from tsconfig.base.json');
return {};
}
}
/**
* Get version information from package.json
*/
function getVersionInfo() {
try {
const packageJson = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf-8'));
const deps = packageJson.dependencies || {};
const devDeps = packageJson.devDependencies || {};
return {
angular: deps['@angular/core']?.replace(/[\^~]/, '') || 'unknown',
nx: devDeps['nx']?.replace(/[\^~]/, '') || 'unknown',
node: packageJson.engines?.node?.replace(/>=\s*/, '≥') || 'unknown',
npm: packageJson.engines?.npm?.replace(/>=\s*/, '≥') || 'unknown',
};
} catch (error) {
console.warn('Warning: Failed to read version info from package.json');
return {
angular: 'unknown',
nx: 'unknown',
node: 'unknown',
npm: 'unknown',
};
}
}
/**
* Group libraries by domain
*/
function groupByDomain(libraries) {
const grouped = {};
for (const lib of libraries) {
if (!lib) continue;
if (!grouped[lib.domain]) {
grouped[lib.domain] = [];
}
grouped[lib.domain].push(lib);
}
// Sort libraries within each domain by name
for (const domain in grouped) {
grouped[domain].sort((a, b) => a.name.localeCompare(b.name));
}
return grouped;
}
/**
* Generate markdown content
*/
function generateMarkdown(librariesByDomain, totalCount, versions, pathAliases) {
const lines = [];
const now = new Date().toISOString().split('T')[0];
// Header
lines.push('# Library Reference Guide');
lines.push('');
lines.push(`> **Last Updated:** ${now}`);
lines.push(`> **Angular Version:** ${versions.angular}`);
lines.push(`> **Nx Version:** ${versions.nx}`);
lines.push(`> **Total Libraries:** ${totalCount}`);
lines.push('');
lines.push(`All ${totalCount} libraries in the monorepo have comprehensive README.md documentation located at \`libs/[domain]/[layer]/[feature]/README.md\`.`);
lines.push('');
lines.push('**IMPORTANT: Always use the `docs-researcher` subagent** to retrieve and analyze library documentation. This keeps the main context clean and prevents pollution.');
lines.push('');
lines.push('---');
lines.push('');
// Get sorted domains
const sortedDomains = Object.keys(librariesByDomain).sort((a, b) => {
const orderA = DOMAIN_CONFIG[a]?.order || 999;
const orderB = DOMAIN_CONFIG[b]?.order || 999;
return orderA - orderB;
});
// Generate content for each domain
for (const domain of sortedDomains) {
const libraries = librariesByDomain[domain];
const domainName = DOMAIN_CONFIG[domain]?.name || `${domain.charAt(0).toUpperCase() + domain.slice(1)} Domain`;
lines.push(`## ${domainName} (${libraries.length} ${libraries.length === 1 ? 'library' : 'libraries'})`);
lines.push('');
for (const lib of libraries) {
// Get path alias from tsconfig.base.json mappings
// Extract relative lib path (e.g., "libs/oms/feature/return-search" -> "oms/feature/return-search")
const relativeLibPath = lib.path.replace(/^libs\//, '');
const pathAlias = pathAliases[relativeLibPath] || `@isa/${lib.name}`;
lines.push(`### \`${pathAlias}\``);
if (lib.description) {
lines.push(lib.description);
lines.push('');
}
lines.push(`**Location:** \`${lib.path}/\``);
lines.push('');
}
lines.push('---');
lines.push('');
}
// Footer
lines.push('## How to Use This Guide');
lines.push('');
lines.push('1. **Quick Lookup**: Use this guide to find the purpose of any library in the monorepo');
lines.push('2. **Detailed Documentation**: Always use the `docs-researcher` subagent to read the full README.md for implementation details');
lines.push('3. **Path Resolution**: Use the location information to navigate to the library source code');
lines.push('4. **Architecture Understanding**: Use `npx nx graph --filter=[library-name]` to visualize dependencies');
lines.push('');
lines.push('---');
lines.push('');
lines.push('## Maintenance Notes');
lines.push('');
lines.push('This file should be updated when:');
lines.push('- New libraries are added to the monorepo');
lines.push('- Libraries are renamed or moved');
lines.push('- Library purposes significantly change');
lines.push('- Angular or Nx versions are upgraded');
lines.push('');
lines.push('**Automation:** This file is auto-generated using `npm run docs:generate`. Run this command after adding or modifying libraries to keep the documentation up-to-date.');
return lines.join('\n');
}
/**
* Main execution
*/
function main() {
console.log('🔍 Scanning for libraries in libs/ directory...');
// Find all project.json files
const projectFiles = findProjectFiles(LIBS_DIR);
console.log(` Found ${projectFiles.length} project.json files`);
// Parse all project files
const libraries = projectFiles.map(parseProjectFile).filter(Boolean);
console.log(` Parsed ${libraries.length} valid libraries`);
// Load path aliases from tsconfig
console.log('📖 Loading path aliases from tsconfig.base.json...');
const pathAliases = loadPathAliases();
console.log(` Loaded ${Object.keys(pathAliases).length} path aliases`);
// Group by domain
const librariesByDomain = groupByDomain(libraries);
const domainCount = Object.keys(librariesByDomain).length;
console.log(` Organized into ${domainCount} domains`);
// Get version info
const versions = getVersionInfo();
// Generate markdown
console.log('📝 Generating markdown content...');
const markdown = generateMarkdown(librariesByDomain, libraries.length, versions, pathAliases);
// Write to file
console.log(`💾 Writing to ${OUTPUT_FILE}...`);
fs.writeFileSync(OUTPUT_FILE, markdown, 'utf-8');
// Summary
console.log('');
console.log('✅ Library reference documentation generated successfully!');
console.log('');
console.log('📊 Summary:');
console.log(` Total Libraries: ${libraries.length}`);
console.log(` Domains: ${domainCount}`);
console.log(` Output: ${path.relative(process.cwd(), OUTPUT_FILE)}`);
console.log('');
// Show domain breakdown
console.log('📚 Libraries by Domain:');
const sortedDomains = Object.keys(librariesByDomain).sort((a, b) => {
const orderA = DOMAIN_CONFIG[a]?.order || 999;
const orderB = DOMAIN_CONFIG[b]?.order || 999;
return orderA - orderB;
});
for (const domain of sortedDomains) {
const count = librariesByDomain[domain].length;
const domainName = DOMAIN_CONFIG[domain]?.name || domain;
console.log(` ${domainName}: ${count}`);
}
}
// Run if executed directly
if (require.main === module) {
try {
main();
} catch (error) {
console.error('❌ Error:', error.message);
process.exit(1);
}
}
module.exports = { findProjectFiles, parseProjectFile, groupByDomain, generateMarkdown };