diff --git a/.prettierrc.json b/.prettierrc.json index a4175b70..23de5dff 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,5 +2,6 @@ "semi": true, "singleQuote": true, "trailingComma": "all", - "printWidth": 120 + "printWidth": 120, + "proseWrap": "always" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c89492c1..669adb1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,12 @@ #### Code of Conduct -This repository has adopted the Contributor Covenant as it's -Code of Conduct. It is expected that participants adhere to it. +This repository has adopted the Contributor Covenant as it's Code of Conduct. It is expected that participants adhere to +it. #### Proposing a Change -If you are unsure about whether or not a change is desired, -you can create an issue. This is useful because it creates +If you are unsure about whether or not a change is desired, you can create an issue. This is useful because it creates the possibility for a discussion that's visible to everyone. When fixing a bug it is fine to submit a pull request right away. @@ -34,6 +33,11 @@ Please note that commit hooks will run automatically to perform some tasks; - run tests - build distributable files +#### Windows users + +Make sure your editor and terminal that run the tests are set to `Powershell 7` or above with +`Git's Unix tools for Windows` installed. Some tests require you to be able to run `sh` and other unix commands. + #### License By contributing to this repository, you agree that your contributions will be licensed under its MIT license. diff --git a/dist/index.js b/dist/index.js index b650f026..d6aa5db9 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/dist/index.js.map b/dist/index.js.map index 0fd01a8a..f420bac2 100644 Binary files a/dist/index.js.map and b/dist/index.js.map differ diff --git a/src/model/__data__/versions.ts b/src/model/__data__/versions.ts new file mode 100644 index 00000000..56ad4a9c --- /dev/null +++ b/src/model/__data__/versions.ts @@ -0,0 +1,112 @@ +export const completelyValidSemanticVersions = [ + '0.0.4', + '1.2.3', + '10.20.30', + '1.1.2-prerelease+meta', + '1.1.2+meta', + '1.1.2+meta-valid', + '1.0.0-alpha', + '1.0.0-beta', + '1.0.0-alpha.beta', + '1.0.0-alpha.beta.1', + '1.0.0-alpha.1', + '1.0.0-alpha0.valid', + '1.0.0-alpha.0valid', + '1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', + '1.0.0-rc.1+build.1', + '2.0.0-rc.1+build.123', + '1.2.3-beta', + '10.2.3-DEV-SNAPSHOT', + '1.2.3-SNAPSHOT-123', + '1.0.0', + '2.0.0', + '1.1.7', + '2.0.0+build.1848', + '2.0.1-alpha.1227', + '1.0.0-alpha+beta', + '1.2.3----RC-SNAPSHOT.12.9.1--.12+788', + '1.2.3----R-S.12.9.1--.12+meta', + '1.2.3----RC-SNAPSHOT.12.9.1--.12', + '1.0.0+0.build.1-rc.10000aaa-kk-0.1', + '99999999999999999999999.999999999999999999.99999999999999999', + '1.0.0-0A.is.legal', +]; + +export const notCompletelyValidSemanticVersions = [ + '1', + '1.2', + '1.2.3-0123', + '1.2.3-0123.0123', + '1.1.2+.123', + '+invalid', + '-invalid', + '-invalid+invalid', + '-invalid.01', + 'alpha', + 'alpha.beta', + 'alpha.beta.1', + 'alpha.1', + 'alpha+beta', + 'alpha_beta', + 'alpha.', + 'alpha..', + 'beta', + '1.0.0-alpha_beta', + '-alpha.', + '1.0.0-alpha..', + '1.0.0-alpha..1', + '1.0.0-alpha...1', + '1.0.0-alpha....1', + '1.0.0-alpha.....1', + '1.0.0-alpha......1', + '1.0.0-alpha.......1', + '01.1.1', + '1.01.1', + '1.1.01', + '1.2', + '1.2.3.DEV', + '1.2-SNAPSHOT', + '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788', + '1.2-RC-SNAPSHOT', + '-1.0.3-gamma+b7718', + '+justmeta', + '9.8.7+meta+meta', + '9.8.7-whatever+meta+meta', + '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12', +]; + +const addVariantsPrependingV = (array: string[]) => array.map((tag) => [tag, `v${tag}`]).flat(); + +/** + * Array of versions that will be detected as version tags. Not all of these are + * "semantic versions", but can be used to generate one. Especially using the + * `versioning: Semantic` option. + */ +export const validVersionTagInputs = addVariantsPrependingV([ + '0', + '1', + '0.1', + '1.0', + '1.1.0', + '1.2.3', + ...completelyValidSemanticVersions, +]); + +export const invalidVersionTagInputs = addVariantsPrependingV([ + '+invalid', + '-invalid', + '-invalid+invalid', + '-invalid.01', + 'alpha', + 'alpha.beta', + 'alpha.beta.1', + 'alpha.1', + 'alpha+beta', + 'alpha_beta', + 'alpha.', + 'alpha..', + 'beta', + '-alpha.', + '-1.0.3-gamma+b7718', + '+justmeta', +]); diff --git a/src/model/versioning.test.ts b/src/model/versioning.test.ts index 5cd7f857..0451c74f 100644 --- a/src/model/versioning.test.ts +++ b/src/model/versioning.test.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import NotImplementedException from './error/not-implemented-exception'; import System from './system'; import Versioning from './versioning'; +import { validVersionTagInputs, invalidVersionTagInputs } from './__data__/versions'; afterEach(() => { jest.restoreAllMocks(); @@ -34,6 +35,26 @@ describe('Versioning', () => { }); }); + describe('grepCompatibleInputVersionRegex', () => { + // eslint-disable-next-line unicorn/consistent-function-scoping + const matchInputUsingGrep = async (input) => { + const output = await System.run('sh', undefined, { + input: Buffer.from(`echo '${input}' | grep -E '${Versioning.grepCompatibleInputVersionRegex}'`), + silent: true, + }); + + return output.trim(); + }; + + it.concurrent.each(validVersionTagInputs)(`accepts valid tag input '%s'`, async (input) => { + expect(await matchInputUsingGrep(input)).toStrictEqual(input); + }); + + it.concurrent.each(invalidVersionTagInputs)(`rejects non-version tag input '%s'`, async (input) => { + await expect(async () => matchInputUsingGrep(input)).rejects.toThrowError(/^Failed to run/); + }); + }); + describe('branch', () => { it('returns headRef when set', () => { const headReference = jest.spyOn(Versioning, 'headRef', 'get').mockReturnValue('feature-branch-1'); diff --git a/src/model/versioning.ts b/src/model/versioning.ts index a4f858b6..7ef0ca47 100644 --- a/src/model/versioning.ts +++ b/src/model/versioning.ts @@ -17,6 +17,10 @@ export default class Versioning { return { None: 'None', Semantic: 'Semantic', Tag: 'Tag', Custom: 'Custom' }; } + static get grepCompatibleInputVersionRegex() { + return '^v?([0-9]+\\.)*[0-9]+.*'; + } + /** * Get the branch name of the (related) branch */ @@ -272,18 +276,20 @@ export default class Versioning { } /** - * Whether or not the repository has any version tags yet. + * Whether the current tree has any version tags yet. + * + * Note: Currently this is run in all OSes, so the syntax must be cross-platform. */ static async hasAnyVersionTags() { - const numberOfCommitsAsString = await System.run('sh', undefined, { - input: Buffer.from('git tag --list --merged HEAD | grep v[0-9]* | wc -l'), + const numberOfTagsAsString = await System.run('sh', undefined, { + input: Buffer.from(`git tag --list --merged HEAD | grep -E '${this.grepCompatibleInputVersionRegex}' | wc -l`), cwd: this.projectPath, silent: false, }); - const numberOfCommits = Number.parseInt(numberOfCommitsAsString, 10); + const numberOfTags = Number.parseInt(numberOfTagsAsString, 10); - return numberOfCommits !== 0; + return numberOfTags !== 0; } /**