Files
BMAD-METHOD/tools/flattener/aggregate.js

77 lines
2.6 KiB
JavaScript

const fs = require('fs-extra');
const path = require('node:path');
const os = require('node:os');
const { isBinaryFile } = require('./binary.js');
/**
* Aggregate file contents with bounded concurrency.
* Returns text files, binary files (with size), and errors.
* @param {string[]} files absolute file paths
* @param {string} rootDir
* @param {{ text?: string, warn?: (msg: string) => void } | null} spinner
*/
async function aggregateFileContents(files, rootDir, spinner = null) {
const results = {
textFiles: [],
binaryFiles: [],
errors: [],
totalFiles: files.length,
processedFiles: 0,
};
// Automatic concurrency selection based on CPU count and workload size.
// - Base on 2x logical CPUs, clamped to [2, 64]
// - For very small workloads, avoid excessive parallelism
const cpuCount = os.cpus && Array.isArray(os.cpus()) ? os.cpus().length : os.cpus?.length || 4;
let concurrency = Math.min(64, Math.max(2, (Number(cpuCount) || 4) * 2));
if (files.length > 0 && files.length < concurrency) {
concurrency = Math.max(1, Math.min(concurrency, Math.ceil(files.length / 2)));
}
async function processOne(filePath) {
try {
const relativePath = path.relative(rootDir, filePath);
if (spinner) {
spinner.text = `Processing: ${relativePath} (${results.processedFiles + 1}/${results.totalFiles})`;
}
const binary = await isBinaryFile(filePath);
if (binary) {
const { size } = await fs.stat(filePath);
results.binaryFiles.push({ path: relativePath, absolutePath: filePath, size });
} else {
const content = await fs.readFile(filePath, 'utf8');
results.textFiles.push({
path: relativePath,
absolutePath: filePath,
content,
size: content.length,
lines: content.split('\n').length,
});
}
} catch (error) {
const relativePath = path.relative(rootDir, filePath);
const errorInfo = { path: relativePath, absolutePath: filePath, error: error.message };
results.errors.push(errorInfo);
if (spinner) {
spinner.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
} else {
console.warn(`Warning: Could not read file ${relativePath}: ${error.message}`);
}
} finally {
results.processedFiles++;
}
}
for (let index = 0; index < files.length; index += concurrency) {
const slice = files.slice(index, index + concurrency);
await Promise.all(slice.map(processOne));
}
return results;
}
module.exports = {
aggregateFileContents,
};