blob: c92ffce3cdf9ebc08ad5ef384bcbf9bc0e1f1f8d [file] [log] [blame]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
import { styleText } from 'node:util';
import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { temporaryWriteTask } from 'tempy';
import { spawn } from '../../utils/index.js';
import { getSemverBumpPulls } from './semver-pulls.js';
import { getStats } from './stats.js';
import { getChanges } from './changes.js';
import { getNotes, addNotes } from './notes.js';
import {
requireGitHubCLI,
requireWriteAccess,
getLatestTag,
getRefDate,
keypress,
fetchMain,
} from './utils.js';
/**
* Perform the commit and submit a pull request
* @param {string} message The commit message
* @param {boolean} wait Whether to wait for user to update the release notes (used when semver bump is minor or major)
* @param {object} options Commit options
* @param {string} options.branch the branch to commit to
* @param {{title: string, body: string}} options.pr PR options
* @returns {Promise<void>}
*/
const commitAndPR = async (message, wait, { branch, pr }) => {
if (wait) {
console.log('');
console.log(
styleText(
'yellow',
`Please ${styleText('bold', 'modify RELEASE_NOTES.md')} and fill out the ${styleText('bold', 'Notable changes')} section. I'll wait for you.`,
),
);
console.log(styleText('yellow', 'Press any key to continue.'));
await keypress();
console.log('');
}
console.log(styleText('blue', `Preparing ${branch} branch...`));
spawn('git', ['stash']);
spawn('git', ['switch', '-C', branch, 'origin/main']);
spawn('git', ['stash', 'pop']);
spawn('git', [
'add',
'package.json',
'package-lock.json',
'RELEASE_NOTES.md',
'release_notes/',
]);
console.log(styleText('blue', 'Committing changes...'));
await temporaryWriteTask(message, (commitFile) =>
spawn('git', ['commit', '--file', commitFile]),
);
console.log(styleText('blue', `Pushing ${branch} branch...`));
spawn('git', ['push', '--force', '--set-upstream', 'origin', branch]);
console.log(styleText('blue', 'Creating/editing pull request...'));
await temporaryWriteTask(pr.body, (bodyFile) => {
const commonArgs = ['--title', pr.title, '--body-file', bodyFile];
try {
const stdout = spawn('gh', ['pr', 'create', '--draft', ...commonArgs]);
console.log(stdout);
} catch {
const stdout = spawn('gh', ['pr', 'edit', ...commonArgs]);
console.log(stdout);
}
});
spawn('git', ['switch', '-']);
spawn('git', ['branch', '-d', branch]);
};
/**
* Perform the release
* @param {object} options The release options
* @param {boolean} options.dryRun Whether to simulate the release locally
* @returns {Promise<void>}
*/
const main = async ({ dryRun }) => {
if (dryRun) {
console.log(styleText('green', 'Simulating release...'));
}
requireGitHubCLI();
if (!dryRun) {
requireWriteAccess();
}
console.log(styleText('blue', 'Fetching main branch...'));
fetchMain();
console.log(styleText('blue', 'Getting last version...'));
const lastVersion = getLatestTag();
const lastVersionDate = getRefDate(lastVersion);
// Determine what semver part to bump
console.log(
styleText('blue', 'Checking merged PRs to determine semver bump level...'),
);
const semverBumpPulls = getSemverBumpPulls(lastVersionDate);
/** @type {'major' | 'minor' | 'patch'} */
const versionBump = semverBumpPulls.major.length
? 'major'
: semverBumpPulls.minor.length
? 'minor'
: 'patch';
// Perform version bump
spawn('npm', ['version', '--no-git-tag-version', versionBump]);
const thisVersion =
'v' +
JSON.parse(spawn('npm', ['version', '--json']))['@mdn/browser-compat-data'];
console.log(
styleText(
'green',
`Performed ${styleText('bold', versionBump)} bump from ${styleText('bold', lastVersion)} to ${styleText('bold', thisVersion)}`,
),
);
console.log(styleText('blue', 'Getting statistics...'));
const stats = await getStats(lastVersion, thisVersion, lastVersionDate);
console.log(styleText('blue', 'Getting lists of added/removed features...'));
const changes = await getChanges(lastVersionDate);
console.log(styleText('blue', 'Updating release notes...'));
const notes = getNotes(thisVersion, changes, stats, versionBump);
await addNotes(notes, versionBump, lastVersion);
if (!dryRun) {
const title = `Release ${thisVersion}`;
const body = `(This release was generated by the project's release script.)\n\n${notes}`;
await commitAndPR(
title,
!process.env.GITHUB_ACTIONS && versionBump !== 'patch',
{
branch: 'release',
pr: { title, body },
},
);
}
console.log(styleText('blue', styleText('bold', 'Done!')));
};
if (esMain(import.meta)) {
const argv = yargs(hideBin(process.argv))
.command(
'$0',
'Prepares a release by determining changes since the last release, and creating/updating a release PR',
)
.option('dry-run', {
alias: 'n',
describe: "Don't commit, push or PR",
type: 'boolean',
default: false,
})
.parseSync();
await main({ dryRun: argv.dryRun });
}