n8n/.github/scripts/split-jscpd-report.mjs
Emilia 81cdc278c2
ci: Add jscpd duplication-scan workflow (no-changelog) (#40331)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-07 07:37:01 +00:00

69 lines
2.3 KiB
JavaScript

#!/usr/bin/env node
// Splits a jscpd JSON report's `duplicates` array into N bucket files so a
// caller can download and sample one small file instead of the whole report
// (which can run into tens of MB on a large monorepo). Round-robin, not
// contiguous chunks, so a run of similarly-sized clusters in scan order
// doesn't pile up in one bucket.
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
export function splitDuplicates(duplicates, bucketCount) {
const buckets = Array.from({ length: bucketCount }, () => []);
duplicates.forEach((dup, i) => buckets[i % bucketCount].push(dup));
return buckets;
}
export function writeBuckets(buckets, outputDir, deps = {}) {
const mkdirImpl = deps.mkdir ?? mkdirSync;
const writeImpl = deps.write ?? writeFileSync;
mkdirImpl(outputDir, { recursive: true });
return buckets.map((duplicates, i) => {
const path = join(outputDir, `bucket-${i}.json`);
writeImpl(path, JSON.stringify({ duplicates }));
return path;
});
}
export function parseBucketCount(raw) {
const count = Number(raw ?? 20);
if (!Number.isInteger(count) || count <= 0) {
throw new Error(`BUCKET_COUNT must be a positive integer, got '${raw}'`);
}
return count;
}
export function loadDuplicates(reportPath, deps = {}) {
const readImpl = deps.read ?? readFileSync;
const report = JSON.parse(readImpl(reportPath, 'utf8'));
if (!Array.isArray(report.duplicates)) {
throw new Error('report.duplicates must be an array');
}
return report.duplicates;
}
// Exits non-zero on any failure. The workflow step already runs with
// continue-on-error, so this can't fail the job — it only stops a broken
// split (zero buckets written) from reporting success.
function main() {
const reportPath = process.env.REPORT_PATH;
const outputDir = process.env.OUTPUT_DIR;
if (!reportPath || !outputDir) {
process.stderr.write('split-jscpd-report: REPORT_PATH and OUTPUT_DIR are required\n');
process.exit(1);
}
try {
const bucketCount = parseBucketCount(process.env.BUCKET_COUNT);
const duplicates = loadDuplicates(reportPath);
const buckets = splitDuplicates(duplicates, bucketCount);
writeBuckets(buckets, outputDir);
} catch (err) {
process.stderr.write(`split-jscpd-report: ${err.message}\n`);
process.exit(1);
}
}
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}