add custom_release_rules and more tests

This commit is contained in:
Moritz Schmitz von Hülst 2020-11-13 11:51:00 +01:00
parent 169b215c7c
commit 158d2b0407
3 changed files with 194 additions and 40 deletions

View File

@ -28,6 +28,9 @@ inputs:
custom_tag:
description: "Custom tag name. If specified, it overrides bump settings."
required: false
custom_release_rules:
description: "Comma separated list of release rules. Format: `<keyword>:<release_type>`. Example: `hotfix:patch,pre-feat:preminor`."
required: false
release_branches:
description: "Comma separated list of branches (bash reg exp accepted) that will generate the release tags. Other branches and pull-requests generate versions postfixed with the commit hash and do not generate any tag. Examples: `master` or `.*` or `release.*,hotfix.*,master`..."
required: false
@ -36,11 +39,11 @@ inputs:
description: "Comma separated list of branches (bash reg exp accepted) that will generate pre-release tags."
required: false
create_annotated_tag:
description: "Boolean to create an annotated tag rather than lightweight"
description: "Boolean to create an annotated tag rather than lightweight."
required: false
default: "false"
dry_run:
description: "Do not perform tagging, just calculate next version and changelog, then exit"
description: "Do not perform tagging, just calculate next version and changelog, then exit."
required: false
default: "false"

View File

