UNPKG

ctx-gen

Version:

AI-Enhanced Documentation Generator for Code Understanding

325 lines 12.9 kB
import fs from 'fs-extra'; import path from 'path'; import { glob } from 'glob'; import { writeFileWithDir, getFileLanguage } from '../utils/fileUtils.js'; /** * Generate metadata for the project * @param options - CtxGen options */ export async function generateMetadata(options) { // Get all source files const excludePatterns = options.exclude.split(','); const languages = options.languages.split(','); // Create glob patterns for each language const patterns = languages.map(lang => `**/*.${lang}`); // Get all files matching the patterns const files = await glob(patterns, { ignore: excludePatterns, cwd: process.cwd(), absolute: false, }); // Count files by language const languageCounts = {}; for (const file of files) { const language = getFileLanguage(file); if (language) { languageCounts[language] = (languageCounts[language] || 0) + 1; } } // Get package info if available let packageInfo = {}; try { if (await fs.pathExists('package.json')) { packageInfo = await fs.readJSON('package.json'); } } catch (error) { console.error('Error reading package.json:', error); } // Detect frameworks const frameworks = await detectFrameworks(packageInfo); // Detect architecture patterns const architecturePatterns = await detectArchitecturePatterns(files); // Create metadata object const metadata = { projectName: packageInfo.name || path.basename(process.cwd()), version: packageInfo.version || '0.0.0', description: packageInfo.description || '', languages: languageCounts, totalFiles: files.length, generatedAt: new Date().toISOString(), dependencies: packageInfo.dependencies || {}, devDependencies: packageInfo.devDependencies || {}, frameworks: frameworks, architecturePatterns: architecturePatterns, }; // Write metadata to file const metadataPath = path.join(options.docsDir, 'metadata.json'); await writeFileWithDir(metadataPath, JSON.stringify(metadata, null, 2)); // Also create a human-readable version const metadataMarkdownPath = path.join(options.docsDir, 'metadata.md'); const metadataMarkdown = generateMetadataMarkdown(metadata); await writeFileWithDir(metadataMarkdownPath, metadataMarkdown); } /** * Detect frameworks used in the project based on dependencies and file patterns * @param packageInfo - package.json information * @returns Detected frameworks with versions */ async function detectFrameworks(packageInfo) { const frameworks = {}; const dependencies = { ...packageInfo.dependencies, ...packageInfo.devDependencies }; // JavaScript/TypeScript frameworks if (dependencies) { // React if (dependencies.react) { frameworks.react = dependencies.react; // React UI libraries if (dependencies['material-ui'] || dependencies['@mui/core'] || dependencies['@mui/material']) { frameworks['material-ui'] = dependencies['material-ui'] || dependencies['@mui/core'] || dependencies['@mui/material']; } if (dependencies['@chakra-ui/react']) { frameworks['chakra-ui'] = dependencies['@chakra-ui/react']; } if (dependencies['@tailwindcss/react'] || dependencies.tailwindcss) { frameworks.tailwindcss = dependencies['@tailwindcss/react'] || dependencies.tailwindcss; } if (dependencies['styled-components']) { frameworks['styled-components'] = dependencies['styled-components']; } } // Angular if (dependencies['@angular/core']) { frameworks.angular = dependencies['@angular/core']; } // Vue if (dependencies.vue) { frameworks.vue = dependencies.vue; if (dependencies.vuex) { frameworks.vuex = dependencies.vuex; } if (dependencies['vue-router']) { frameworks['vue-router'] = dependencies['vue-router']; } } // Testing frameworks if (dependencies.jest) { frameworks.jest = dependencies.jest; } if (dependencies.mocha) { frameworks.mocha = dependencies.mocha; } if (dependencies.cypress) { frameworks.cypress = dependencies.cypress; } // Backend frameworks if (dependencies.express) { frameworks.express = dependencies.express; } if (dependencies.koa) { frameworks.koa = dependencies.koa; } if (dependencies.nest || dependencies['@nestjs/core']) { frameworks.nest = dependencies.nest || dependencies['@nestjs/core']; } if (dependencies.fastify) { frameworks.fastify = dependencies.fastify; } // GraphQL if (dependencies.graphql) { frameworks.graphql = dependencies.graphql; if (dependencies.apollo || dependencies['apollo-server']) { frameworks.apollo = dependencies.apollo || dependencies['apollo-server']; } } // Mobile frameworks if (dependencies['react-native']) { frameworks['react-native'] = dependencies['react-native']; } // State management if (dependencies.redux) { frameworks.redux = dependencies.redux; if (dependencies['@reduxjs/toolkit']) { frameworks['redux-toolkit'] = dependencies['@reduxjs/toolkit']; } } if (dependencies.mobx) { frameworks.mobx = dependencies.mobx; } if (dependencies.zustand) { frameworks.zustand = dependencies.zustand; } } // Check other package manager files try { // Python if (await fs.pathExists('requirements.txt')) { const requirements = await fs.readFile('requirements.txt', 'utf-8'); if (requirements.includes('django')) { frameworks.django = 'detected (version not determined)'; } if (requirements.includes('flask')) { frameworks.flask = 'detected (version not determined)'; } if (requirements.includes('fastapi')) { frameworks.fastapi = 'detected (version not determined)'; } } // Ruby if (await fs.pathExists('Gemfile')) { const gemfile = await fs.readFile('Gemfile', 'utf-8'); if (gemfile.includes('rails')) { frameworks.rails = 'detected (version not determined)'; } if (gemfile.includes('sinatra')) { frameworks.sinatra = 'detected (version not determined)'; } } // Java if (await fs.pathExists('pom.xml')) { const pom = await fs.readFile('pom.xml', 'utf-8'); if (pom.includes('spring-boot') || pom.includes('spring-core')) { frameworks.spring = 'detected (version not determined)'; } } if (await fs.pathExists('build.gradle')) { const gradle = await fs.readFile('build.gradle', 'utf-8'); if (gradle.includes('spring-boot') || gradle.includes('spring-core')) { frameworks.spring = 'detected (version not determined)'; } } } catch (error) { console.error('Error detecting frameworks from package files:', error); } return frameworks; } /** * Detect architecture patterns used in the project based on file and directory structures * @param files - List of files in the project * @returns Detected architecture patterns */ async function detectArchitecturePatterns(files) { const patterns = []; const directories = new Set(); // Get directories from files files.forEach(file => { const dir = path.dirname(file); directories.add(dir); }); const dirList = Array.from(directories); // MVC pattern detection if (dirList.some(dir => dir.includes('models')) && dirList.some(dir => dir.includes('views')) && dirList.some(dir => dir.includes('controllers'))) { patterns.push('MVC (Model-View-Controller)'); } // Microservices if (dirList.some(dir => dir.includes('services')) && dirList.some(dir => dir.includes('api'))) { patterns.push('Service-oriented'); } // Repository pattern if (dirList.some(dir => dir.includes('repositories') || dir.includes('repos'))) { patterns.push('Repository pattern'); } // Feature-based architecture if (dirList.some(dir => dir.includes('features'))) { patterns.push('Feature-based architecture'); } // Domain-driven design if (dirList.some(dir => dir.includes('domain')) && dirList.some(dir => dir.includes('application'))) { patterns.push('Domain-driven design (DDD)'); } // Clean architecture if (dirList.some(dir => dir.includes('entities') || dir.includes('domain')) && dirList.some(dir => dir.includes('usecases') || dir.includes('application')) && dirList.some(dir => dir.includes('interfaces') || dir.includes('adapters') || dir.includes('infrastructure'))) { patterns.push('Clean architecture'); } // MVVM pattern (common in frontend) if (dirList.some(dir => dir.includes('viewmodels'))) { patterns.push('MVVM (Model-View-ViewModel)'); } // Redux pattern (for React apps) if (files.some(file => file.includes('reducers')) && files.some(file => file.includes('actions'))) { patterns.push('Redux pattern'); } // JAMstack if (files.some(file => file.includes('gatsby') || file.includes('next') || file.includes('nuxt')) && files.some(file => file.includes('content') || file.includes('markdown') || file.includes('assets'))) { patterns.push('JAMstack'); } return patterns; } /** * Generate human-readable markdown from metadata * @param metadata - Project metadata * @returns Markdown string */ function generateMetadataMarkdown(metadata) { let markdown = '# Project Metadata\n\n'; markdown += `**Project Name:** ${metadata.projectName}\n\n`; markdown += `**Version:** ${metadata.version}\n\n`; markdown += `**Description:** ${metadata.description}\n\n`; markdown += `**Generated At:** ${metadata.generatedAt}\n\n`; markdown += '## Language Distribution\n\n'; markdown += `Total Files: ${metadata.totalFiles}\n\n`; markdown += '| Language | File Count | Percentage |\n'; markdown += '|----------|------------|------------|\n'; Object.entries(metadata.languages).forEach(([language, count]) => { const percentage = ((count / metadata.totalFiles) * 100).toFixed(2); markdown += `| ${language} | ${count} | ${percentage}% |\n`; }); // Add frameworks section markdown += '\n## Detected Frameworks\n\n'; if (Object.keys(metadata.frameworks).length > 0) { markdown += '| Framework | Version |\n'; markdown += '|-----------|----------|\n'; Object.entries(metadata.frameworks).forEach(([framework, version]) => { markdown += `| ${framework} | ${version} |\n`; }); } else { markdown += '*No frameworks detected*\n'; } // Add architecture patterns section markdown += '\n## Detected Architecture Patterns\n\n'; if (metadata.architecturePatterns.length > 0) { metadata.architecturePatterns.forEach((pattern) => { markdown += `- ${pattern}\n`; }); } else { markdown += '*No specific architecture patterns detected*\n'; } // Add dependencies section markdown += '\n## Dependencies\n\n'; if (Object.keys(metadata.dependencies).length > 0) { markdown += '| Dependency | Version |\n'; markdown += '|------------|----------|\n'; Object.entries(metadata.dependencies).forEach(([name, version]) => { markdown += `| ${name} | ${version} |\n`; }); } else { markdown += '*No dependencies found*\n'; } markdown += '\n## Dev Dependencies\n\n'; if (Object.keys(metadata.devDependencies).length > 0) { markdown += '| Dependency | Version |\n'; markdown += '|------------|----------|\n'; Object.entries(metadata.devDependencies).forEach(([name, version]) => { markdown += `| ${name} | ${version} |\n`; }); } else { markdown += '*No dev dependencies found*\n'; } return markdown; } //# sourceMappingURL=metadataGenerator.js.map