From fb4e2545c792592a436ebf9322bdd4bbe613801d Mon Sep 17 00:00:00 2001 From: zimij Date: Mon, 11 May 2026 16:43:19 +0200 Subject: [PATCH] feat: Support multi-digit semver tags --- src/commands.spec.ts | 70 ++++++++++++++++++++++++++++++++------------ src/commands.ts | 10 ++++--- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/src/commands.spec.ts b/src/commands.spec.ts index 70c7274..25e70ae 100644 --- a/src/commands.spec.ts +++ b/src/commands.spec.ts @@ -65,34 +65,68 @@ describe('commands', () => { }); }); - describe('isReleaseCommit', () => { - it('return true for a tag starting with "v" and pointing at HEAD', async () => { - mockExecCallWith({ - stdout: `a7730ce591e5494a455a5283d75eddca8dc80b98 commit refs/tags/v1.2.3`, - }); + it('returns true for valid single-digit semver tag', async () => { + mockExecCallWith({stdout: 'refs/tags/v1.2.3'}); - expect(await isReleaseCommit('v')).toBe(true); - }); + await expect(isReleaseCommit('v')).resolves.toBe(true); + }); - it('return false if no tag starting with "v" and pointing at HEAD could be found', async () => { - mockExecCallWith({ - stdout: ``, - }); + it('returns true for multi-digit semver tag', async () => { + mockExecCallWith({stdout: 'refs/tags/v12.34.56'}); + + await expect(isReleaseCommit('v')).resolves.toBe(true); + }); + + it('returns false for invalid semver format', async () => { + mockExecCallWith({stdout: 'refs/tags/v12.34'}); + + await expect(isReleaseCommit('v')).resolves.toBe(false); + }); + + it('returns false when no tags exist', async () => { + mockExecCallWith({stdout: ''}); - expect(await isReleaseCommit('v')).toBe(false); + await expect(isReleaseCommit('v')).resolves.toBe(false); + }); + + it('returns false when prefix does not match', async () => { + mockExecCallWith({stdout: 'refs/tags/x1.2.3'}); + + await expect(isReleaseCommit('v')).resolves.toBe(false); + }); + + it('returns true when multiple lines include valid tag', async () => { + mockExecCallWith({ + stdout: ` +refs/tags/dev +refs/tags/v2.0.0 +refs/tags/other + `.trim(), }); - it('return false if no tag starting with "v" and pointing at HEAD could be found', async () => { - mockExecCallWith({ - error: Error('Something went wrong...'), - }); + await expect(isReleaseCommit('v')).resolves.toBe(true); + }); - await expect(isReleaseCommit('v')).rejects.toThrow('Could not determine whether the current commit is a release commit!'); + it('returns false when multiple tags exist but none match regex', async () => { + mockExecCallWith({ + stdout: ` +refs/tags/dev +refs/tags/v2.0 +refs/tags/test + `.trim(), }); + + await expect(isReleaseCommit('v')).resolves.toBe(false); + }); + + it('throws error when exec fails', async () => { + mockExecCallWith({error: new Error('git failed')}); + + await expect(isReleaseCommit('v')).rejects.toThrow('Could not determine whether the current commit is a release commit!'); }); describe('getNextSemanticVersion', () => { - it('should leave the version unchanged if there ar no commits since the last release', async () => { + it('should leave the version unchanged if there are no commits since the last release', async () => { mockExecCallWith({ stdout: ``, }); diff --git a/src/commands.ts b/src/commands.ts index fc521b1..a6f4d5e 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -56,11 +56,13 @@ export const getLatestReleaseTag: ( export const isReleaseCommit: (tagPrefix: string) => Promise = async (tagPrefix) => { try { - return (await exec(`git for-each-ref --points-at HEAD 'refs/tags/${tagPrefix ?? ''}[0-9].[0-9].[0-9]*'`)).stdout.length > 0; + const {stdout} = await exec(`git for-each-ref --points-at HEAD --format='%(refname)'`); + + const regex = new RegExp(`^refs/tags/${tagPrefix}\\d+\\.\\d+\\.\\d+$`); + + return stdout.split('\n').some((line) => regex.test(line)); } catch (error) { - if (error instanceof Error) { - core.error(error); - } + if (error instanceof Error) core.error(error); throw Error('Could not determine whether the current commit is a release commit!'); }