From c481088b640c80e3ad6274978022bf64d97a1aaa Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 15 Jun 2022 16:27:33 +0800 Subject: [PATCH 01/10] Move repo-tool.js to utils directory --- .../actions/update-version-tags/src/update-version-tags.js | 2 +- .../{actions/update-version-tags/src => utils}/repo-tool.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/js/github-actions/{actions/update-version-tags/src => utils}/repo-tool.js (100%) diff --git a/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js b/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js index 1d1130d0..fa440e1b 100644 --- a/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js +++ b/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js @@ -7,7 +7,7 @@ import core from '@actions/core'; /** * Internal dependencies */ -import RepoTool from './repo-tool.js'; +import RepoTool from '../../../utils/repo-tool.js'; import parseVersion from './parse-version.js'; async function updateVersionTags() { diff --git a/packages/js/github-actions/actions/update-version-tags/src/repo-tool.js b/packages/js/github-actions/utils/repo-tool.js similarity index 100% rename from packages/js/github-actions/actions/update-version-tags/src/repo-tool.js rename to packages/js/github-actions/utils/repo-tool.js From e22883c070a7072ca32194e7c4628865693ede1a Mon Sep 17 00:00:00 2001 From: Eason Su Date: Thu, 16 Jun 2022 16:38:25 +0800 Subject: [PATCH 02/10] Move action error handler to utils --- .../src/update-version-tags.js | 23 +++---------------- .../utils/handle-action-errors.js | 22 ++++++++++++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 packages/js/github-actions/utils/handle-action-errors.js diff --git a/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js b/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js index fa440e1b..0beac88b 100644 --- a/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js +++ b/packages/js/github-actions/actions/update-version-tags/src/update-version-tags.js @@ -9,6 +9,7 @@ import core from '@actions/core'; */ import RepoTool from '../../../utils/repo-tool.js'; import parseVersion from './parse-version.js'; +import handleActionErrors from '../../../utils/handle-action-errors.js'; async function updateVersionTags() { // Prepare parameters @@ -57,23 +58,5 @@ async function updateVersionTags() { // Start running this action. updateVersionTags() - .then( () => { - core.info( 'Finish updating the version tags.' ); - } ) - .catch( ( e ) => { - let message; - - if ( e instanceof Error ) { - message = `${ e.name } - ${ e.message }`; - - if ( e.stack ) { - core.startGroup( 'Call stack' ); - core.info( e.stack ); - core.endGroup(); - } - } else { - message = JSON.stringify( e, null, 2 ); - } - - core.setFailed( `Action failed with error: ${ message }` ); - } ); + .then( () => core.info( 'Finish updating the version tags.' ) ) + .catch( handleActionErrors ); diff --git a/packages/js/github-actions/utils/handle-action-errors.js b/packages/js/github-actions/utils/handle-action-errors.js new file mode 100644 index 00000000..882c273a --- /dev/null +++ b/packages/js/github-actions/utils/handle-action-errors.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import core from '@actions/core'; + +export default function handleActionErrors( e ) { + let message; + + if ( e instanceof Error ) { + message = `${ e.name } - ${ e.message }`; + + if ( e.stack ) { + core.startGroup( 'Call stack' ); + core.info( e.stack ); + core.endGroup(); + } + } else { + message = JSON.stringify( e, null, 2 ); + } + + core.setFailed( `Action failed with error: ${ message }` ); +} From 0d0da16070e7a8da3376417cbb0b3ae354d01f41 Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 15 Jun 2022 16:28:20 +0800 Subject: [PATCH 03/10] Add generateReleaseNotes method to RepoTool class --- packages/js/github-actions/utils/repo-tool.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/js/github-actions/utils/repo-tool.js b/packages/js/github-actions/utils/repo-tool.js index 3cc7a388..520c5cdc 100644 --- a/packages/js/github-actions/utils/repo-tool.js +++ b/packages/js/github-actions/utils/repo-tool.js @@ -17,6 +17,7 @@ export default class RepoTool { this.context = context; this.octokit = github.getOctokit( token ); this.git = this.octokit.rest.git; + this.repos = this.octokit.rest.repos; } /** @@ -78,4 +79,30 @@ export default class RepoTool { sha, } ); } + + /** + * Generate release notes content via GitHub. + * + * @param {string} tag The tag name for the release. This can be an existing tag or a new one. + * @param {string} [targetCommitish] Specifies the commitish value that will be the target for the release's tag. Required if the supplied `tag` does not reference an existing tag. + * @param {string} [previousTag] The name of the previous tag to use as the starting point for the release notes. + * @param {string} [configPath] Specifies a file path in this repository containing the config used for generating the release notes. If unspecified, the fallback is .github/release.yml and then the default config of GitHub. + * @return {Promise} Promise instance represents the result of this operation. + */ + async generateReleaseNotes( + tag, + targetCommitish, + previousTag, + configPath + ) { + const response = await this.repos.generateReleaseNotes( { + ...this.context.repo, + tag_name: tag, + target_commitish: targetCommitish, + previous_tag_name: previousTag, + configuration_file_path: configPath, + } ); + + return response.data; + } } From a0e14408dd6acd47cbe2efb725cab634ca3fecac Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 15 Jun 2022 16:29:56 +0800 Subject: [PATCH 04/10] Add a new util getPackageSettings --- .../utils/get-package-settings.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/js/github-actions/utils/get-package-settings.js diff --git a/packages/js/github-actions/utils/get-package-settings.js b/packages/js/github-actions/utils/get-package-settings.js new file mode 100644 index 00000000..7a0173c2 --- /dev/null +++ b/packages/js/github-actions/utils/get-package-settings.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import path from 'path'; +import fs from 'fs'; + +/** + * Gets the settings of package.json + * + * @param {string} workspace The working directory on the job runner in GitHub actions for steps. Usually, it's an environment variable that can be accessed by the variable name `GITHUB_WORKSPACE`. + * @param {string} packageDir The path to the directory containing the package.json. + * @return {Object} The parsed JSON content of package settings. + * @throws {Error} Will throw an error if the package.json does not exist. + */ +export default function getPackageSettings( workspace, packageDir ) { + const filePath = path.join( workspace, packageDir, 'package.json' ); + + if ( ! fs.existsSync( filePath ) ) { + throw new Error( + `The specified package.json file at: ${ filePath } does not exist.` + ); + } + + const content = fs.readFileSync( filePath, 'utf8' ); + return JSON.parse( content ); +} From a4b74c741e4c557c084045d0aaf57192e09c47ba Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 15 Jun 2022 16:31:19 +0800 Subject: [PATCH 05/10] Add a new custom GitHub action for getting release notes via a GitHub API --- .../actions/get-release-notes/action.yml | 58 ++++++++++++++ .../src/get-release-notes.js | 79 +++++++++++++++++++ packages/js/github-actions/package.json | 4 +- 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/js/github-actions/actions/get-release-notes/action.yml create mode 100644 packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js diff --git a/packages/js/github-actions/actions/get-release-notes/action.yml b/packages/js/github-actions/actions/get-release-notes/action.yml new file mode 100644 index 00000000..19110eb2 --- /dev/null +++ b/packages/js/github-actions/actions/get-release-notes/action.yml @@ -0,0 +1,58 @@ +name: Get release notes +description: Get release notes via GitHub, infer the next version and tag. + +inputs: + repo-token: + description: The GITHUB_TOKEN secret. + required: true + + package-dir: + description: The directory path of package.json. + default: "" + + tag-template: + description: > + The template to generate a tag name with a version. + For example, with a version 1.4.7, + "v{version}" gets 'v1.4.7' and "my-tool-v{version}" gets 'my-tool-v1.4.7'. + default: "{version}" + + tag: + description: The tag name for the release. This can be an existing tag or leave it empty by default. + default: "" + + target-commitish: + description: > + Specifies the commitish value that will be the target for the release's tag. + If the input tag does not reference an existing tag, the default empty value + will be replaced with the branch that triggers this action. If the input tag + already exists, leave it empty by default. + default: "" + + previous-tag: + description: > + The name of the previous tag to use as the starting point for the release notes. + Use to manually specify the range for the set of changes considered as part of this + release. If this value is empty, this action will use the version in package.json + to generate a version tag name with the input tag-template, and check whether that + tag exists to be used as the previous tag. + default: "" + + config-path: + description: > + Specifies a path to a file in the repository containing the config used for + generating the release notes. If unspecified, the config file located in the + repository at '.github/release.yml' will be used. If that is not present, + the default config of GitHub will be used. + default: "" + +outputs: + release-notes: + description: The content of release notes. + + release-changelog: + description: The changelog part in release notes. + +runs: + using: node16 + main: get-release-notes.js diff --git a/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js new file mode 100644 index 00000000..cc428649 --- /dev/null +++ b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import github from '@actions/github'; +import core from '@actions/core'; +import semver from 'semver'; + +/** + * Internal dependencies + */ +import getPackageSettings from '../../../utils/get-package-settings.js'; +import RepoTool from '../../../utils/repo-tool.js'; +import handleActionErrors from '../../../utils/handle-action-errors.js'; + +const TMP_TAG = 'tmp--github-action-get-release-notes'; + +function compositeVersionTag( version ) { + const template = core.getInput( 'tag-template' ); + return template.replace( '{version}', version ); +} + +function parseChangelog( notesContent ) { + const matched = notesContent.match( + /## What's Changed\n([\d\D]+?)(?=\n\n)/i + ); + if ( matched ) { + return matched[ 1 ]; + } + return ''; +} + +async function getReleaseNotes() { + // Prepare parameters + const { context } = github; + const workspace = process.env.GITHUB_WORKSPACE; + const token = core.getInput( 'repo-token' ); + const packageDir = core.getInput( 'package-dir' ); + + const tag = core.getInput( 'tag' ) || TMP_TAG; + const targetCommitish = + core.getInput( 'target-commitish' ) || + context.ref.replace( 'refs/heads/', '' ); + let previousTag = core.getInput( 'previous-tag' ); + const configPath = core.getInput( 'config-path' ); + + // Resolve the previous tag + const repoTool = new RepoTool( token, context ); + const { version } = getPackageSettings( workspace, packageDir ); + + if ( ! previousTag ) { + const versionTag = compositeVersionTag( version ); + + if ( await repoTool.hasTag( versionTag ) ) { + previousTag = versionTag; + } + } + + // Log info + core.info( `Resolved target commitish: ${ targetCommitish }` ); + core.info( `Resolved previous tag: ${ previousTag }` ); + + // Fetch release notes + const { body } = await repoTool.generateReleaseNotes( + tag, + targetCommitish, + previousTag, + configPath + ); + const changelog = parseChangelog( body ); + + // Output results + core.setOutput( 'release-notes', body ); + core.setOutput( 'release-changelog', changelog ); +} + +// Start running this action. +getReleaseNotes() + .then( () => core.info( 'Finish getting the release notes.' ) ) + .catch( handleActionErrors ); diff --git a/packages/js/github-actions/package.json b/packages/js/github-actions/package.json index 0aaa2762..f01e0833 100644 --- a/packages/js/github-actions/package.json +++ b/packages/js/github-actions/package.json @@ -4,7 +4,9 @@ "description": "GitHub JavaScript actions for a WooCommerce plugin repo by Grow Team.", "type": "module", "scripts": { - "build": "npm run build:update-version-tags", + "build": "npm run build:get-release-notes && npm run build:update-version-tags", + "build:get-release-notes": "npx @vercel/ncc build ./actions/get-release-notes/src/get-release-notes.js -o ./actions/get-release-notes", + "postbuild:get-release-notes": "mv ./actions/get-release-notes/index.js ./actions/get-release-notes/get-release-notes.js", "build:update-version-tags": "npx @vercel/ncc build ./actions/update-version-tags/src/update-version-tags.js -o ./actions/update-version-tags", "postbuild:update-version-tags": "mv ./actions/update-version-tags/index.js ./actions/update-version-tags/update-version-tags.js" }, From b343099b9c02eb1a63e41e11731f5f9cb079c86e Mon Sep 17 00:00:00 2001 From: Eason Su Date: Thu, 16 Jun 2022 16:31:39 +0800 Subject: [PATCH 06/10] Infer the next version and tag --- .../actions/get-release-notes/action.yml | 29 ++++++++---- .../src/get-release-notes.js | 46 +++++++++++++----- .../src/match-version-level.js | 47 +++++++++++++++++++ 3 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 packages/js/github-actions/actions/get-release-notes/src/match-version-level.js diff --git a/packages/js/github-actions/actions/get-release-notes/action.yml b/packages/js/github-actions/actions/get-release-notes/action.yml index 19110eb2..1fd6d887 100644 --- a/packages/js/github-actions/actions/get-release-notes/action.yml +++ b/packages/js/github-actions/actions/get-release-notes/action.yml @@ -7,7 +7,11 @@ inputs: required: true package-dir: - description: The directory path of package.json. + description: > + The directory path of package.json. This action uses the version in package.json + to generate a version tag name with the input tag-template, and check whether that + tag exists to be used as the previous tag. The previous tag is the starting point of + a range for getting the release notes. default: "" tag-template: @@ -29,15 +33,6 @@ inputs: already exists, leave it empty by default. default: "" - previous-tag: - description: > - The name of the previous tag to use as the starting point for the release notes. - Use to manually specify the range for the set of changes considered as part of this - release. If this value is empty, this action will use the version in package.json - to generate a version tag name with the input tag-template, and check whether that - tag exists to be used as the previous tag. - default: "" - config-path: description: > Specifies a path to a file in the repository containing the config used for @@ -46,6 +41,14 @@ inputs: the default config of GitHub will be used. default: "" + major-keywords: + description: Comma-separated keywords for matching the major level. For example, "breaking, major changes". + default: "breaking" + + minor-keywords: + description: Comma-separated keywords for matching the minor level. For example, "new features, improvements". + default: "feature, enhancement" + outputs: release-notes: description: The content of release notes. @@ -53,6 +56,12 @@ outputs: release-changelog: description: The changelog part in release notes. + next-version: + description: The next version inferred via the release notes. For example, 2.0.0, 1.5.0 or 1.4.8. + + next-tag: + description: The next tag name inferred via the release notes. For example, 2.0.0, v1.5.0 or my-tool-v1.4.8. + runs: using: node16 main: get-release-notes.js diff --git a/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js index cc428649..b22deb39 100644 --- a/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js +++ b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js @@ -11,6 +11,7 @@ import semver from 'semver'; import getPackageSettings from '../../../utils/get-package-settings.js'; import RepoTool from '../../../utils/repo-tool.js'; import handleActionErrors from '../../../utils/handle-action-errors.js'; +import matchVersionLevel from './match-version-level.js'; const TMP_TAG = 'tmp--github-action-get-release-notes'; @@ -40,24 +41,28 @@ async function getReleaseNotes() { const targetCommitish = core.getInput( 'target-commitish' ) || context.ref.replace( 'refs/heads/', '' ); - let previousTag = core.getInput( 'previous-tag' ); const configPath = core.getInput( 'config-path' ); + const majorKeywords = core.getInput( 'major-keywords' ); + const minorKeywords = core.getInput( 'minor-keywords' ); + // Resolve the previous tag const repoTool = new RepoTool( token, context ); const { version } = getPackageSettings( workspace, packageDir ); - - if ( ! previousTag ) { - const versionTag = compositeVersionTag( version ); - - if ( await repoTool.hasTag( versionTag ) ) { - previousTag = versionTag; - } - } + const versionTag = compositeVersionTag( version ); + const previousTag = ( await repoTool.hasTag( versionTag ) ) + ? versionTag + : ''; // Log info core.info( `Resolved target commitish: ${ targetCommitish }` ); - core.info( `Resolved previous tag: ${ previousTag }` ); + if ( previousTag ) { + core.info( `Resolved previous tag: ${ previousTag }` ); + } else { + core.info( + `The previous tag ${ versionTag } does not exist. The inferred version number will be the same as package.json.` + ); + } // Fetch release notes const { body } = await repoTool.generateReleaseNotes( @@ -68,9 +73,28 @@ async function getReleaseNotes() { ); const changelog = parseChangelog( body ); + let notesContent = body; + let nextVersion = ''; + let nextTag = ''; + + // Infer the next version and tag. + if ( semver.valid( version ) ) { + const level = matchVersionLevel( body, majorKeywords, minorKeywords ); + nextVersion = previousTag ? semver.inc( version, level ) : version; + nextTag = compositeVersionTag( nextVersion ); + + if ( notesContent.endsWith( TMP_TAG ) ) { + notesContent = notesContent.replace( TMP_TAG, nextTag ); + } + } else { + core.warning( `The ${ version } is not a valid semantic versioning.` ); + } + // Output results - core.setOutput( 'release-notes', body ); + core.setOutput( 'release-notes', notesContent ); core.setOutput( 'release-changelog', changelog ); + core.setOutput( 'next-version', nextVersion ); + core.setOutput( 'next-tag', nextTag ); } // Start running this action. diff --git a/packages/js/github-actions/actions/get-release-notes/src/match-version-level.js b/packages/js/github-actions/actions/get-release-notes/src/match-version-level.js new file mode 100644 index 00000000..58adb6d3 --- /dev/null +++ b/packages/js/github-actions/actions/get-release-notes/src/match-version-level.js @@ -0,0 +1,47 @@ +/** + * Separates keywords by commas, trims for each keyword, and clears the empty items. + * + * @param {string} keywords The string of version keywords that separated by commas. + * @return {Array} The array of parsed keywords. + */ +function parseKeywords( keywords ) { + return keywords.split( /\s*,\s*/ ).filter( Boolean ); +} + +/** + * Matches a version level with keywords regardless of case. + * The version pattern used is semantic versioning. + * + * The matching priority is 'major', 'minor', and 'patch'. + * Once any of headings (`###`) in release notes has a single word + * starting with the same keyword, the match level is returned. + * If both major and minor cannot be matched, 'patch' will be returned. + * + * @param {string} notesContent The content of release notes got from GitHub. + * @param {string} majorKeywords The string of major level keywords that separated by commas. For example, 'breaking, brand new'. + * @param {string} minorKeywords The string of minor level keywords that separated by commas. For example, 'feature, enhancement'. + * @return {'major'|'minor'|'patch'} The matched version level. + */ +export default function matchVersionLevel( + notesContent, + majorKeywords, + minorKeywords +) { + const headings = []; + notesContent.replace( /^### (.+)/gm, ( _, el ) => headings.push( el ) ); + + const headingContent = headings.join( '\n' ); + const levels = [ + [ 'major', parseKeywords( majorKeywords ) ], + [ 'minor', parseKeywords( minorKeywords ) ], + [ 'patch', [ '|' ] ], // Match all includes empty string. + ]; + + return levels + .find( ( [ , keywords ] ) => { + const pattern = keywords.join( '|' ).replace( /^|\|/g, '$&\\b' ); + const regex = new RegExp( pattern, 'i' ); + return regex.test( headingContent ); + } ) + .shift(); +} From ec90f378c85df41a7a2344fc746eed2a4b2b114b Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 15 Jun 2022 16:32:17 +0800 Subject: [PATCH 07/10] Add a new workflow to prepare a new release for GitHub actions package --- .../github-actions-prepare-release.yml | 81 +++++++++++++++++++ packages/js/github-actions/CHANGELOG.md | 1 + .../github-actions/release-notes-config.yml | 32 ++++++++ 3 files changed, 114 insertions(+) create mode 100644 .github/workflows/github-actions-prepare-release.yml create mode 100644 packages/js/github-actions/CHANGELOG.md create mode 100644 packages/js/github-actions/release-notes-config.yml diff --git a/.github/workflows/github-actions-prepare-release.yml b/.github/workflows/github-actions-prepare-release.yml new file mode 100644 index 00000000..ea62b384 --- /dev/null +++ b/.github/workflows/github-actions-prepare-release.yml @@ -0,0 +1,81 @@ +name: GitHub Actions - Prepare New Release + +on: + push: + branches: + - release/actions + +jobs: + CheckCreatedBranch: + name: Check Created Branch + runs-on: ubuntu-latest + steps: + - name: Check created release branch + uses: actions/github-script@v6 + with: + script: | + if ( ! context.payload.created ) { + await github.rest.actions.cancelWorkflowRun( { + ...context.repo, + run_id: context.runId, + } ); + } + + PrepareRelease: + name: Prepare Release + runs-on: ubuntu-latest + needs: CheckCreatedBranch + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Prepare node + uses: ./packages/js/github-actions/actions/prepare-node + with: + node-version: 14 + cache-dependency-path: ./packages/js/github-actions/package-lock.json + install-deps: "no" + + # The checkout revision of this repo itself doesn't have the built actions, + # so it needs to build them before using it locally. + - name: Build actions + run: | + cd ./packages/js/github-actions + npm ci --ignore-scripts + npm run build + cd - + + - name: Get release notes + id: get-notes + uses: ./packages/js/github-actions/actions/get-release-notes + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + package-dir: packages/js/github-actions + config-path: packages/js/github-actions/release-notes-config.yml + tag-template: "actions-v{version}" + minor-keywords: feature, update, enhancement + + - name: Prepare release commits + # Use the github-actions bot account to commit. + # https://api.github.com/users/github-actions%5Bbot%5D + run: | + cd ./packages/js/github-actions + TODAY=$(date '+%Y-%m-%d') + NEXT_VER="${{ steps.get-notes.outputs.next-version }}" + CHANGELOG="${{ steps.get-notes.outputs.release-changelog }}" + CHANGELOG=$(echo "$CHANGELOG" | sed -E 's/\.? by @[^ ]+ in (https:\/\/github\.com\/.+)/. (\1)/') + sed -i "/# Changelog/r"<( + printf "\n## ${TODAY} (${NEXT_VER})\n${CHANGELOG}\n" + ) CHANGELOG.md + jq ".version=\"${NEXT_VER}\"" package.json > package.json.tmp + mv package.json.tmp package.json + jq ".version=\"${NEXT_VER}\"" package-lock.json > package-lock.json.tmp + mv package-lock.json.tmp package-lock.json + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add CHANGELOG.md + git add package.json + git add package-lock.json + cd - + git commit -q -m "Update changelog and package version for the ${{ steps.get-notes.outputs.next-tag }} release of GitHub actions." + git push diff --git a/packages/js/github-actions/CHANGELOG.md b/packages/js/github-actions/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/packages/js/github-actions/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/packages/js/github-actions/release-notes-config.yml b/packages/js/github-actions/release-notes-config.yml new file mode 100644 index 00000000..50974421 --- /dev/null +++ b/packages/js/github-actions/release-notes-config.yml @@ -0,0 +1,32 @@ +changelog: + categories: + # Major level + - title: Breaking Changes 🚨 + labels: + - "[actions] changelog: breaking" + + # Minor level + - title: New Features 🎉 + labels: + - "[actions] changelog: feature" + + - title: Updated ✨ + labels: + - "[actions] changelog: update" + + - title: Enhancements ⚡️ + labels: + - "[actions] changelog: enhancement" + + # Patch level + - title: Bug Fixes 🐛 + labels: + - "[actions] changelog: fix" + + - title: Tweaked 🔧 + labels: + - "[actions] changelog: tweak" + + - title: Documentation 📚 + labels: + - "[actions] changelog: docs" From 6c4f1acb00fd8782388446fb9cbcbfd5198555f4 Mon Sep 17 00:00:00 2001 From: Eason Su Date: Fri, 17 Jun 2022 13:13:25 +0800 Subject: [PATCH 08/10] Update docs for get-release-notes action --- packages/js/github-actions/README.md | 19 +- .../actions/get-release-notes/README.md | 163 ++++++++++++++++++ 2 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 packages/js/github-actions/actions/get-release-notes/README.md diff --git a/packages/js/github-actions/README.md b/packages/js/github-actions/README.md index 7dafffd1..f35cb260 100644 --- a/packages/js/github-actions/README.md +++ b/packages/js/github-actions/README.md @@ -6,6 +6,7 @@ Custom GitHub actions that help to composite GitHub workflows across the repos m ## Actions list +- [`get-release-notes`](actions/get-release-notes) - Get release notes via GitHub, infer the next version and tag - [`prepare-mysql`](actions/prepare-mysql) - Enable MySQL, handle authentication compatibility - [`prepare-node`](actions/prepare-node) - Set up Node.js with a specific version, load npm cache, install Node dependencies - [`prepare-php`](actions/prepare-php) - Set up PHP with a specific version and tools, load Composer cache, install Composer dependencies @@ -94,32 +95,36 @@ gitGraph branch release/actions order: 1 commit id: "Changelog" commit id: "Bump version" - checkout trunk - merge release/actions branch tmp/release-build order: 0 commit id: "Release build" type: HIGHLIGHT tag: "actions-v1.2.3" checkout trunk - commit + merge release/actions + ``` ## Release ### Official release -1. Find the latest version tag of GitHub actions in this repo. For example, `actions-v1.4.7`. -1. Create a new release with a new version tag that increases numerically in the format `actions-vX.Y.Z`. +1. Create the specific branch `release/actions` onto the target revision on `trunk` branch. +1. The ["GitHub Actions - Prepare New Release" workflow](https://github.com/woocommerce/grow/actions/workflows/github-actions-prepare-release.yml) will continue to prepend changelog to [CHANGELOG.md](CHANGELOG.md), update versions to [package.json](package.json) and [package-lock.json](package-lock.json), and then commit the changes to `release/actions` branch. +1. Create a release PR from `release/actions` branch with `trunk` as the base branch. +1. Check if the new changelog content and updated version are correct. - For a patch version like fixing bugs, increases the Z number. For example, `actions-v1.4.8`. - For a minor version like adding new features, increases the Y number and reset the Z to 0. For example, `actions-v1.5.0`. - For a major version like having incompatible changes, increases the X number and reset the Y and Z to 0. For example, `actions-v2.0.0`. +1. Create a new release with a new version tag. +1. Check if the ["GitHub Actions - Release" workflow](https://github.com/woocommerce/grow/actions/workflows/github-actions-release.yml) is run successfully. 1. After publishing the new release, the "GitHub Actions - Release" workflow of the GitHub Actions in this repo will continue the creating and committing the release build. And then update the references of the corresponding major and minor version tags onto the new release. For example: - When the new release version is `actions-v1.4.8`, it should update the references of `actions-v1` and `actions-v1.4` onto `actions-v1.4.8`. - When the new release version is `actions-v1.5.0`, it should update the reference of `actions-v1` and create `actions-v1.5` tag onto `actions-v1.5.0`. - When the new release version is `actions-v2.0.0`, it should create `actions-v2` and `actions-v2.0` tags onto `actions-v2.0.0`. -1. Check if the ["GitHub Actions - Release" workflow](https://github.com/woocommerce/grow/actions/workflows/github-actions-release.yml) is run successfully. +1. Merge the release PR. ### Testing release -1. Basically use the same processing as the [Official release](#official-release) above, :warning: **but the format of version tag should be `actions-vX.Y.Z-pre`**. +1. Create a new release with a **prerelease version tag**. For example `actions-vX.Y.Z-pre`. +1. Check if the ["GitHub Actions - Release" workflow](https://github.com/woocommerce/grow/actions/workflows/github-actions-release.yml) is run successfully. 1. Delete the testing releases and tags once they are no longer in use.

diff --git a/packages/js/github-actions/actions/get-release-notes/README.md b/packages/js/github-actions/actions/get-release-notes/README.md new file mode 100644 index 00000000..c0e1c729 --- /dev/null +++ b/packages/js/github-actions/actions/get-release-notes/README.md @@ -0,0 +1,163 @@ +# Get release notes via GitHub API in GitHub Actions + +This action provides the following functionality for GitHub Actions users: + +- Get release notes via GitHub +- Based on package.json, infer the next release version and tag by matching keywords to release notes + +## Usage + +See [action.yml](action.yml) + +#### Basic: + +```yaml +on: + push: + branches: + - release/my-tool + + EchoRelease: + name: Echo Release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get release notes + id: get-notes + uses: ./packages/js/github-actions/actions/get-release-notes + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + tag-template: "v{version}" + + - name: Echo release notes + run: | + echo "${{ steps.get-notes.outputs.release-notes }}" + echo "${{ steps.get-notes.outputs.release-changelog }}" +``` + +#### Matching version level keywords to infer the next version and tag: + +Create a [configuration for the changelog categories](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes) at `.github/release.yml` in the repository. For example: + +```yaml +# .github/release.yml + +changelog: + categories: + # Major level + - title: Major Changes + - major + - title: Breaking Changes + labels: + - breaking + + # Minor level + - title: Exciting New Features + labels: + - feature + - title: Improvements + labels: + - improvement + + # Patch level + - title: Other Changes + labels: + - "*" +``` + +Matching the `title` by keywords in each level: + +```yaml +# A GitHub workflow + +on: + push: + branches: + - release/my-tool + +jobs: + EchoRelease: + name: Echo Release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get release notes + id: get-notes + uses: ./packages/js/github-actions/actions/get-release-notes + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + tag-template: "v{version}" + major-keywords: major changes, breaking + minor-keywords: new Features, improvement + + - name: Echo release notes + run: | + echo "${{ steps.get-notes.outputs.release-notes }}" + echo "${{ steps.get-notes.outputs.release-changelog }}" + echo "${{ steps.get-notes.outputs.next-version }}" + echo "${{ steps.get-notes.outputs.next-tag }}" +``` + +Once any of changelog headings (`###` syntax in Markdown) in release notes has a single word starting with the same keyword, the level is matched. If both major and minor cannot be matched, it falls back to patch level. The matched level will be used to determine the next version number. + +#### Prepare release commits: + +```yaml +on: + push: + branches: + - release/my-tool + +jobs: + CheckCreatedBranch: + name: Check Created Branch + runs-on: ubuntu-latest + steps: + - name: Check created release branch + uses: actions/github-script@v6 + with: + script: | + if ( ! context.payload.created ) { + await github.rest.actions.cancelWorkflowRun( { + ...context.repo, + run_id: context.runId, + } ); + } + + PrepareRelease: + name: Prepare Release + runs-on: ubuntu-latest + needs: CheckCreatedBranch + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get release notes + id: get-notes + uses: ./packages/js/github-actions/actions/get-release-notes + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + tag-template: "v{version}" + + - name: Prepare release commits + run: | + TODAY=$(date '+%Y-%m-%d') + NEXT_VER="${{ steps.get-notes.outputs.next-version }}" + CHANGELOG="${{ steps.get-notes.outputs.release-changelog }}" + printf "## ${TODAY} (${NEXT_VER})\n${CHANGELOG}\n\n%s\n" "$(cat CHANGELOG.md)" > CHANGELOG.md + jq ".version=\"${NEXT_VER}\"" package.json > package.json.tmp + mv package.json.tmp package.json + jq ".version=\"${NEXT_VER}\"" package-lock.json > package-lock.json.tmp + mv package-lock.json.tmp package-lock.json + git config user.name github-actions + git config user.email github-actions@users.noreply.github.com + git add CHANGELOG.md + git add package.json + git add package-lock.json + git commit -q -m "Update changelog and package version for the ${{ steps.get-notes.outputs.next-tag }} release." + git push +``` From dd0226ad09ce3b314615b99ec9ce3660f6ec693c Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 22 Jun 2022 11:16:42 +0800 Subject: [PATCH 09/10] Add logs for output keys and values in the get-release-notes action --- .../get-release-notes/src/get-release-notes.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js index b22deb39..819c0279 100644 --- a/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js +++ b/packages/js/github-actions/actions/get-release-notes/src/get-release-notes.js @@ -30,6 +30,11 @@ function parseChangelog( notesContent ) { return ''; } +function setOutput( key, value ) { + core.info( `==> Output "${ key }":\n${ value }` ); + core.setOutput( key, value ); +} + async function getReleaseNotes() { // Prepare parameters const { context } = github; @@ -91,10 +96,10 @@ async function getReleaseNotes() { } // Output results - core.setOutput( 'release-notes', notesContent ); - core.setOutput( 'release-changelog', changelog ); - core.setOutput( 'next-version', nextVersion ); - core.setOutput( 'next-tag', nextTag ); + setOutput( 'release-notes', notesContent ); + setOutput( 'release-changelog', changelog ); + setOutput( 'next-version', nextVersion ); + setOutput( 'next-tag', nextTag ); } // Start running this action. From fc88a0632d01fbd82105f0feaa162eb8cbe3f2c0 Mon Sep 17 00:00:00 2001 From: Eason Su Date: Wed, 22 Jun 2022 11:16:53 +0800 Subject: [PATCH 10/10] Tweak release notes config for the github-actions package. Address https://github.com/woocommerce/grow/pull/16#discussion_r902845866 --- packages/js/github-actions/release-notes-config.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/js/github-actions/release-notes-config.yml b/packages/js/github-actions/release-notes-config.yml index 50974421..d4e6179c 100644 --- a/packages/js/github-actions/release-notes-config.yml +++ b/packages/js/github-actions/release-notes-config.yml @@ -1,4 +1,9 @@ changelog: + exclude: + authors: + - dependabot + - github-actions + categories: # Major level - title: Breaking Changes 🚨 @@ -8,16 +13,12 @@ changelog: # Minor level - title: New Features 🎉 labels: - - "[actions] changelog: feature" + - "[actions] changelog: add" - title: Updated ✨ labels: - "[actions] changelog: update" - - title: Enhancements ⚡️ - labels: - - "[actions] changelog: enhancement" - # Patch level - title: Bug Fixes 🐛 labels: