From 569468e8fbfbcebd1318c795710866738f383037 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 23 Dec 2024 23:43:52 +0100 Subject: [PATCH] feat(git-node): add support for auto-fill Notable Changes section Allows PR author (or repo maintainers) to define themselves the Notable Change using a `
` section in the PR description. --- components/git/release.js | 6 ++-- lib/prepare_release.js | 61 ++++++++++++++++++++++++++------- lib/queries/PRNotableChange.gql | 12 +++++++ 3 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 lib/queries/PRNotableChange.gql diff --git a/components/git/release.js b/components/git/release.js index 51ea89a5..c4a410ab 100644 --- a/components/git/release.js +++ b/components/git/release.js @@ -116,8 +116,10 @@ async function main(state, argv, cli, dir) { if (prID) { argv.prid = Number(prID[1]); } + const credentials = await auth({ github: true }); + const request = new Request(credentials); if (state === PREPARE) { - const release = new ReleasePreparation(argv, cli, dir); + const release = new ReleasePreparation(argv, request, cli, dir); await release.prepareLocalBranch(); @@ -137,8 +139,6 @@ async function main(state, argv, cli, dir) { return release.prepare(); } else if (state === PROMOTE) { - const credentials = await auth({ github: true }); - const request = new Request(credentials); const release = new ReleasePromotion(argv, request, cli, dir); cli.startSpinner('Verifying Releaser status'); diff --git a/lib/prepare_release.js b/lib/prepare_release.js index c34a5e80..2d69142e 100644 --- a/lib/prepare_release.js +++ b/lib/prepare_release.js @@ -1,5 +1,7 @@ import path from 'node:path'; import { promises as fs } from 'node:fs'; +import { createInterface } from 'node:readline'; +import { spawn } from 'node:child_process'; import semver from 'semver'; import { replaceInFile } from 'replace-in-file'; @@ -19,8 +21,9 @@ import Session from './session.js'; const isWindows = process.platform === 'win32'; export default class ReleasePreparation extends Session { - constructor(argv, cli, dir) { + constructor(argv, request, cli, dir) { super(cli, dir); + this.req = request; this.isSecurityRelease = argv.security; this.isLTS = false; this.isLTSTransition = argv.startLTS; @@ -176,10 +179,10 @@ export default class ReleasePreparation extends Session { // Check the branch diff to determine if the releaser // wants to backport any more commits before proceeding. cli.startSpinner('Fetching branch-diff'); - const raw = await this.getBranchDiff({ + const raw = runSync(...await this.getBranchDiff({ onlyNotableChanges: false, comparisonBranch: newVersion - }); + })); const diff = raw.split('*'); cli.stopSpinner('Got branch diff'); @@ -414,6 +417,38 @@ export default class ReleasePreparation extends Session { }); } + async fetchNotableChanges(commitListCmd) { + const commitListProcess = spawn(...commitListCmd); + const commitList = createInterface({ input: commitListProcess.stdout }); + const commitPattern = /^\* \\\[\[`([a-f0-9]+)`\]\(https:\/\/github\.com\/([^/]+)\/([^/]+)\/commit\/\1\)\] -(?: \*\*\(SEMVER-MINOR\)\*\*)? (.+) \((.+)\) \[#(\d+)\]\(https:\/\/github\.com\/\2\/\3\/pull\/\6\)$/; + const detailsPattern = /(?:^|\r?\n\r?\n)]+>Notable changes?<\/summary>\r?\n\r?\n(?:#+ ([^\r\n]+)\r?\n)?(.+)\r?\n\r?\n<\/details>(?:\r?\n\r?\n|$)/s; + let notableChangesSections = ''; + let otherNotableChanges = ''; + for await (const line of commitList) { + const { 2: owner, 3: repo, 4: title, 5: author, 6: prId } = commitPattern.exec(line); + const { repository: { pullRequest: { body, labels } } } = await this.req.gql( + 'PRNotableChange', + { owner, repo, prId: Number(prId) }); + const notableChangeMd = + labels.nodes.some(({ name }) => name === 'notable-change') && + detailsPattern.exec(body); + if (notableChangeMd) { + notableChangesSections += + `\n#### ${notableChangeMd[1] || title}\n${notableChangeMd[2]}\nContributed by ${author} in [#${prId}](https://github.com/${owner}/${repo}/pull/${prId}).\n`; + } else { + otherNotableChanges += line + '\n'; + } + } + + if (notableChangesSections.length === 0) { + return otherNotableChanges; + } + if (otherNotableChanges.length !== 0) { + notableChangesSections += `\n#### Other notable changes\n\n${otherNotableChanges}`; + } + return notableChangesSections.slice(1); + } + async updateMainChangelog() { const { date, isLTSTransition, versionComponents, newVersion } = this; @@ -476,7 +511,8 @@ export default class ReleasePreparation extends Session { const data = await fs.readFile(majorChangelogPath, 'utf8'); const arr = data.split('\n'); const allCommits = this.getChangelog(); - const notableChanges = await this.getBranchDiff({ onlyNotableChanges: true }); + const notableChanges = + await this.fetchNotableChanges(await this.getBranchDiff({ onlyNotableChanges: true })); let releaseHeader = `## ${date}, Version ${newVersion}` + ` ${releaseInfo}, @${username}\n`; if (isSecurityRelease) { @@ -612,10 +648,10 @@ export default class ReleasePreparation extends Session { messageBody.push('This is a security release.\n\n'); } - const notableChanges = await this.getBranchDiff({ + const notableChanges = runSync(...await this.getBranchDiff({ onlyNotableChanges: true, format: 'plaintext' - }); + })); messageBody.push('Notable changes:\n\n'); if (isLTSTransition) { messageBody.push(`${getStartLTSBlurb(this)}\n\n`); @@ -735,12 +771,13 @@ export default class ReleasePreparation extends Session { ]; } - const branchDiff = new URL( - '../node_modules/.bin/branch-diff' + (isWindows ? '.cmd' : ''), - import.meta.url - ); - - return runSync(branchDiff, branchDiffOptions); + return [ + path.join( + import.meta.dirname, + '../node_modules/.bin/branch-diff' + (isWindows ? '.cmd' : '') + ), + branchDiffOptions + ]; } async getLastRelease(major) { diff --git a/lib/queries/PRNotableChange.gql b/lib/queries/PRNotableChange.gql new file mode 100644 index 00000000..2c611fad --- /dev/null +++ b/lib/queries/PRNotableChange.gql @@ -0,0 +1,12 @@ +query PR($prId: Int!, $owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $prId) { + body, + labels(first: 100) { + nodes { + name + } + } + } + } +}