#!/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 };