From e83dc8ab7413587bead3d7b2c53bbc6ec896259d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 27 Nov 2023 18:54:07 -0800 Subject: [PATCH] Add `scopes` option to filter analysis/changelog --- README.md | 4 ++++ action.yml | 3 +++ package-lock.json | 1 + package.json | 1 + src/action.ts | 18 ++++++++++++++++++ tests/action.test.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/helper.test.ts | 18 ++++++++++-------- types/semantic.d.ts | 4 ++++ 8 files changed, 83 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 09774b4aa..39d2b6fff 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ jobs: - **release_branches** _(optional)_ - Comma separated list of branches (JavaScript regular expression accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any repository tag. Examples: `master` or `.*` or `release.*,hotfix.*,master`... (default: `master,main`). - **pre_release_branches** _(optional)_ - Comma separated list of branches (JavaScript regular expression accepted) that will generate the pre-release tags. +#### Filter commits + +- **scopes** _(optional)_ - Comma separated list of scopes (JavaScript regular expression accepted) to consider when tagging, and to include in the changelog. If this option is specified, then commits with scopes not matching this list will not be analyzed nor included in the changelog. + #### Customize the tag - **default_bump** _(optional)_ - Which type of bump to use when [none is explicitly provided](#bumping) when commiting to a release branch (default: `patch`). You can also set `false` to avoid generating a new tag when none is explicitly provided. Can be `patch, minor or major`. diff --git a/action.yml b/action.yml index eae936ed1..fac972ed8 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,9 @@ inputs: pre_release_branches: description: "Comma separated list of branches (bash reg exp accepted) that will generate pre-release tags." required: false + scopes: + description: "Comma separated list of scopes (bash reg exp accepted) to analyze and to include in the changelog." + required: false commit_sha: description: "The commit SHA value to add the tag. If specified, it uses this value instead GITHUB_SHA. It could be useful when a previous step merged a branch into github.ref." required: false diff --git a/package-lock.json b/package-lock.json index 7c3e39838..04b01b04b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@semantic-release/commit-analyzer": "^8.0.1", "@semantic-release/release-notes-generator": "^9.0.1", "conventional-changelog-conventionalcommits": "^4.6.1", + "conventional-commits-parser": "^3.2.0", "semver": "^7.3.5" }, "devDependencies": { diff --git a/package.json b/package.json index a7fa5b618..02ef2aa33 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@semantic-release/commit-analyzer": "^8.0.1", "@semantic-release/release-notes-generator": "^9.0.1", "conventional-changelog-conventionalcommits": "^4.6.1", + "conventional-commits-parser": "^3.2.0", "semver": "^7.3.5" }, "devDependencies": { diff --git a/src/action.ts b/src/action.ts index 2a073169e..5f8076132 100644 --- a/src/action.ts +++ b/src/action.ts @@ -1,4 +1,5 @@ import * as core from '@actions/core'; +import { sync as parser } from 'conventional-commits-parser'; import { gte, inc, parse, ReleaseType, SemVer, valid } from 'semver'; import { analyzeCommits } from '@semantic-release/commit-analyzer'; import { generateNotes } from '@semantic-release/release-notes-generator'; @@ -24,6 +25,7 @@ export default async function main() { const customTag = core.getInput('custom_tag'); const releaseBranches = core.getInput('release_branches'); const preReleaseBranches = core.getInput('pre_release_branches'); + const scopes = core.getInput('scopes'); const appendToPreReleaseTag = core.getInput('append_to_pre_release_tag'); const createAnnotatedTag = /true/i.test( core.getInput('create_annotated_tag') @@ -123,6 +125,22 @@ export default async function main() { commits = await getCommits(previousTag.commit.sha, commitRef); + if (scopes.length) { + const isInScope = (scope: string) => + scopes.split(',').some((includedScope) => scope.match(includedScope)); + commits = commits.filter((commit) => { + const scope = parser(commit.message).scope; + if (scope) { + const isInScopes = scopes + .split(',') + .some((includedScope) => scope.match(includedScope)); + return isInScopes; + } else { + return false; + } + }); + } + let bump = await analyzeCommits( { releaseRules: mappedReleaseRules diff --git a/tests/action.test.ts b/tests/action.test.ts index 413e7bfae..082881b6b 100644 --- a/tests/action.test.ts +++ b/tests/action.test.ts @@ -215,6 +215,48 @@ describe('github-tag-action', () => { ); expect(mockSetFailed).not.toBeCalled(); }); + + it('does skip commits not included in scopes', async () => { + /* + * Given + */ + setInput('scopes', 'yes'); + const commits = [ + { message: 'fix(YES): 1ne', hash: null }, + { message: 'feat(NO): 2wo', hash: null }, + ]; + jest + .spyOn(utils, 'getCommits') + .mockImplementation(async (sha) => commits); + + const validTags = [ + { + name: 'v1.2.3', + commit: { sha: '012345', url: '' }, + zipball_url: '', + tarball_url: 'string', + node_id: 'string', + }, + ]; + jest + .spyOn(utils, 'getValidTags') + .mockImplementation(async () => validTags); + + /* + * When + */ + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.2.4', + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); }); describe('release branches', () => { diff --git a/tests/helper.test.ts b/tests/helper.test.ts index d56b50be5..694d7236b 100644 --- a/tests/helper.test.ts +++ b/tests/helper.test.ts @@ -18,8 +18,12 @@ export function setCommitSha(sha: string) { process.env['GITHUB_SHA'] = sha; } -export function setInput(key: string, value: string) { - process.env[`INPUT_${key.toUpperCase()}`] = value; +export function setInput(key: string, value: string | undefined) { + if (value) { + process.env[`INPUT_${key.toUpperCase()}`] = value; + } else { + delete process.env[`INPUT_${key.toUpperCase()}`]; + } } export function setInputs(map: { [key: string]: string }) { @@ -34,12 +38,10 @@ export function loadDefaultInputs() { const actionJson = yaml.load(actionYaml) as { inputs: { [key: string]: { default?: string } }; }; - const defaultInputs = Object.keys(actionJson['inputs']) - .filter((key) => actionJson['inputs'][key].default) - .reduce( - (obj, key) => ({ ...obj, [key]: actionJson['inputs'][key].default }), - {} - ); + const defaultInputs = Object.keys(actionJson['inputs']).reduce( + (obj, key) => ({ ...obj, [key]: actionJson['inputs'][key].default }), + {} + ); setInputs(defaultInputs); } diff --git a/types/semantic.d.ts b/types/semantic.d.ts index 59af2c9f5..7b18feb9d 100644 --- a/types/semantic.d.ts +++ b/types/semantic.d.ts @@ -1,5 +1,9 @@ /// +declare module 'conventional-commits-parser' { + export function sync(message: string): { scope?: string }; +} + declare module '@semantic-release/commit-analyzer' { export function analyzeCommits( config: {