mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-28 17:22:01 +08:00
126 lines
4.3 KiB
JavaScript
126 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Roll every results/<pkg>.json into a single tracked Markdown report, so
|
|
// per-package before/after numbers are reviewable as PRs land.
|
|
//
|
|
// Usage:
|
|
// node scripts/typescript-migration/summarize.mjs [--out=<file>]
|
|
//
|
|
// Default output: scripts/typescript-migration/SUMMARY.md (committed — the
|
|
// results/ JSON is gitignored, this rollup is not).
|
|
|
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
const RESULTS_DIR = join(SCRIPT_DIR, 'results');
|
|
|
|
function parseArgs(argv) {
|
|
let out = join(SCRIPT_DIR, 'SUMMARY.md');
|
|
for (const arg of argv) {
|
|
if (arg.startsWith('--out=')) out = resolve(process.cwd(), arg.slice('--out='.length));
|
|
else {
|
|
console.error(`\n✖ Unknown option: ${arg}\n`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
return { out };
|
|
}
|
|
|
|
function fmtMs(ms) {
|
|
return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`;
|
|
}
|
|
|
|
// Baseline-first ordering: a label named "before" is always the baseline and
|
|
// "after" always last, regardless of the order they were recorded in; anything
|
|
// else falls between them by timestamp. Keeps deltas pointing the right way.
|
|
function orderLabels(runs) {
|
|
const rank = (l) => (/^before$/i.test(l) ? -1 : /^after$/i.test(l) ? 1 : 0);
|
|
return Object.keys(runs).sort((a, b) => {
|
|
if (rank(a) !== rank(b)) return rank(a) - rank(b);
|
|
return (runs[a].timestamp ?? '').localeCompare(runs[b].timestamp ?? '');
|
|
});
|
|
}
|
|
|
|
// Render one package's stored runs as the Δ-vs-baseline block.
|
|
function renderBlock(data) {
|
|
const labels = orderLabels(data.runs);
|
|
const base = labels[0];
|
|
const lines = [`=== ${data.package} — median times (Δ vs "${base}") ===`];
|
|
|
|
const tasks = new Set();
|
|
for (const l of labels) for (const t of Object.keys(data.runs[l].tasks)) tasks.add(t);
|
|
|
|
for (const task of tasks) {
|
|
lines.push('', `${task}:`);
|
|
const baseMedian = data.runs[base].tasks[task]?.median;
|
|
for (const label of labels) {
|
|
const stat = data.runs[label].tasks[task];
|
|
if (!stat) {
|
|
lines.push(` ${label.padEnd(16)} (not measured)`);
|
|
continue;
|
|
}
|
|
let delta = '';
|
|
if (label !== base && baseMedian != null) {
|
|
const diff = stat.median - baseMedian;
|
|
const pct = ((diff / baseMedian) * 100).toFixed(1);
|
|
const sign = diff <= 0 ? '' : '+';
|
|
delta = ` ${sign}${fmtMs(diff)} (${sign}${pct}%)`;
|
|
}
|
|
lines.push(` ${label.padEnd(16)} ${fmtMs(stat.median).padStart(9)}${delta}`);
|
|
}
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// One "before → after" delta per task for the overview table (first vs last label).
|
|
function overviewDelta(data, task) {
|
|
const labels = orderLabels(data.runs);
|
|
const first = data.runs[labels[0]].tasks[task]?.median;
|
|
const last = data.runs[labels[labels.length - 1]].tasks[task]?.median;
|
|
if (labels.length < 2 || first == null || last == null) return '—';
|
|
const pct = (((last - first) / first) * 100).toFixed(1);
|
|
return `${last - first <= 0 ? '' : '+'}${pct}%`;
|
|
}
|
|
|
|
function main() {
|
|
const { out } = parseArgs(process.argv.slice(2));
|
|
|
|
if (!existsSync(RESULTS_DIR)) {
|
|
console.error(`\n✖ No results directory at ${RESULTS_DIR}. Run benchmark.mjs first.\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const files = readdirSync(RESULTS_DIR)
|
|
.filter((f) => f.endsWith('.json'))
|
|
.map((f) => JSON.parse(readFileSync(join(RESULTS_DIR, f), 'utf8')))
|
|
.sort((a, b) => a.package.localeCompare(b.package));
|
|
|
|
if (files.length === 0) {
|
|
console.error('\n✖ No result files found. Run benchmark.mjs first.\n');
|
|
process.exit(1);
|
|
}
|
|
|
|
const md = [];
|
|
md.push('# TypeScript 6 → 7 migration benchmarks', '');
|
|
md.push(`Generated ${new Date().toISOString()} from \`scripts/typescript-migration/results/\`.`, '');
|
|
|
|
// Overview table: median Δ (first → last label) per package.
|
|
md.push('| Package | typecheck Δ | build Δ |', '| --- | --- | --- |');
|
|
for (const data of files) {
|
|
md.push(`| \`${data.package}\` | ${overviewDelta(data, 'typecheck')} | ${overviewDelta(data, 'build')} |`);
|
|
}
|
|
md.push('');
|
|
|
|
// Per-package detail blocks in the benchmark's own format.
|
|
for (const data of files) {
|
|
md.push('```', renderBlock(data), '```', '');
|
|
}
|
|
|
|
mkdirSync(dirname(out), { recursive: true });
|
|
writeFileSync(out, `${md.join('\n')}\n`);
|
|
console.log(`✔ Wrote ${out} (${files.length} package${files.length === 1 ? '' : 's'})`);
|
|
}
|
|
|
|
main();
|