diff --git a/README.md b/README.md index 09774b4aa..f9a606454 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ jobs: - **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`. - **default_prerelease_bump** _(optional)_ - Which type of bump to use when [none is explicitly provided](#bumping) when commiting to a prerelease branch (default: `prerelease`). You can also set `false` to avoid generating a new tag when none is explicitly provided. Can be `prerelease, prepatch, preminor or premajor`. +- **default_draft_bump** _(optional)_ - Which type of bump to use when [none is explicitly provided](#bumping) when commiting to a prerelease branch with no previous prerelease version (default: `prerelease`). You can also set `false` to avoid generating a new tag when none is explicitly provided. Can be `prerelease, patch, minor or major`. - **custom_tag** _(optional)_ - Custom tag name. If specified, it overrides bump settings. - **create_annotated_tag** _(optional)_ - Boolean to create an annotated rather than a lightweight one (default: `false`). - **tag_prefix** _(optional)_ - A prefix to the tag name (default: `v`). diff --git a/action.yml b/action.yml index eae936ed1..b859708d8 100644 --- a/action.yml +++ b/action.yml @@ -20,6 +20,11 @@ inputs: description: "Which type of bump to use when none explicitly provided when commiting to a release branch (default: `patch`)." required: false default: "patch" + default_draft_bump: + description: | + Which type of bump to use when none explicitly provided when commiting to a prerelease branch with no previous prerelease version (default: same as `default_prerelease_bump`). + Defaults to prerelease for backward-compatibility with previous behaviour. Might make more sense to set it to the same as `default_bump`. + required: false default_prerelease_bump: description: "Which type of bump to use when none explicitly provided when commiting to a prerelease branch (default: `prerelease`)." required: false diff --git a/src/action.ts b/src/action.ts index 2a073169e..0ef74e297 100644 --- a/src/action.ts +++ b/src/action.ts @@ -20,6 +20,9 @@ export default async function main() { const defaultPreReleaseBump = core.getInput('default_prerelease_bump') as | ReleaseType | 'false'; + const defaultDraftBump = + (core.getInput('default_draft_bump') as ReleaseType | 'false') || + defaultPreReleaseBump; const tagPrefix = core.getInput('tag_prefix'); const customTag = core.getInput('custom_tag'); const releaseBranches = core.getInput('release_branches'); @@ -120,6 +123,7 @@ export default async function main() { ); core.setOutput('previous_version', previousVersion.version); core.setOutput('previous_tag', previousTag.name); + const previousWasPrerelease = previousVersion.prerelease.length != 0; commits = await getCommits(previousTag.commit.sha, commitRef); @@ -136,6 +140,8 @@ export default async function main() { // Determine if we should continue with tag creation based on main vs prerelease branch let shouldContinue = true; if (isPrerelease) { + if (!bump && !previousWasPrerelease && defaultDraftBump === 'false') + shouldContinue = false; if (!bump && defaultPreReleaseBump === 'false') { shouldContinue = false; } @@ -155,9 +161,13 @@ export default async function main() { // If we don't have an automatic bump for the prerelease, just set our bump as the default if (isPrerelease && !bump) { - bump = defaultPreReleaseBump; + if (!previousWasPrerelease) + // previous version is a prerelease -> draft a new version with the default bump and make it a prerelease + bump = defaultDraftBump; + else bump = defaultPreReleaseBump; } + // TODO: these next 10 lines are horrible!! why we have preminor as bump type at all if it is always striped away? // If somebody uses custom release rules on a prerelease branch they might create a 'preprepatch' bump. const preReg = /^pre/; if (isPrerelease && preReg.test(bump)) { diff --git a/tests/action.test.ts b/tests/action.test.ts index 413e7bfae..6d66f0dd6 100644 --- a/tests/action.test.ts +++ b/tests/action.test.ts @@ -450,6 +450,7 @@ describe('github-tag-action', () => { beforeEach(() => { jest.clearAllMocks(); setBranch('prerelease'); + loadDefaultInputs(); setInput('pre_release_branches', 'prerelease'); }); @@ -527,6 +528,234 @@ describe('github-tag-action', () => { expect(mockSetFailed).not.toBeCalled(); }); + /** 1.3.0 commit =[minor, minor, prerelease]=> 1.4.0-pre.0 */ + it('does create prerelease tag respecting default_draft_bump', async () => { + /* + * Given + */ + const commits = [{ message: 'this is a commit', 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 + */ + setInput('default_bump', 'minor'); + setInput('default_draft_bump', 'minor'); + setInput('default_prerelease_bump', 'prerelease'); + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.3.0-prerelease.0', + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); + + /** 1.3.0-pre.0 + commit =[minor, minor, prerelease]=> 1.3.0-pre.1 */ + it('does update prerelease tag respecting default_draft_bump', async () => { + /* + * Given + */ + const commits = [{ message: 'this is a commit', 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', + }, + { + name: 'v1.3.0-prerelease.0', + commit: { sha: '123456', url: '' }, + zipball_url: '', + tarball_url: 'string', + node_id: 'string', + }, + ]; + jest + .spyOn(utils, 'getValidTags') + .mockImplementation(async () => validTags); + + /* + * When + */ + setInput('default_bump', 'minor'); + setInput('default_draft_bump', 'minor'); + setInput('default_prerelease_bump', 'prerelease'); + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.3.0-prerelease.1', + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); + + /** 1.3.0-pre.0 + commit =[minor, minor, preminor]=> 1.4.0-pre.0 */ + it('does update prerelease tag with preminor', async () => { + /* + * Given + */ + const commits = [{ message: 'this is a commit', 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', + }, + { + name: 'v1.3.0-prerelease.0', + commit: { sha: '123456', url: '' }, + zipball_url: '', + tarball_url: 'string', + node_id: 'string', + }, + ]; + jest + .spyOn(utils, 'getValidTags') + .mockImplementation(async () => validTags); + + /* + * When + */ + setInput('default_bump', 'minor'); + setInput('default_draft_bump', 'minor'); + setInput('default_prerelease_bump', 'preminor'); + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.4.0-prerelease.0', + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); + + /** + * 1.2.3 commit =[minor, -, prerelease]=> 1.2.4-pre.0 + * according to semver, a prerelease increment on a non-prerelease version drafts a new minor version + */ + it('default_draft_bump defaults to default_prerelease_bump (prerelease)', async () => { + /* + * Given + */ + const commits = [{ message: 'this is a commit', 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 + */ + setInput('default_bump', 'minor'); + setInput('default_prerelease_bump', 'prerelease'); + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.2.4-prerelease.0', // prerelease drafts patch upgrades + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); + + /** + * 1.2.3 commit =[minor, -, prerelease]=> 1.2.4-pre.0 + * according to semver, a prerelease increment on a non-prerelease version drafts a new minor version + */ + it('default_draft_bump defaults to default_prerelease_bump (preminor)', async () => { + /* + * Given + */ + const commits = [{ message: 'this is a commit', 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 + */ + setInput('default_bump', 'minor'); + setInput('default_prerelease_bump', 'preminor'); + await action(); + + /* + * Then + */ + expect(mockCreateTag).toHaveBeenCalledWith( + 'v1.3.0-prerelease.0', // prerelease drafts patch upgrades + expect.any(Boolean), + expect.any(String) + ); + expect(mockSetFailed).not.toBeCalled(); + }); + it('does create prepatch tag', async () => { /* * Given diff --git a/tests/helper.test.ts b/tests/helper.test.ts index d56b50be5..8cd3c1c8b 100644 --- a/tests/helper.test.ts +++ b/tests/helper.test.ts @@ -26,6 +26,13 @@ export function setInputs(map: { [key: string]: string }) { Object.keys(map).forEach((key) => setInput(key, map[key])); } +export function resetInputs(map: { [key: string]: string }) { + Object.keys(process.env) + .filter((k) => k.startsWith('INPUT_')) + .forEach((k) => delete process.env[k]); + Object.keys(map).forEach((key) => setInput(key, map[key])); +} + export function loadDefaultInputs() { const actionYaml = fs.readFileSync( path.join(process.cwd(), 'action.yml'), @@ -40,7 +47,7 @@ export function loadDefaultInputs() { (obj, key) => ({ ...obj, [key]: actionJson['inputs'][key].default }), {} ); - setInputs(defaultInputs); + resetInputs(defaultInputs); } // Don't know how to have this file only for test but not have 'tsc' complain. So I made it a test file...