diff --git a/package.json b/package.json index 6b7b5fdd..04bc8715 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:codesandbox": "NODE_OPTIONS=\"--max_old_space_size=4096\" lerna run --concurrency 8 --no-private build", "release:version": "lerna version --no-changelog --no-push --no-git-tag-version --no-private", "release:build": "lerna run --concurrency 8 --no-private build --skip-nx-cache", - "release:changelog": "node scripts/releaseChangelog.mjs", + "release:changelog": "node scripts/releaseChangelog.mjs --repo pigment-css --release master", "release:publish": "pnpm publish --recursive --tag latest", "release:publish:dry-run": "pnpm publish --recursive --tag latest --registry=\"http://localhost:4873/\"", "release:tag": "node scripts/releaseTag.mjs", diff --git a/scripts/releaseChangelog.mjs b/scripts/releaseChangelog.mjs index 75e3e184..76bc2029 100644 --- a/scripts/releaseChangelog.mjs +++ b/scripts/releaseChangelog.mjs @@ -1,213 +1 @@ -/* eslint-disable no-restricted-syntax */ -import childProcess from 'child_process'; -import { promisify } from 'util'; -import { Octokit } from '@octokit/rest'; -import chalk from 'chalk'; -import yargs from 'yargs'; - -const exec = promisify(childProcess.exec); - -/** - * @param {string} commitMessage - * @returns {string} The tags in lowercases, ordered ascending and comma separated - */ -function parseTags(commitMessage) { - const tagMatch = commitMessage.match(/^(\[[\w-]+\])+/); - if (tagMatch === null) { - return ''; - } - const [tagsWithBracketDelimiter] = tagMatch; - return tagsWithBracketDelimiter - .match(/([\w-]+)/g) - .map((tag) => { - return tag.toLocaleLowerCase(); - }) - .sort((a, b) => { - return a.localeCompare(b); - }) - .join(','); -} - -/** - * @param {Octokit.ReposCompareCommitsResponseCommitsItem} commitsItem - */ -function filterCommit(commitsItem) { - // TODO: Use labels - // Filter dependency updates - return !commitsItem.commit.message.startsWith('Bump'); -} - -async function findLatestTaggedVersion() { - const { stdout } = await exec( - [ - 'git', - 'describe', - // Earlier tags used lightweight tags + commit. - // We switched to annotated tags later. - '--tags', - '--abbrev=0', - // only include "version-tags" - '--match "v*"', - ].join(' '), - ); - - return stdout.trim(); -} - -async function main(argv) { - const { githubToken, lastRelease: lastReleaseInput, release, repo } = argv; - - if (!githubToken) { - throw new TypeError( - 'Unable to authenticate. Make sure you either call the script with `--githubToken $token` or set `process.env.GITHUB_TOKEN`. The token needs `public_repo` permissions.', - ); - } - const octokit = new Octokit({ - auth: githubToken, - request: { - fetch, - }, - }); - - const latestTaggedVersion = await findLatestTaggedVersion(); - const lastRelease = lastReleaseInput !== undefined ? lastReleaseInput : latestTaggedVersion; - if (lastRelease !== latestTaggedVersion) { - console.warn( - `Creating changelog for ${lastRelease}..${release} when latest tagged version is '${latestTaggedVersion}'.`, - ); - } - - /** - * @type {AsyncIterableIterator>} - */ - const timeline = octokit.paginate.iterator( - octokit.repos.compareCommits.endpoint.merge({ - owner: 'mui', - repo, - base: lastRelease, - head: release, - }), - ); - - /** - * @type {Octokit.ReposCompareCommitsResponseCommitsItem[]} - */ - const commitsItems = []; - for await (const response of timeline) { - const { data: compareCommits } = response; - commitsItems.push(...compareCommits.commits.filter(filterCommit)); - } - - let warnedOnce = false; - - const getAuthor = (commit) => { - if (!commit.author) { - if (!warnedOnce) { - console.warn( - `The author of the commit: ${commit.commit.tree.url} cannot be retrieved. Please add the github username manually.`, - ); - } - warnedOnce = true; - return chalk.red("TODO INSERT AUTHOR'S USERNAME"); - } - - return commit.author?.login; - }; - - const authors = Array.from( - new Set( - commitsItems.map((commitsItem) => { - return getAuthor(commitsItem); - }), - ), - ); - const contributorHandles = authors - .sort((a, b) => a.localeCompare(b)) - .map((author) => `@${author}`) - .join(', '); - - // We don't know when a particular commit was made from the API. - // Only that the commits are ordered by date ASC - const commitsItemsByDateDesc = commitsItems.slice().reverse(); - // Sort by tags ASC, date desc - // Will only consider exact matches of tags so `[Slider]` will not be grouped with `[Slider][Modal]` - commitsItems.sort((a, b) => { - const aTags = parseTags(a.commit.message); - const bTags = parseTags(b.commit.message); - if (aTags === bTags) { - return commitsItemsByDateDesc.indexOf(a) - commitsItemsByDateDesc.indexOf(b); - } - return aTags.localeCompare(bTags); - }); - const changes = commitsItems.map((commitsItem) => { - // Helps changelog author keeping track of order when grouping commits under headings. - // ​ is a zero-width-space that ensures that the content of the listitem is formatted properly - const dateSortMarker = `​`; - const shortMessage = commitsItem.commit.message.split('\n')[0]; - return `- ${dateSortMarker}${shortMessage} @${getAuthor(commitsItem)}`; - }); - const nowFormatted = new Date().toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - }); - - const changelog = ` -## TODO RELEASE NAME - -_${nowFormatted}_ - -A big thanks to the ${ - authors.length - } contributors who made this release possible. Here are some highlights ✨: - -TODO INSERT HIGHLIGHTS - -${changes.join('\n')} - -All contributors of this release in alphabetical order: ${contributorHandles} -`; - - // eslint-disable-next-line no-console -- output of this script - console.log(changelog); -} - -yargs(process.argv.slice(2)) - .command({ - command: '$0', - description: 'Creates a changelog', - builder: (command) => { - return command - .option('lastRelease', { - describe: - 'The release to compare against e.g. `v5.0.0-alpha.23`. Default: The latest tag on the current branch.', - type: 'string', - }) - .option('githubToken', { - default: process.env.GITHUB_TOKEN, - describe: - 'The personal access token to use for authenticating with GitHub. Needs public_repo permissions.', - type: 'string', - }) - .option('release', { - // #default-branch-switch - default: 'master', - describe: 'Ref which we want to release', - type: 'string', - }) - .option('repo', { - default: 'pigment-css', - describe: 'Repository to generate a changelog for', - type: 'string', - }); - }, - handler: main, - }) - .help() - .strict(true) - .version(false) - .parse(); +import '@mui/monorepo/scripts/releaseChangelog.mjs';