unity-builder icon indicating copy to clipboard operation
unity-builder copied to clipboard

Semantic versioning doesn't set the Patch field if the latest Tag includes 3 fields (like 'v0.0.0')

Open rferrese opened this issue 11 months ago • 1 comments

Bug description

Semantic versioning patch field does not behave as expected when the most recent tag has major, minor, and tag in it.

How to reproduce

  1. Create workflow with uses: game-ci/unity-builder@v4 to build a project
  2. Add a tag v0.1 a couple CLs back (or submit a couple CLs after)
  3. Build with GitHub Actions
  4. See that the built project outputs.buildVersion is something like 0.1.14 as expected
  5. Delete the tag and instead add a tag v0.1.0 a couple CLs back
  6. Build with GitHub Actions
  7. See that the built project outputs.buildVersion is 0.1.0 which is NOT as expected

Expected behavior

Based on the docs for Semantic versioning here I would expect the major and minor versions to be dictated by the most recent tag, but for the patch to always be the number of CLs since the tag.

Additional details

I would like to have GitHub Actions automatically create a tag for each release with the version. So for example, if commit 8db7eea is built to create 0.1.14, I would like to have it create the tag v0.1.14 on that commit. This behavior prevents me from doing that.

I believe that the cause is in the src/model/versioning.ts file. With 3 field in the version, getVersionDescription would something like v0.12.15-24-gd2198ab (If the tag was v.0.12.15). parseSemanticVersion would then match on the first descriptionRegexes to generate [match=..., tag='0.12.15', commits='24', hash='d2198ab'] . Then inside generateSemanticVersion we would combine tag and commits with const [major, minor, patch] = ${tag}.${commits}.split('.'); to create '0.12.15.24', but we only look at the first 3 so it would resolve to '0.12.15'.

rferrese avatar Jun 02 '25 17:06 rferrese

My recommendation for fixing this (I do not have the time to get this all running locally to test and create a PR), would be to modify src/model/versioning.ts so that generateSemanticVersion() does this instead:

      const { tag, commits, hash } = versionDescriptor;

      // Use major and minor from tag, with patch from commit count
      let [major, minor] = tag.split('.');
      minor = minor ?? '0';
      const threeDigitVersion = `${major}.${minor}.${commits}`;

      core.info(`Found semantic version ${threeDigitVersion} for ${this.branch}@${hash}`);

      return `${threeDigitVersion}`;

And then the generateSemanticVersion unit tests would have some additional cases for this:

    it('tag with 3 fields uses commits', async () => {
      ...
      jest.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
        match: '0.1.0-2-g1b345678',
        tag: '0.1.0',
        commits: '2',
        hash: '1b345678',
      });

      await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual('0.1.2');
    });
    it('tag with 1 field skips minor', async () => {
      ...
      jest.spyOn(Versioning, 'parseSemanticVersion').mockResolvedValue({
        match: '1-2-g1b345678',
        tag: '1',
        commits: '2',
        hash: '1b345678',
      });

      await expect(Versioning.generateSemanticVersion()).resolves.toStrictEqual('1.0.2');
    });

rferrese avatar Jun 02 '25 21:06 rferrese