Skip to content

Commit

Permalink
chore(NODE-5382): backport release automation scripts (#3747)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken authored Jul 3, 2023
1 parent 2d028af commit 4adff37
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Setup
description: 'Installs node, driver dependencies, and builds source'

runs:
using: composite
steps:
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest
shell: bash
- run: npm clean-install
shell: bash
79 changes: 79 additions & 0 deletions .github/scripts/highlights.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @ts-check
import * as process from 'node:process';
import { Octokit } from '@octokit/core';
import { output } from './util.mjs';

const {
GITHUB_TOKEN = '',
PR_LIST = '',
owner = 'mongodb',
repo = 'node-mongodb-native'
} = process.env;
if (GITHUB_TOKEN === '') throw new Error('GITHUB_TOKEN cannot be empty');

const octokit = new Octokit({
auth: GITHUB_TOKEN,
log: {
debug: msg => console.error('Octokit.debug', msg),
info: msg => console.error('Octokit.info', msg),
warn: msg => console.error('Octokit.warn', msg),
error: msg => console.error('Octokit.error', msg)
}
});

const prs = PR_LIST.split(',').map(pr => {
const prNum = Number(pr);
if (Number.isNaN(prNum))
throw Error(`expected PR number list: ${PR_LIST}, offending entry: ${pr}`);
return prNum;
});

/** @param {number} pull_number */
async function getPullRequestContent(pull_number) {
const startIndicator = 'RELEASE_HIGHLIGHT_START -->';
const endIndicator = '<!-- RELEASE_HIGHLIGHT_END';

let body;
try {
const res = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
owner,
repo,
pull_number,
headers: { 'X-GitHub-Api-Version': '2022-11-28' }
});
body = res.data.body;
} catch (error) {
console.log(`Could not get PR ${pull_number}, skipping. ${error.status}`);
return '';
}

if (body == null || !(body.includes(startIndicator) && body.includes(endIndicator))) {
console.log(`PR #${pull_number} has no highlight`);
return '';
}

const start = body.indexOf('### ', body.indexOf(startIndicator));
const end = body.indexOf(endIndicator);
const highlightSection = body.slice(start, end).trim();

console.log(`PR #${pull_number} has a highlight ${highlightSection.length} characters long`);
return highlightSection;
}

/** @param {number[]} prs */
async function pullRequestHighlights(prs) {
const highlights = [];
for (const pr of prs) {
const content = await getPullRequestContent(pr);
highlights.push(content);
}
if (!highlights.length) return '';

highlights.unshift('## Release Notes\n\n');
return highlights.join('\n\n');
}

console.log('List of PRs to collect highlights from:', prs);
const highlights = await pullRequestHighlights(prs);

await output('highlights', JSON.stringify({ highlights }));
28 changes: 28 additions & 0 deletions .github/scripts/pr_list.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-check
import * as url from 'node:url';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { getCurrentHistorySection, output } from './util.mjs';

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const historyFilePath = path.join(__dirname, '..', '..', 'HISTORY.md');

/**
* @param {string} history
* @returns {string[]}
*/
function parsePRList(history) {
const prRegexp = /node-mongodb-native\/issues\/(?<prNum>\d+)\)/iu;
return history
.split('\n')
.map(line => prRegexp.exec(line)?.groups?.prNum ?? '')
.filter(prNum => prNum !== '');
}

const historyContents = await fs.readFile(historyFilePath, { encoding: 'utf8' });

const currentHistorySection = getCurrentHistorySection(historyContents);

const prs = parsePRList(currentHistorySection);

await output('pr_list', prs.join(','));
56 changes: 56 additions & 0 deletions .github/scripts/release_notes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//@ts-check
import * as url from 'node:url';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as process from 'node:process';
import * as semver from 'semver';
import { getCurrentHistorySection, output } from './util.mjs';

const { HIGHLIGHTS = '' } = process.env;
if (HIGHLIGHTS === '') throw new Error('HIGHLIGHTS cannot be empty');

const { highlights } = JSON.parse(HIGHLIGHTS);

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const historyFilePath = path.join(__dirname, '..', '..', 'HISTORY.md');
const packageFilePath = path.join(__dirname, '..', '..', 'package.json');

const historyContents = await fs.readFile(historyFilePath, { encoding: 'utf8' });

const currentHistorySection = getCurrentHistorySection(historyContents);

const version = semver.parse(
JSON.parse(await fs.readFile(packageFilePath, { encoding: 'utf8' })).version
);
if (version == null) throw new Error(`could not create semver from package.json`);

console.log('\n\n--- history entry ---\n\n', currentHistorySection);

const currentHistorySectionLines = currentHistorySection.split('\n');
const header = currentHistorySectionLines[0];
const history = currentHistorySectionLines.slice(1).join('\n').trim();

const releaseNotes = `${header}
The MongoDB Node.js team is pleased to announce version ${version.version} of the \`mongodb\` package!
${highlights}
${history}
## Documentation
* [Reference](https://docs.mongodb.com/drivers/node/current/)
* [API](https://mongodb.github.io/node-mongodb-native/${version.major}.${version.minor}/)
* [Changelog](https://github.com/mongodb/node-mongodb-native/blob/v${version.version}/HISTORY.md)
We invite you to try the \`mongodb\` library immediately, and report any issues to the [NODE project](https://jira.mongodb.org/projects/NODE).
`;

const releaseNotesPath = path.join(process.cwd(), 'release_notes.md');

await fs.writeFile(
releaseNotesPath,
`:seedling: A new release!\n---\n${releaseNotes}\n---\n`,
{ encoding:'utf8' }
);

await output('release_notes_path', releaseNotesPath)
47 changes: 47 additions & 0 deletions .github/scripts/util.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// @ts-check
import * as process from 'node:process';
import * as fs from 'node:fs/promises';

export async function output(key, value) {
const { GITHUB_OUTPUT = '' } = process.env;
const output = `${key}=${value}\n`;
console.log('outputting:', output);

if (GITHUB_OUTPUT.length === 0) {
// This is always defined in Github actions, and if it is not for some reason, tasks that follow will fail.
// For local testing it's convenient to see what scripts would output without requiring the variable to be defined.
console.log('GITHUB_OUTPUT not defined, printing only');
return;
}

const outputFile = await fs.open(GITHUB_OUTPUT, 'a');
await outputFile.appendFile(output, { encoding: 'utf8' });
await outputFile.close();
}

/**
* @param {string} historyContents
* @returns {string}
*/
export function getCurrentHistorySection(historyContents) {
/** Markdown version header */
const VERSION_HEADER = /^#.+\(\d{4}-\d{2}-\d{2}\)$/g;

const historyLines = historyContents.split('\n');

// Search for the line with the first version header, this will be the one we're releasing
const headerLineIndex = historyLines.findIndex(line => VERSION_HEADER.test(line));
if (headerLineIndex < 0) throw new Error('Could not find any version header');

console.log('Found markdown header current release', headerLineIndex, ':', historyLines[headerLineIndex]);

// Search lines starting after the first header, and add back the offset we sliced at
const nextHeaderLineIndex = historyLines
.slice(headerLineIndex + 1)
.findIndex(line => VERSION_HEADER.test(line)) + headerLineIndex + 1;
if (nextHeaderLineIndex < 0) throw new Error(`Could not find previous version header, searched ${headerLineIndex + 1}`);

console.log('Found markdown header previous release', nextHeaderLineIndex, ':', historyLines[nextHeaderLineIndex]);

return historyLines.slice(headerLineIndex, nextHeaderLineIndex).join('\n');
}

0 comments on commit 4adff37

Please sign in to comment.