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