reporemix
Version:
A opiniated repomix tool for Rust and NextJS projects.
274 lines (273 loc) • 13.3 kB
JavaScript
;
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
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();