ctx-gen
Version:
AI-Enhanced Documentation Generator for Code Understanding
325 lines • 12.9 kB
JavaScript
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