@ -1,7 +1,7 @@
import * as core from "@actions/core";
import { gte, inc, parse, ReleaseType, SemVer, valid } from "semver";
import { analyzeCommits } from "@semantic-release/commit-analyzer";
import { generateNotes } from "@semantic-release/release-notes-generator";
import * as core from '@actions/core';
import { gte, inc, parse, ReleaseType, SemVer, valid } from 'semver';
import { analyzeCommits } from '@semantic-release/commit-analyzer';
import { generateNotes } from '@semantic-release/release-notes-generator';
import {
getBranchFromRef,
getCommits,
@ -9,20 +9,20 @@ import {
getLatestTag,
getValidTags,
mapCustomReleaseRules,
} from "./utils";
import { createTag } from "./github";
} from './utils';
import { createTag } from './github';
export default async () => {
try {
const defaultBump = core.getInput("default_bump") as ReleaseType | "false";
const tagPrefix = core.getInput("tag_prefix");
const customTag = core.getInput("custom_tag");
const releaseBranches = core.getInput("release_branches");
const preReleaseBranches = core.getInput("pre_release_branches");
const appendToPreReleaseTag = core.getInput("append_to_pre_release_tag");
const createAnnotatedTag = !!core.getInput("create_annotated_tag");
const dryRun = core.getInput("dry_run");
const customReleaseRules = core.getInput("custom_release_rules");
const defaultBump = core.getInput('default_bump') as ReleaseType | 'false';
const tagPrefix = core.getInput('tag_prefix');
const customTag = core.getInput('custom_tag');
const releaseBranches = core.getInput('release_branches');
const preReleaseBranches = core.getInput('pre_release_branches');
const appendToPreReleaseTag = core.getInput('append_to_pre_release_tag');
const createAnnotatedTag = !!core.getInput('create_annotated_tag');
const dryRun = core.getInput('dry_run');
const customReleaseRules = core.getInput('custom_release_rules');
let mappedReleaseRules;
if (customReleaseRules) {
@ -32,21 +32,21 @@ export default async () => {
const { GITHUB_REF, GITHUB_SHA } = process.env;
if (!GITHUB_REF) {
core.setFailed("Missing GITHUB_REF.");
core.setFailed('Missing GITHUB_REF.');
return;
}
if (!GITHUB_SHA) {
core.setFailed("Missing GITHUB_SHA.");
core.setFailed('Missing GITHUB_SHA.');
return;
}
const currentBranch = getBranchFromRef(GITHUB_REF);
const isReleaseBranch = releaseBranches
.split(",")
.split(',')
.some((branch) => currentBranch.match(branch));
const isPreReleaseBranch = preReleaseBranches
.split(",")
.split(',')
.some((branch) => currentBranch.match(branch));
const isPrerelease = !isReleaseBranch && isPreReleaseBranch;
@ -55,7 +55,7 @@ export default async () => {
const latestTag = getLatestTag(validTags);
const latestPrereleaseTag = getLatestPrereleaseTag(
validTags,
identifier
identifier,
);
const commits = await getCommits(latestTag.commit.sha);
@ -72,30 +72,34 @@ export default async () => {
previousTag = parse(
gte(latestTag.name, latestPrereleaseTag.name)
? latestTag.name
: latestPrereleaseTag.name
: latestPrereleaseTag.name,
);
}
if (!previousTag) {
core.setFailed("Could not parse previous tag.");
core.setFailed('Could not parse previous tag.');
return;
}
core.info(`Previous tag was ${previousTag}.`);
core.setOutput("previous_tag", previousTag.version);
core.setOutput('previous_tag', previousTag.version);
const bump = await analyzeCommits(
let bump = await analyzeCommits(
{ releaseRules: mappedReleaseRules },
{ commits, logger: { log: console.info.bind(console) } }
{ commits, logger: { log: console.info.bind(console) } },
);
if (!bump && defaultBump === "false") {
core.debug(
"No commit specifies the version bump. Skipping the tag creation."
);
if (!bump && defaultBump === 'false') {
core.debug('No commit specifies the version bump. Skipping the tag creation.');
return;
}
// If somebody uses custom release rules on a prerelease branch they might create a 'preprepatch' bump.
const preReg = /^pre/;
if (isPrerelease && preReg.test(bump)) {
bump = bump.replace(preReg,'');
}
const releaseType: ReleaseType = isPrerelease
? `pre${bump || defaultBump}`
: bump || defaultBump;
@ -103,11 +107,11 @@ export default async () => {
const incrementedVersion = inc(
previousTag,
releaseType,
identifier
identifier,
);
if (!incrementedVersion) {
core.setFailed("Could not increment version.");
core.setFailed('Could not increment version.');
return;
}
@ -120,11 +124,11 @@ export default async () => {
}
core.info(`New version is ${newVersion}.`);
core.setOutput("new_version", newVersion);
core.setOutput('new_version', newVersion);
const newTag = `${tagPrefix}${newVersion}`;
core.info(`New tag after applying prefix is ${newTag}.`);
core.setOutput("new_tag", newTag);
core.setOutput('new_tag', newTag);
const changelog = await generateNotes(
{},
@ -136,25 +140,25 @@ export default async () => {
},
lastRelease: { gitTag: latestTag.name },
nextRelease: { gitTag: newTag, version: newVersion },
}
},
);
core.info(`Changelog is ${changelog}.`);
core.setOutput("changelog", changelog);
core.setOutput('changelog', changelog);
if (!isReleaseBranch && !isPreReleaseBranch) {
core.info(
"This branch is neither a release nor a pre-release branch. Skipping the tag creation."
'This branch is neither a release nor a pre-release branch. Skipping the tag creation.',
);
return;
}
if (validTags.map((tag) => tag.name).includes(newTag)) {
core.info("This tag already exists. Skipping the tag creation.");
core.info('This tag already exists. Skipping the tag creation.');
return;
}
if (/true/i.test(dryRun)) {
core.info("Dry run: not performing tag action.");
core.info('Dry run: not performing tag action.');
return;
}

View File

@ -126,6 +126,33 @@ describe('github-tag-action', () => {
expect(mockCreateTag).toHaveBeenCalledWith('v2.0.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create tag using custom release types but non-custom commit message', async () => {
/*
* Given
*/
setInput('custom_release_rules', 'james:patch,bond:major');
const commits = [{ message: 'fix: is the new cool guy' }, { message: 'feat: is his last name' }];
jest
.spyOn(utils, 'getCommits')
.mockImplementation(async (sha) => commits);
const validTags = [{ name: 'v1.2.3', commit: { sha: '012345' } }];
jest
.spyOn(utils, 'getValidTags')
.mockImplementation(async () => validTags);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockCreateTag).toHaveBeenCalledWith('v1.3.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
});
describe('release branches', () => {
@ -212,6 +239,66 @@ describe('github-tag-action', () => {
expect(mockCreateTag).toHaveBeenCalledWith('v2.0.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create tag when pre-release tag is newer', async () => {
/*
* Given
*/
const commits = [{ message: 'feat: some new feature on a release branch' }];
jest
.spyOn(utils, 'getCommits')
.mockImplementation(async (sha) => commits);
const validTags = [
{ name: 'v1.2.3', commit: { sha: '012345' } },
{ name: 'v2.1.3-prerelease.0', commit: { sha: '678901' } },
{ name: 'v2.1.3-prerelease.1', commit: { sha: '234567' } },
];
jest
.spyOn(utils, 'getValidTags')
.mockImplementation(async () => validTags);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockCreateTag).toHaveBeenCalledWith('v2.2.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create tag with custom release rules', async () => {
/*
* Given
*/
setInput('custom_release_rules', 'james:preminor')
const commits = [
{ message: 'feat: some new feature on a pre-release branch' },
{ message: 'james: this should make a preminor' },
];
jest
.spyOn(utils, 'getCommits')
.mockImplementation(async (sha) => commits);
const validTags = [{ name: 'v1.2.3', commit: { sha: '012345' } }];
jest
.spyOn(utils, 'getValidTags')
.mockImplementation(async () => validTags);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockCreateTag).toHaveBeenCalledWith('v1.3.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
});
describe('pre-release branches', () => {
@ -298,6 +385,66 @@ describe('github-tag-action', () => {
expect(mockCreateTag).toHaveBeenCalledWith('v2.0.0-prerelease.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create tag when release tag is newer', async () => {
/*
* Given
*/
const commits = [{ message: 'feat: some new feature on a pre-release branch' }];
jest
.spyOn(utils, 'getCommits')
.mockImplementation(async (sha) => commits);
const validTags = [
{ name: 'v1.2.3-prerelease.0', commit: { sha: '012345' } },
{ name: 'v3.1.2-feature.0', commit: { sha: '012345' } },
{ name: 'v2.1.4', commit: { sha: '234567' } },
];
jest
.spyOn(utils, 'getValidTags')
.mockImplementation(async () => validTags);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockCreateTag).toHaveBeenCalledWith('v2.2.0-prerelease.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
it('does create tag with custom release rules', async () => {
/*
* Given
*/
setInput('custom_release_rules', 'james:preminor')
const commits = [
{ message: 'feat: some new feature on a pre-release branch' },
{ message: 'james: this should make a preminor' },
];
jest
.spyOn(utils, 'getCommits')
.mockImplementation(async (sha) => commits);
const validTags = [{ name: 'v1.2.3', commit: { sha: '012345' } }];
jest
.spyOn(utils, 'getValidTags')
.mockImplementation(async () => validTags);
/*
* When
*/
await main();
/*
* Then
*/
expect(mockCreateTag).toHaveBeenCalledWith('v1.3.0-prerelease.0', expect.any(Boolean), expect.any(String));
expect(mockSetFailed).not.toBeCalled();
});
});
describe('other branches', () => {