UNPKG

reporemix

Version:

A opiniated repomix tool for Rust and NextJS projects.

274 lines (273 loc) 13.3 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const fs = __importStar(require("node:fs/promises")); const path = __importStar(require("node:path")); const clipboardy_1 = __importDefault(require("clipboardy")); const tree_sitter_parser_1 = require("../tree-sitter-parser"); const index_1 = require("../transformers/index"); const codeMetrics_1 = require("../transformers/codeMetrics"); const program = new commander_1.Command(); function processFiles(inputPath, options) { return __awaiter(this, void 0, void 0, function* () { // Initialize Tree-sitter yield tree_sitter_parser_1.treeSitterParser.init(); const stats = { totalFiles: 0, processedFiles: 0, transformedFiles: 0, errors: 0, }; // Read directory recursively function walkDirectory(dir_1) { return __awaiter(this, arguments, void 0, function* (dir, basePath = '') { var _a; const files = []; try { const entries = yield fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); const relativePath = path.join(basePath, entry.name); // Skip hidden files if ignore-hidden is true if (options['ignore-hidden'] && entry.name.startsWith('.')) { continue; } // Apply exclude patterns if ((_a = options.exclude) === null || _a === void 0 ? void 0 : _a.some((pattern) => relativePath.includes(pattern))) { continue; } // Apply include patterns (if specified) if (options.include && !options.include.some((pattern) => relativePath.includes(pattern))) { continue; } if (entry.isDirectory()) { // Skip common directories if (['node_modules', '.git', 'dist', 'build', '.next'].includes(entry.name)) { continue; } const subFiles = yield walkDirectory(fullPath, relativePath); files.push(...subFiles); } else if (entry.isFile()) { // Apply file extension filtering based on --only option if (options.only) { const ext = path.extname(entry.name).toLowerCase(); const languageExtensions = { javascript: ['.js', '.jsx', '.mjs', '.cjs'], typescript: ['.ts', '.tsx'], rust: ['.rs', '.toml'], python: ['.py', '.pyi'], css: ['.css', '.scss', '.sass'], html: ['.html', '.htm'], json: ['.json'], }; const allowedExtensions = languageExtensions[options.only.toLowerCase()] || []; if (!allowedExtensions.includes(ext) && !entry.name.toLowerCase().includes(options.only.toLowerCase())) { continue; } } try { const fileStats = yield fs.stat(fullPath); // Check file size limit if (options['max-file-size'] && fileStats.size > options['max-file-size'] * 1024) { if (options.verbose) { console.log(`Skipping large file: ${relativePath} (${fileStats.size} bytes)`); } continue; } const content = yield fs.readFile(fullPath, 'utf-8'); stats.totalFiles++; // Apply transformations try { const transformedContent = yield (0, index_1.applyTransformations)(content, fullPath); if (transformedContent !== content) { stats.transformedFiles++; } files.push({ path: relativePath, content: transformedContent, }); stats.processedFiles++; if (options.verbose) { console.log(`Processed: ${relativePath}`); } } catch (transformError) { console.error(`Transformation failed for ${relativePath}:`, transformError); stats.errors++; // Include original content if transformation fails files.push({ path: relativePath, content: content, }); stats.processedFiles++; } } catch (readError) { console.error(`Failed to read ${relativePath}:`, readError); stats.errors++; } } } } catch (dirError) { console.error(`Failed to read directory ${dir}:`, dirError); stats.errors++; } return files; }); } // Process input path const inputStats = yield fs.stat(inputPath); let files = []; if (inputStats.isDirectory()) { files = yield walkDirectory(inputPath); } else if (inputStats.isFile()) { const content = yield fs.readFile(inputPath, 'utf-8'); const transformedContent = yield (0, index_1.applyTransformations)(content, inputPath); files.push({ path: path.basename(inputPath), content: transformedContent, }); stats.totalFiles = 1; stats.processedFiles = 1; if (transformedContent !== content) { stats.transformedFiles = 1; } } // Generate XML output let xml = '<?xml version="1.0" encoding="UTF-8"?>\n'; xml += '<repository>\n'; xml += ' <summary>\n'; xml += ` <total_files>${stats.totalFiles}</total_files>\n`; xml += ` <processed_files>${stats.processedFiles}</processed_files>\n`; xml += ` <transformed_files>${stats.transformedFiles}</transformed_files>\n`; xml += ` <errors>${stats.errors}</errors>\n`; xml += ' </summary>\n'; xml += ' <files>\n'; for (const file of files) { // Compute metrics for each file const metrics = yield (0, codeMetrics_1.computeMetrics)(file.content, file.path); xml += ` <file path="${escapeXml(file.path)}">\n`; xml += ' <metrics>\n'; xml += ` <complexity>${metrics.complexity}</complexity>\n`; xml += ` <max_depth>${metrics.maxNesting}</max_depth>\n`; xml += ` <comment_ratio>${metrics.commentRatio.toFixed(2)}</comment_ratio>\n`; xml += ` <lines>${file.content.split('\n').length}</lines>\n`; xml += ' </metrics>\n'; xml += ' <content><![CDATA['; xml += file.content; xml += ']]></content>\n'; xml += ' </file>\n'; } xml += ' </files>\n'; xml += '</repository>\n'; if (options.verbose) { console.log('\nProcessing complete:'); console.log(` Total files: ${stats.totalFiles}`); console.log(` Processed files: ${stats.processedFiles}`); console.log(` Transformed files: ${stats.transformedFiles}`); console.log(` Errors: ${stats.errors}`); } return xml; }); } function escapeXml(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } program .name('reporemix') .description('An opinionated repomix tool with Tree-sitter powered transformations') .version('1.0.3'); program .argument('<path>', 'Path to the repository or file to process') .option('-o, --output <file>', 'Output file path') .option('--only <language>', 'Only process files of specific language (javascript, typescript, rust, python, etc.)') .option('-c, --copy', 'Copy output to clipboard') .option('--include <patterns...>', 'Include patterns') .option('--exclude <patterns...>', 'Exclude patterns') .option('--max-file-size <kb>', 'Maximum file size in KB', '1024') .option('--ignore-hidden', 'Ignore hidden files and directories', true) .option('-v, --verbose', 'Verbose output') .action((inputPath, options) => __awaiter(void 0, void 0, void 0, function* () { try { console.log('🔄 Processing repository with Tree-sitter transformations...'); // Convert max-file-size to number if (options['max-file-size']) { options['max-file-size'] = Number.parseInt(options['max-file-size'].toString(), 10); } const result = yield processFiles(inputPath, options); if (options.output) { yield fs.writeFile(options.output, result, 'utf-8'); console.log(`✅ Output written to: ${options.output}`); } if (options.copy) { yield clipboardy_1.default.write(result); console.log('📋 Output copied to clipboard'); } if (!options.output && !options.copy) { console.log(result); } } catch (error) { console.error('❌ Error:', error); process.exit(1); } finally { // Clean up Tree-sitter resources tree_sitter_parser_1.treeSitterParser.cleanup(); } })); program.parse();