From 5cc0678fa7f314cc1ee2225e428f01f191f44836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:39:39 +0200 Subject: [PATCH 01/10] Add release version source --- version.cmake | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 version.cmake diff --git a/version.cmake b/version.cmake new file mode 100644 index 00000000..0bddab40 --- /dev/null +++ b/version.cmake @@ -0,0 +1,4 @@ +set(ELEM_VERSION_MAJOR "1") +set(ELEM_VERSION_MINOR "0") +set(ELEM_VERSION_PATCH "0") +set(ELEM_VERSION_STAGE "dev") From 4c24f7c5eddb13a5df48646d0dee14e8b7ce3650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:39:51 +0200 Subject: [PATCH 02/10] Add build version injection script --- .github/scripts/apply-version.ps1 | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/scripts/apply-version.ps1 diff --git a/.github/scripts/apply-version.ps1 b/.github/scripts/apply-version.ps1 new file mode 100644 index 00000000..6d034de3 --- /dev/null +++ b/.github/scripts/apply-version.ps1 @@ -0,0 +1,87 @@ +param( + [Parameter(Mandatory = $false)] + [string]$BuildNumber = "local", + + [Parameter(Mandatory = $false)] + [string]$VersionFile = "version.cmake", + + [Parameter(Mandatory = $false)] + [string]$HeaderPath = "src/Elemental/Elemental.h" +) + +$ErrorActionPreference = "Stop" + +function Get-CMakeValue +{ + param( + [string]$Content, + [string]$Name + ) + + $pattern = '(?m)^\s*set\(\s*' + [Regex]::Escape($Name) + '\s+"(?[^"]*)"\s*\)\s*$' + $match = [Regex]::Match($Content, $pattern) + + if (!$match.Success) + { + throw "Unable to read $Name from $VersionFile." + } + + return $match.Groups["value"].Value +} + +$versionContent = Get-Content -Path $VersionFile -Raw +$major = Get-CMakeValue $versionContent "ELEM_VERSION_MAJOR" +$minor = Get-CMakeValue $versionContent "ELEM_VERSION_MINOR" +$patch = Get-CMakeValue $versionContent "ELEM_VERSION_PATCH" +$stage = Get-CMakeValue $versionContent "ELEM_VERSION_STAGE" + +if ($major -notmatch '^\d+$' -or $minor -notmatch '^\d+$' -or $patch -notmatch '^\d+$') +{ + throw "Elemental semantic version components must be numeric." +} + +if (![string]::IsNullOrWhiteSpace($stage) -and $stage -notmatch '^(dev|alpha|beta|rc)$') +{ + throw "Unsupported Elemental release stage: $stage" +} + +$productVersion = "$major.$minor.$patch" + +if ([string]::IsNullOrWhiteSpace($stage)) +{ + $version = $productVersion +} +else +{ + if ($BuildNumber -notmatch '^(local|\d+)$') + { + throw "Prerelease build number must be 'local' or numeric: $BuildNumber" + } + + $version = "$productVersion-$stage.$BuildNumber" +} + +$header = Get-Content -Path $HeaderPath -Raw +$versionCommentPattern = '(?m)^// Version: .*$' +$versionMacroPattern = '(?m)^#define ELEM_VERSION_LABEL ".*"$' + +if ([Regex]::Matches($header, $versionCommentPattern).Count -ne 1) +{ + throw "Expected exactly one version comment in $HeaderPath." +} + +if ([Regex]::Matches($header, $versionMacroPattern).Count -ne 1) +{ + throw "Expected exactly one ELEM_VERSION_LABEL definition in $HeaderPath." +} + +$header = [Regex]::Replace($header, $versionCommentPattern, "// Version: $version") +$header = [Regex]::Replace($header, $versionMacroPattern, "#define ELEM_VERSION_LABEL `"$version`"") + +[System.IO.File]::WriteAllText( + $HeaderPath, + $header, + [System.Text.UTF8Encoding]::new($false) +) + +Write-Host "Elemental build version: $version" From e7cb75710593a2912517895a366bc1748e6e77a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:40:02 +0200 Subject: [PATCH 03/10] Add release version resolver --- .github/scripts/resolve-version.ps1 | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 .github/scripts/resolve-version.ps1 diff --git a/.github/scripts/resolve-version.ps1 b/.github/scripts/resolve-version.ps1 new file mode 100644 index 00000000..863a4684 --- /dev/null +++ b/.github/scripts/resolve-version.ps1 @@ -0,0 +1,107 @@ +param( + [Parameter(Mandatory = $false)] + [string]$VersionFile = "version.cmake" +) + +$ErrorActionPreference = "Stop" + +function Get-CMakeValue +{ + param( + [string]$Content, + [string]$Name + ) + + $pattern = '(?m)^\s*set\(\s*' + [Regex]::Escape($Name) + '\s+"(?[^"]*)"\s*\)\s*$' + $match = [Regex]::Match($Content, $pattern) + + if (!$match.Success) + { + throw "Unable to read $Name from $VersionFile." + } + + return $match.Groups["value"].Value +} + +$versionContent = Get-Content -Path $VersionFile -Raw +$major = Get-CMakeValue $versionContent "ELEM_VERSION_MAJOR" +$minor = Get-CMakeValue $versionContent "ELEM_VERSION_MINOR" +$patch = Get-CMakeValue $versionContent "ELEM_VERSION_PATCH" +$stage = Get-CMakeValue $versionContent "ELEM_VERSION_STAGE" + +if ($major -notmatch '^\d+$' -or $minor -notmatch '^\d+$' -or $patch -notmatch '^\d+$') +{ + throw "Elemental semantic version components must be numeric." +} + +if (![string]::IsNullOrWhiteSpace($stage) -and $stage -notmatch '^(dev|alpha|beta|rc)$') +{ + throw "Unsupported Elemental release stage: $stage" +} + +$productVersion = "$major.$minor.$patch" +$isPrerelease = ![string]::IsNullOrWhiteSpace($stage) + +if ($isPrerelease) +{ + $buildNumbers = @( + & git tag --merged HEAD --list "v*" | + ForEach-Object { + if ($_ -match '^v\d+\.\d+\.\d+-(?:dev|alpha|beta|rc)\.(?\d+)$') + { + [int64]$Matches["build"] + } + } + ) + + if ($LASTEXITCODE -ne 0) + { + throw "Unable to inspect existing Elemental release tags." + } + + $buildNumber = if ($buildNumbers.Count -eq 0) + { + 1 + } + else + { + ([int64]($buildNumbers | Measure-Object -Maximum).Maximum) + 1 + } + + $version = "$productVersion-$stage.$buildNumber" +} +else +{ + $buildNumber = 0 + $version = $productVersion + + $stableNotesPath = ".github/release-notes/$version.md" + + if (!(Test-Path -Path $stableNotesPath)) + { + throw "Stable release $version requires editorial notes at $stableNotesPath." + } +} + +$tag = "v$version" +& git rev-parse --verify --quiet "refs/tags/$tag" *> $null + +if ($LASTEXITCODE -eq 0) +{ + throw "Release identity already exists: $tag" +} + +if ([string]::IsNullOrWhiteSpace($env:GITHUB_OUTPUT)) +{ + Write-Host "version=$version" + Write-Host "tag=$tag" + Write-Host "build_number=$buildNumber" + Write-Host "prerelease=$($isPrerelease.ToString().ToLowerInvariant())" +} +else +{ + "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + "build_number=$buildNumber" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + "prerelease=$($isPrerelease.ToString().ToLowerInvariant())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 +} From e7b96411e213b1232c8de6bf6d6caa0ce688b6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:40:17 +0200 Subject: [PATCH 04/10] Add Elemental release notes generator --- .github/scripts/release-notes.ps1 | 205 ++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 .github/scripts/release-notes.ps1 diff --git a/.github/scripts/release-notes.ps1 b/.github/scripts/release-notes.ps1 new file mode 100644 index 00000000..4ae680d2 --- /dev/null +++ b/.github/scripts/release-notes.ps1 @@ -0,0 +1,205 @@ +param( + [Parameter(Mandatory = $true)] + [string]$Version, + + [Parameter(Mandatory = $true)] + [string]$Repository, + + [Parameter(Mandatory = $true)] + [string]$OutputPath +) + +$ErrorActionPreference = "Stop" + +if ($Version -notmatch '^(?\d+\.\d+\.\d+)(?:-(?dev|alpha|beta|rc)\.(?\d+))?$') +{ + throw "Unsupported release version: $Version" +} + +$productVersion = $Matches["product"] +$stage = $Matches["stage"] + +function Get-PreviousTag +{ + param( + [string]$Revision + ) + + $tag = & git describe --tags --abbrev=0 $Revision 2>$null + + if ($LASTEXITCODE -ne 0) + { + return $null + } + + return ($tag | Select-Object -First 1).Trim() +} + +function Get-LatestStableTag +{ + $tags = @( + & git tag --merged HEAD --sort=-version:refname --list "v*" | + Where-Object { $_ -match '^v\d+\.\d+\.\d+$' } + ) + + if ($tags.Count -eq 0) + { + return $null + } + + return $tags[0].Trim() +} + +$previousTag = Get-PreviousTag "HEAD^" +$baseTag = $null +$rangeDescription = $null +$sameStageAsPreviousRelease = $false + +if (![string]::IsNullOrWhiteSpace($stage) -and $null -ne $previousTag -and + $previousTag -match '^v(?\d+\.\d+\.\d+)-(?dev|alpha|beta|rc)\.\d+$') +{ + $sameStageAsPreviousRelease = + $Matches["product"] -eq $productVersion -and + $Matches["stage"] -eq $stage +} + +if ($sameStageAsPreviousRelease) +{ + $baseTag = $previousTag + $rangeDescription = "Includes changes since $baseTag." +} +else +{ + $baseTag = Get-LatestStableTag + + $rangeDescription = if ($null -eq $baseTag) + { + "Includes all changes in the $productVersion release cycle since the beginning of the project." + } + else + { + "Includes all changes in the $productVersion release cycle since $baseTag." + } +} + +$commitArguments = if ($null -eq $baseTag) +{ + @("rev-list", "--reverse", "HEAD") +} +else +{ + @("rev-list", "--reverse", "$baseTag..HEAD") +} + +$commits = @(& git @commitArguments) + +if ($LASTEXITCODE -ne 0) +{ + throw "Unable to determine commits for the release notes." +} + +$pullRequests = @{} +$directCommits = [System.Collections.Generic.List[object]]::new() + +foreach ($commit in $commits) +{ + $sha = $commit.Trim() + + if ([string]::IsNullOrWhiteSpace($sha)) + { + continue + } + + $json = (& gh api "repos/$Repository/commits/$sha/pulls" | Out-String) + + if ($LASTEXITCODE -ne 0) + { + throw "Unable to find pull requests associated with commit $sha." + } + + $associatedPullRequests = @( + $json | ConvertFrom-Json | + Where-Object { $null -ne $_.merged_at -and $_.base.ref -eq "main" } + ) + + if ($associatedPullRequests.Count -gt 0) + { + foreach ($pullRequest in $associatedPullRequests) + { + $pullRequests[$pullRequest.number.ToString()] = $pullRequest + } + + continue + } + + $subject = (& git show -s --format=%s $sha | Out-String).Trim() + $shortSha = (& git rev-parse --short $sha | Out-String).Trim() + + $directCommits.Add([pscustomobject]@{ + Sha = $shortSha + Subject = $subject + }) +} + +$orderedPullRequests = @( + $pullRequests.Values | + Sort-Object @{ Expression = { [DateTime]$_.merged_at } }, @{ Expression = { [int]$_.number } } +) + +$releaseUrl = "https://github.com/$Repository/releases/tag/v$Version" +$notes = [System.Collections.Generic.List[string]]::new() +$notes.Add("# Elemental [**$Version**]($releaseUrl)") +$notes.Add("") +$notes.Add($rangeDescription) +$notes.Add("") + +if ($orderedPullRequests.Count -gt 0) +{ + $notes.Add("## Pull requests") + $notes.Add("") + + foreach ($pullRequest in $orderedPullRequests) + { + $notes.Add("### [#$($pullRequest.number)]($($pullRequest.html_url)) — $($pullRequest.title)") + $notes.Add("") + + if ([string]::IsNullOrWhiteSpace($pullRequest.body)) + { + $notes.Add("_No description provided._") + } + else + { + $notes.Add($pullRequest.body.Trim()) + } + + $notes.Add("") + } +} + +if ($directCommits.Count -gt 0) +{ + $notes.Add("## Direct commits") + $notes.Add("") + + foreach ($commit in $directCommits) + { + $notes.Add("- ``$($commit.Sha)`` $($commit.Subject)") + } + + $notes.Add("") +} + +if ($orderedPullRequests.Count -eq 0 -and $directCommits.Count -eq 0) +{ + $notes.Add("No changes were found for this release range.") + $notes.Add("") +} + +$parentDirectory = Split-Path -Parent $OutputPath + +if (![string]::IsNullOrWhiteSpace($parentDirectory)) +{ + New-Item -ItemType Directory -Path $parentDirectory -Force | Out-Null +} + +$notes | Set-Content -Path $OutputPath -Encoding utf8 From 339895f727015bb3d1c8c533f86833b3d90ac7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:40:26 +0200 Subject: [PATCH 05/10] Run build CI only for pull requests --- .github/workflows/build-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 37bb2721..0e6c337a 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -1,9 +1,6 @@ name: Build CI on: - push: - branches: - - main pull_request: jobs: From b2f55576fda90203a134a4c79c969d0fb99b1a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:40:39 +0200 Subject: [PATCH 06/10] Inject release build number into build artifacts --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09058670..2c824059 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,10 @@ on: architecture: required: true type: string + build_number: + required: false + type: string + default: local jobs: build: @@ -21,6 +25,10 @@ jobs: with: submodules: recursive + - name: Apply build version + shell: pwsh + run: ./.github/scripts/apply-version.ps1 -BuildNumber "${{ inputs.build_number }}" + - name: Setup Dependencies (Windows) if: inputs.platform == 'win' uses: TheMrMilchmann/setup-msvc-dev@v4 From 48546232d841f9a843445482f521fa319c7ac72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:40:58 +0200 Subject: [PATCH 07/10] Add automatic main release workflow --- .github/workflows/release.yml | 173 ++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..56010821 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,173 @@ +name: Release + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: read + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + version: + name: Resolve version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + build_number: ${{ steps.version.outputs.build_number }} + prerelease: ${{ steps.version.outputs.prerelease }} + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Resolve release version + id: version + shell: pwsh + run: ./.github/scripts/resolve-version.ps1 + + build: + needs: [version] + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(vars.Platforms) }} + uses: ./.github/workflows/build.yml + name: build + secrets: inherit + with: + platform: ${{ matrix.platform }} + architecture: ${{ matrix.architecture }} + build_number: ${{ needs.version.outputs.build_number }} + + run_tests: + needs: [build] + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(vars.Platforms) }} + uses: ./.github/workflows/run-tests.yml + name: run-tests + secrets: inherit + with: + platform: ${{ matrix.platform }} + architecture: ${{ matrix.architecture }} + + release: + name: Publish release + needs: [version, build, run_tests] + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + pattern: "*" + path: ${{ runner.temp }}/artifacts + merge-multiple: true + + - name: Generate release notes + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $notesPath = Join-Path $env:RUNNER_TEMP "release-notes.md" + $version = "${{ needs.version.outputs.version }}" + + if ("${{ needs.version.outputs.prerelease }}" -eq "true") + { + & ./.github/scripts/release-notes.ps1 ` + -Version $version ` + -Repository "${{ github.repository }}" ` + -OutputPath $notesPath + } + else + { + $stableNotesPath = ".github/release-notes/$version.md" + + if (!(Test-Path -Path $stableNotesPath)) + { + throw "Stable release notes are missing: $stableNotesPath" + } + + Copy-Item -Path $stableNotesPath -Destination $notesPath + } + + - name: Verify release identity is unused + shell: pwsh + run: | + $tag = "${{ needs.version.outputs.tag }}" + $existingTag = (& git ls-remote --tags origin "refs/tags/$tag" | Out-String).Trim() + + if (![string]::IsNullOrWhiteSpace($existingTag)) + { + throw "Release identity already exists: $tag" + } + + - name: Create tag and GitHub release + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $version = "${{ needs.version.outputs.version }}" + $tag = "${{ needs.version.outputs.tag }}" + $artifactPath = Join-Path $env:RUNNER_TEMP "artifacts" + $notesPath = Join-Path $env:RUNNER_TEMP "release-notes.md" + + $assets = @( + Get-ChildItem -Path $artifactPath -File | + Where-Object { $_.Name -match '^(elemental(?:-tools)?|samples)\.' } | + Sort-Object Name + ) + + if ($assets.Count -eq 0) + { + throw "No release assets were found." + } + + $arguments = @( + "release", + "create", + $tag + ) + + foreach ($asset in $assets) + { + $arguments += $asset.FullName + } + + $arguments += @( + "--repo", + "${{ github.repository }}", + "--title", + "Elemental $version", + "--notes-file", + $notesPath, + "--target", + "${{ github.sha }}" + ) + + if ("${{ needs.version.outputs.prerelease }}" -eq "true") + { + $arguments += "--prerelease" + } + + & gh @arguments + + if ($LASTEXITCODE -ne 0) + { + exit $LASTEXITCODE + } From f66dcd8c5b85b785077771d08dcee8386742f508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:42:58 +0200 Subject: [PATCH 08/10] Handle Windows line endings in version injection --- .github/scripts/apply-version.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/apply-version.ps1 b/.github/scripts/apply-version.ps1 index 6d034de3..b9b45b73 100644 --- a/.github/scripts/apply-version.ps1 +++ b/.github/scripts/apply-version.ps1 @@ -62,8 +62,8 @@ else } $header = Get-Content -Path $HeaderPath -Raw -$versionCommentPattern = '(?m)^// Version: .*$' -$versionMacroPattern = '(?m)^#define ELEM_VERSION_LABEL ".*"$' +$versionCommentPattern = '(?m)^// Version: [^\r\n]*(?=\r?$)' +$versionMacroPattern = '(?m)^#define ELEM_VERSION_LABEL "[^"\r\n]*"(?=\r?$)' if ([Regex]::Matches($header, $versionCommentPattern).Count -ne 1) { From 77f232c6cd6d8f74b34efd28b18b83f9d87d88a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:45:57 +0200 Subject: [PATCH 09/10] Validate release metadata on pull requests --- .github/workflows/build-ci.yml | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 0e6c337a..d2f56627 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -4,6 +4,31 @@ on: pull_request: jobs: + validate_release: + runs-on: ubuntu-latest + name: validate-release + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Resolve next release version + id: version + shell: pwsh + run: ./.github/scripts/resolve-version.ps1 + + - name: Generate release notes preview + if: steps.version.outputs.prerelease == 'true' + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + & ./.github/scripts/release-notes.ps1 ` + -Version "${{ steps.version.outputs.version }}" ` + -Repository "${{ github.repository }}" ` + -OutputPath (Join-Path $env:RUNNER_TEMP "release-notes.md") + build: strategy: fail-fast: false @@ -33,12 +58,13 @@ jobs: if: always() runs-on: ubuntu-latest name: CI Build Check - needs: [build, run_tests] + needs: [validate_release, build, run_tests] steps: - - name: Check build and test results + - name: Check build, test and release validation results if: >- ${{ - needs.build.result != 'success' + needs.validate_release.result != 'success' + || needs.build.result != 'success' || needs.run_tests.result != 'success' }} run: exit 1 From 1d305fb4a758ed1936fe1d3aeb6a144ecfb2a522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Decroy=C3=A8re?= Date: Sat, 29 Aug 2026 12:46:39 +0200 Subject: [PATCH 10/10] Avoid failing Git probe for unused release tag --- .github/scripts/resolve-version.ps1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/scripts/resolve-version.ps1 b/.github/scripts/resolve-version.ps1 index 863a4684..3ae64dfd 100644 --- a/.github/scripts/resolve-version.ps1 +++ b/.github/scripts/resolve-version.ps1 @@ -39,13 +39,20 @@ if (![string]::IsNullOrWhiteSpace($stage) -and $stage -notmatch '^(dev|alpha|bet throw "Unsupported Elemental release stage: $stage" } +$tags = @(& git tag --merged HEAD --list "v*") + +if ($LASTEXITCODE -ne 0) +{ + throw "Unable to inspect existing Elemental release tags." +} + $productVersion = "$major.$minor.$patch" $isPrerelease = ![string]::IsNullOrWhiteSpace($stage) if ($isPrerelease) { $buildNumbers = @( - & git tag --merged HEAD --list "v*" | + $tags | ForEach-Object { if ($_ -match '^v\d+\.\d+\.\d+-(?:dev|alpha|beta|rc)\.(?\d+)$') { @@ -54,11 +61,6 @@ if ($isPrerelease) } ) - if ($LASTEXITCODE -ne 0) - { - throw "Unable to inspect existing Elemental release tags." - } - $buildNumber = if ($buildNumbers.Count -eq 0) { 1 @@ -84,9 +86,8 @@ else } $tag = "v$version" -& git rev-parse --verify --quiet "refs/tags/$tag" *> $null -if ($LASTEXITCODE -eq 0) +if ($tags -contains $tag) { throw "Release identity already exists: $tag" }