mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
- 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
331 lines
11 KiB
JavaScript
Executable File
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 };
|