101 lines
3.0 KiB
JavaScript
101 lines
3.0 KiB
JavaScript
/**
|
|
* Enriches the latest CHANGELOG.md release section with PR author credits.
|
|
*
|
|
* Squash-merges collapse every commit author to the repo owner, so the real
|
|
* contributor is not in git history. Instead we read the PR number already
|
|
* present in each entry (`(#531)`), resolve the GitHub handle via the `gh`
|
|
* CLI, and append `by [@handle](https://github.com/handle)` to the line.
|
|
*
|
|
* Only the most recent release section (first `## [` heading up to the next
|
|
* one) is processed, so historical sections stay untouched. The pass is
|
|
* idempotent: already-credited lines are skipped. Failures to resolve an
|
|
* author (e.g. `gh` missing or unauthenticated) are non-fatal and the entry
|
|
* is left as-is, so a release is never blocked by this step.
|
|
*
|
|
* Wired into release-it via the `after:bump` hook; can also be run manually:
|
|
* node scripts/enrich-changelog.js
|
|
*/
|
|
|
|
import { execFileSync } from 'child_process';
|
|
import { readFileSync, writeFileSync } from 'fs';
|
|
import { resolve } from 'path';
|
|
import process from 'process';
|
|
|
|
const FILE = resolve(import.meta.dirname, '../CHANGELOG.md');
|
|
|
|
const lines = readFileSync(FILE, 'utf8').split('\n');
|
|
|
|
const sectionStart = lines.findIndex((line) => line.startsWith('## ['));
|
|
if (sectionStart === -1) {
|
|
console.log('enrich-changelog: no release section found, nothing to do.');
|
|
process.exit(0);
|
|
}
|
|
let sectionEnd = lines.findIndex(
|
|
(line, index) => index > sectionStart && line.startsWith('## ['),
|
|
);
|
|
if (sectionEnd === -1) {
|
|
sectionEnd = lines.length;
|
|
}
|
|
|
|
const PR_LINK = /\[#(\d+)\]\([^)]*\/issues\/\d+\)/;
|
|
const COMMIT_LINK = /\(\[[0-9a-f]{7,40}\]\([^)]*\/commit\/[^)]*\)\)/;
|
|
|
|
const authorByPr = new Map();
|
|
|
|
function resolveAuthor(pr) {
|
|
if (authorByPr.has(pr)) {
|
|
return authorByPr.get(pr);
|
|
}
|
|
|
|
let handle = null;
|
|
try {
|
|
handle =
|
|
execFileSync(
|
|
'gh',
|
|
['pr', 'view', pr, '--json', 'author', '--jq', '.author.login'],
|
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
).trim() || null;
|
|
} catch {
|
|
handle = null;
|
|
}
|
|
|
|
authorByPr.set(pr, handle);
|
|
return handle;
|
|
}
|
|
|
|
let enriched = 0;
|
|
for (let index = sectionStart; index < sectionEnd; index++) {
|
|
const line = lines[index];
|
|
|
|
if (!line.startsWith('* ') || / by \[@/.test(line)) {
|
|
continue;
|
|
}
|
|
|
|
const prMatch = line.match(PR_LINK);
|
|
if (!prMatch) {
|
|
continue;
|
|
}
|
|
|
|
const handle = resolveAuthor(prMatch[1]);
|
|
if (!handle) {
|
|
continue;
|
|
}
|
|
|
|
const credit = ` by [@${handle}](https://github.com/${handle})`;
|
|
const commitMatch = line.match(COMMIT_LINK);
|
|
if (commitMatch) {
|
|
const insertAt = commitMatch.index + commitMatch[0].length;
|
|
lines[index] = line.slice(0, insertAt) + credit + line.slice(insertAt);
|
|
} else {
|
|
lines[index] = line + credit;
|
|
}
|
|
|
|
enriched++;
|
|
}
|
|
|
|
if (enriched > 0) {
|
|
writeFileSync(FILE, lines.join('\n'));
|
|
}
|
|
|
|
console.log(`enrich-changelog: ${enriched} ${enriched === 1 ? 'entry' : 'entries'} credited.`);
|