From 036fa0dd9af860f051819720324cb3ee681c3c40 Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Fri, 24 Jul 2026 11:59:42 +0200 Subject: [PATCH 1/9] Add initial version --- .github/workflows/Master Workflow.yml | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index 13b51f4..009ee05 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -1000,6 +1000,78 @@ jobs: name: SignedInstallers path: _SignedInstallers + # ════════════════════════════════════════════════════════════════ + # DxM Release (on tag only) + # Pre-release tags publish to QA; stable tags publish to QA and production. + # ════════════════════════════════════════════════════════════════ + + upload_dxms: + name: Upload DxM Installers (${{ matrix.environment }}) + runs-on: ubuntu-latest + if: github.ref_type == 'tag' && needs.package_windows.outputs.has-msi == 'true' && needs.check_oidc.outputs.use-oidc == 'true' + needs: [package_windows, check_oidc] + permissions: + contents: read + id-token: write + actions: read + strategy: + fail-fast: false + matrix: + environment: ${{ fromJSON(contains(github.ref_name, '-') && '["qa"]' || '["qa","prod"]') }} + steps: + - name: Azure Login + uses: azure/login@v3 + with: + client-id: ${{ needs.check_oidc.outputs.client-id }} + tenant-id: ${{ needs.check_oidc.outputs.tenant-id }} + subscription-id: ${{ needs.check_oidc.outputs.subscription-id }} + + - name: Load DxM release secrets + uses: SkylineCommunications/_ReusableWorkflows/.github/actions/load-secrets@main + with: + use-oidc: ${{ needs.check_oidc.outputs.use-oidc }} + secret-names: | + azure-token + skylinecommunications-github-token + skylinecommunicationscore-github-token + dxm-release-service-secret-${{ matrix.environment }} + dxm-release-artifact-access-key-${{ matrix.environment }} + + - uses: actions/checkout@v7 + + - name: Setup Skyline NuGet sources + uses: SkylineCommunications/_ReusableWorkflows/.github/actions/setup-skyline-nuget-sources@main + with: + skylinecommunications-github-token: ${{ env.SKYLINECOMMUNICATIONS_GITHUB_TOKEN }} + skylinecommunicationscore-github-token: ${{ env.SKYLINECOMMUNICATIONSCORE_GITHUB_TOKEN }} + azure-token: ${{ env.AZURE_TOKEN }} + + - name: Install DxM release tool + run: dotnet tool install Skyline.DataMiner.CICD.Tools.DxmRelease --global --prerelease + shell: bash + + - name: Download signed installers + uses: actions/download-artifact@v8 + with: + name: SignedInstallers + path: _SignedInstallers + + - name: Publish DxM installers + env: + RELEASE_ENVIRONMENT: ${{ matrix.environment }} + run: | + environment_suffix="${RELEASE_ENVIRONMENT^^}" + service_secret_name="DXM_RELEASE_SERVICE_SECRET_${environment_suffix}" + artifact_access_key_name="DXM_RELEASE_ARTIFACT_ACCESS_KEY_${environment_suffix}" + + dataminer-dxm-release \ + --dxm-id-file dxm-id-file.json \ + --path-to-installers _SignedInstallers \ + --secret "${!service_secret_name}" \ + --environment "$RELEASE_ENVIRONMENT" \ + --artifact-storage-access-key "${!artifact_access_key_name}" + shell: bash + # ════════════════════════════════════════════════════════════════ # NuGet Push (on tag only) # ════════════════════════════════════════════════════════════════ From db5d5c404e3658e375b63f8a7d54536dfc793e9b Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Wed, 29 Jul 2026 11:07:55 +0200 Subject: [PATCH 2/9] Adapt to not needing the secrets --- .github/workflows/Master Workflow.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index 009ee05..6bd02c3 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -1026,7 +1026,7 @@ jobs: tenant-id: ${{ needs.check_oidc.outputs.tenant-id }} subscription-id: ${{ needs.check_oidc.outputs.subscription-id }} - - name: Load DxM release secrets + - name: Load package feed secrets uses: SkylineCommunications/_ReusableWorkflows/.github/actions/load-secrets@main with: use-oidc: ${{ needs.check_oidc.outputs.use-oidc }} @@ -1034,8 +1034,6 @@ jobs: azure-token skylinecommunications-github-token skylinecommunicationscore-github-token - dxm-release-service-secret-${{ matrix.environment }} - dxm-release-artifact-access-key-${{ matrix.environment }} - uses: actions/checkout@v7 @@ -1060,16 +1058,10 @@ jobs: env: RELEASE_ENVIRONMENT: ${{ matrix.environment }} run: | - environment_suffix="${RELEASE_ENVIRONMENT^^}" - service_secret_name="DXM_RELEASE_SERVICE_SECRET_${environment_suffix}" - artifact_access_key_name="DXM_RELEASE_ARTIFACT_ACCESS_KEY_${environment_suffix}" - dataminer-dxm-release \ --dxm-id-file dxm-id-file.json \ --path-to-installers _SignedInstallers \ - --secret "${!service_secret_name}" \ - --environment "$RELEASE_ENVIRONMENT" \ - --artifact-storage-access-key "${!artifact_access_key_name}" + --environment "$RELEASE_ENVIRONMENT" shell: bash # ════════════════════════════════════════════════════════════════ From d675f20e6db4e2f9b02d6e7b789d12b048621c37 Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Wed, 29 Jul 2026 12:21:06 +0200 Subject: [PATCH 3/9] Support dev environment --- .github/workflows/Master Workflow.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index 6bd02c3..0eebfaa 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -1002,7 +1002,8 @@ jobs: # ════════════════════════════════════════════════════════════════ # DxM Release (on tag only) - # Pre-release tags publish to QA; stable tags publish to QA and production. + # Add future non-production targets (test1/test2/test3) to both arrays below. + # Production remains stable-only. # ════════════════════════════════════════════════════════════════ upload_dxms: @@ -1017,7 +1018,7 @@ jobs: strategy: fail-fast: false matrix: - environment: ${{ fromJSON(contains(github.ref_name, '-') && '["qa"]' || '["qa","prod"]') }} + environment: ${{ fromJSON(contains(github.ref_name, '-') && '["dev","qa"]' || '["dev","qa","prod"]') }} steps: - name: Azure Login uses: azure/login@v3 From 2ac07c59a566df55029701d39b929e3ff64117dd Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Wed, 29 Jul 2026 13:17:36 +0200 Subject: [PATCH 4/9] Support debug option --- .github/workflows/Master Workflow.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index 0eebfaa..e1328ab 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -1058,8 +1058,10 @@ jobs: - name: Publish DxM installers env: RELEASE_ENVIRONMENT: ${{ matrix.environment }} + DEBUG_FLAG: ${{ inputs.debug }} run: | dataminer-dxm-release \ + --debug $DEBUG_FLAG \ --dxm-id-file dxm-id-file.json \ --path-to-installers _SignedInstallers \ --environment "$RELEASE_ENVIRONMENT" From 3fa45e3717140e9120863f1a35ee7477f3a988e9 Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Thu, 30 Jul 2026 11:17:10 +0200 Subject: [PATCH 5/9] Deal with differences between building a solution and a project --- .github/workflows/Master Workflow.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index e1328ab..ced9735 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -875,17 +875,27 @@ jobs: NUMERIC_VERSION: ${{ needs.determine_version.outputs.numeric-version }} CONFIGURATION: ${{ inputs.configuration }} run: | - # Rebuild ONLY the WiX projects so the MSI is built from the signed DLLs/EXEs. - # BuildProjectReferences=false keeps the referenced projects from being - # recompiled (which would overwrite the signatures). - $solutionDir = Split-Path "$env:SOLUTION_PATH" -Parent - $wixProjects = dotnet sln "$env:SOLUTION_PATH" list | Select-String '\.wixproj' | ForEach-Object { $_.ToString().Trim() } - foreach ($wixProject in $wixProjects) { - dotnet build (Join-Path $solutionDir ($wixProject -replace '\\', '/')) ` + # Preserve the original solution's configuration/platform mappings while + # limiting the rebuild to WiX projects. Building the solution ensures WiX + # uses the same output paths as the initial solution build. + $solutionPath = (Resolve-Path "$env:SOLUTION_PATH").Path + $solutionDir = Split-Path $solutionPath -Parent + $temporarySolutionPath = Join-Path $solutionDir ".wix-build-$([guid]::NewGuid())$(Split-Path $solutionPath -Extension)" + + Copy-Item $solutionPath $temporarySolutionPath + try { + $nonWixProjects = dotnet sln "$temporarySolutionPath" list | Select-Object -Skip 2 | Where-Object { $_ -and $_ -notlike '*.wixproj' } + foreach ($nonWixProject in $nonWixProjects) { + dotnet sln "$temporarySolutionPath" remove (Join-Path $solutionDir ($nonWixProject -replace '\\', '/')) + } + + dotnet build "$temporarySolutionPath" ` -p:Version="$env:VERSION" ` -p:ProductVersion="$env:NUMERIC_VERSION" ` -p:BuildProjectReferences=false ` --configuration "$env:CONFIGURATION" + } finally { + Remove-Item $temporarySolutionPath -Force -ErrorAction SilentlyContinue } shell: pwsh From e46ed605349163c13bf85f4d82185f0c0325bc7a Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Thu, 30 Jul 2026 16:14:27 +0200 Subject: [PATCH 6/9] Use released version --- .github/workflows/Master Workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index ced9735..e4a8c74 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -1056,7 +1056,7 @@ jobs: azure-token: ${{ env.AZURE_TOKEN }} - name: Install DxM release tool - run: dotnet tool install Skyline.DataMiner.CICD.Tools.DxmRelease --global --prerelease + run: dotnet tool install Skyline.DataMiner.CICD.Tools.DxmRelease --global --version 2.* shell: bash - name: Download signed installers From ede9204a3cf72932a19cb84a6529edbfd4be4694 Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Fri, 31 Jul 2026 09:32:12 +0200 Subject: [PATCH 7/9] Text changes for pre-releases --- .github/actions/compute-next-version/README.md | 6 +++--- .github/actions/compute-next-version/action.yml | 4 ++-- .github/actions/compute-next-version/test.ps1 | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/compute-next-version/README.md b/.github/actions/compute-next-version/README.md index 32d2343..4d4c4ec 100644 --- a/.github/actions/compute-next-version/README.md +++ b/.github/actions/compute-next-version/README.md @@ -13,13 +13,13 @@ bumping from the same final version. | --- | --- | --- | | `change-type` | no | The bump to apply: `Patch`, `Minor`, or `Major`. Empty resolves to `Patch`. | | `mode` | no | `release` (bare version) or `prerelease` (suffix appended). Defaults to `release`. | -| `suffix` | no | Pre-release suffix body (e.g. `dev-myfeature.42`). Used only when `mode` is `prerelease`. | +| `suffix` | no | Pre-release suffix body (e.g. `a1b2c3d4.42`). Used only when `mode` is `prerelease`. | ## Outputs | Output | Description | | --- | --- | -| `version` | The computed version, e.g. `1.4.0` or `1.4.0-dev-myfeature.42`. | +| `version` | The computed version, e.g. `1.4.0` or `1.4.0-a1b2c3d4.42`. | | `latest-tag` | The latest final tag the bump was computed from, or empty when none exists. | ## Rules @@ -39,7 +39,7 @@ bumping from the same final version. with: change-type: ${{ steps.references.outputs.change-type }} mode: prerelease - suffix: dev-myfeature.42 + suffix: a1b2c3d4.42 ``` The checkout that precedes this action must fetch tags (`fetch-depth: 0`) so the base tag can be diff --git a/.github/actions/compute-next-version/action.yml b/.github/actions/compute-next-version/action.yml index 545b020..03e617a 100644 --- a/.github/actions/compute-next-version/action.yml +++ b/.github/actions/compute-next-version/action.yml @@ -15,13 +15,13 @@ inputs: required: false default: release suffix: - description: The pre-release suffix body (e.g. dev-myfeature.42). Used only when mode is prerelease. + description: The pre-release suffix body (e.g. a1b2c3d4.42). Used only when mode is prerelease. required: false default: "" outputs: version: - description: The computed version, e.g. 1.4.0 or 1.4.0-dev-myfeature.42. + description: The computed version, e.g. 1.4.0 or 1.4.0-a1b2c3d4.42. value: ${{ steps.compute.outputs.version }} latest-tag: description: The latest final tag the bump was computed from, or empty when no final tag exists. diff --git a/.github/actions/compute-next-version/test.ps1 b/.github/actions/compute-next-version/test.ps1 index 4a78983..1d810d6 100644 --- a/.github/actions/compute-next-version/test.ps1 +++ b/.github/actions/compute-next-version/test.ps1 @@ -82,7 +82,7 @@ try { Invoke-Case -Name 'Major bump on 1.3.5' -Tags @('1.3.5') -ChangeType 'Major' -Mode 'release' -Suffix '' -ExpectedVersion '2.0.0' -ExpectedLatestTag '1.3.5' Invoke-Case -Name 'Patch bump, no tags' -Tags @() -ChangeType 'Patch' -Mode 'release' -Suffix '' -ExpectedVersion '0.0.1' -ExpectedLatestTag '' Invoke-Case -Name 'Minor bump ignores pre-release' -Tags @('1.3.5', '1.4.0-dev.1') -ChangeType 'Minor' -Mode 'release' -Suffix '' -ExpectedVersion '1.4.0' -ExpectedLatestTag '1.3.5' - Invoke-Case -Name 'Minor pre-release with suffix' -Tags @('1.3.5') -ChangeType 'Minor' -Mode 'prerelease' -Suffix 'dev-x.42' -ExpectedVersion '1.4.0-dev-x.42' -ExpectedLatestTag '1.3.5' + Invoke-Case -Name 'Minor pre-release with suffix' -Tags @('1.3.5') -ChangeType 'Minor' -Mode 'prerelease' -Suffix 'a1b2c3d4.42' -ExpectedVersion '1.4.0-a1b2c3d4.42' -ExpectedLatestTag '1.3.5' Invoke-Case -Name 'Empty change-type defaults Patch' -Tags @('1.3.5') -ChangeType '' -Mode 'release' -Suffix '' -ExpectedVersion '1.3.6' -ExpectedLatestTag '1.3.5' # Extra guards beyond the plan corpus. From f91c6f20b31a069da13b22b3e54566e0758cd601 Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Fri, 31 Jul 2026 10:51:44 +0200 Subject: [PATCH 8/9] Refine the ProductVersion --- .github/actions/determine-version/README.md | 25 ++++++++--- .github/actions/determine-version/action.yml | 13 ++++-- .../determine-version/determine-version.ps1 | 26 ++++++++++- .github/actions/determine-version/test.ps1 | 43 ++++++++++++------- .github/workflows/Master Workflow.yml | 24 ++++++++--- .github/workflows/Test composite actions.yml | 24 +++++++++-- 6 files changed, 121 insertions(+), 34 deletions(-) diff --git a/.github/actions/determine-version/README.md b/.github/actions/determine-version/README.md index 65dee4a..3306549 100644 --- a/.github/actions/determine-version/README.md +++ b/.github/actions/determine-version/README.md @@ -1,9 +1,9 @@ # determine-version -Determines the **single canonical build version** shared by every job in the Master Workflow, and a -strict 4-field numeric companion for consumers that reject SemVer suffixes. +Determines the **single canonical build version** shared by every job in the Master Workflow, a +strict 4-field numeric companion for assemblies, and an MSI-specific ProductVersion. -- **Tag builds** use the tag name verbatim (e.g. `2.3.1`, `1.4.0-dev-myfeature.42`). +- **Tag builds** use the tag name verbatim (e.g. `2.3.1`, `1.4.0-a1b2c3d4.42`). - **Branch builds** keep the legacy `0.0.` in all modes. ## Inputs @@ -19,7 +19,9 @@ strict 4-field numeric companion for consumers that reject SemVer suffixes. | Output | Suffix allowed? | Description | | --- | :--: | --- | | `version` | ✔ | Full SemVer — for MSBuild `Version` / `PackageVersion`, NuGet, `.dmapp` / Catalog, `.deb` (after its own `~` normalisation), DxM release. | -| `numeric-version` | ✘ | Strict 4-field `major.minor.patch.` (every field wrapped `% 65535`) — for `AssemblyVersion` / `FileVersion` and the WiX MSI `ProductVersion`. `` = the run number, or the version's own 4th field when it already has one. | +| `numeric-version` | ✘ | Strict 4-field `major.minor.patch.` (every field wrapped `% 65535`) — for `AssemblyVersion` / `FileVersion`. `` = the run number, or the version's own 4th field when it already has one. | +| `product-version` | ✘ | Stable three-part tags use `major.minor.build`; prereleases, branches, and explicit four-part tags retain `numeric-version`'s fourth field for Skyline release classification. | +| `product-version-valid` | — | `true` when MSI limits are met: major/minor ≤ 255 and build ≤ 65,535. | ## Rules @@ -35,9 +37,18 @@ strict 4-field numeric companion for consumers that reject SemVer suffixes. whose patch field is the (unbounded) run number. - A tag whose core is not `major.minor.patch` or `major.minor.patch.build` (e.g. `release-1`, `1.2`) fails the action with a clear error — such a version could not build anyway. -- **MSI caveat:** Windows Installer compares only the first **three** fields of `ProductVersion` for - upgrade detection; the 4th (run number) is parsed but ignored. A real upgrade must still move - `major.minor.patch` (the tag / auto-tag bump already does). +- Stable three-part tags use the normalized three-field numeric core (`1.2.3`). Pre-release tags + cannot carry their SemVer suffix in ProductVersion, so they use the suffix-free four-field + `numeric-version` (`1.2.3-sha.run` → `1.2.3.`). Branch builds and explicit legacy + four-part tags also retain that fourth field. Skyline tooling uses this shape to distinguish a + prerelease/non-final package from a stable release. +- MSI limits are independent from assembly limits: **major ≤ 255, minor ≤ 255, build ≤ 65,535**. + `product-version-valid` exposes the result. The Master Workflow fails clearly on an invalid value + only when a WiX project exists, so a non-MSI repository may still use a date-style tag such as + `2026.07.08.230`. +- Windows Installer itself ignores the fourth field for upgrade comparison, so a real MSI upgrade + must still change at least one of the first three fields. Retaining field four is a Skyline package + convention; it does not change native MSI upgrade ordering. ## Usage diff --git a/.github/actions/determine-version/action.yml b/.github/actions/determine-version/action.yml index 39164eb..ca1cdfd 100644 --- a/.github/actions/determine-version/action.yml +++ b/.github/actions/determine-version/action.yml @@ -5,8 +5,9 @@ description: > 4-field numeric version (pre-release/build suffix stripped; the run number is appended as 4th field unless the version already has one, e.g. date-based tags; every field wrapped into the assembly-metadata range - with % 65535) for MSBuild AssemblyVersion/FileVersion and the WiX MSI - ProductVersion. + with % 65535) for MSBuild AssemblyVersion/FileVersion. Also derives an + MSI ProductVersion (three fields for stable releases, four for prereleases) + plus an MSI-specific bounds result. inputs: ref-type: @@ -21,11 +22,17 @@ inputs: outputs: version: - description: The full SemVer version — the tag name on tag builds (e.g. `2.3.1`, `1.4.0-dev-myfeature.42`), `0.0.` on branch builds. + description: The full SemVer version — the tag name on tag builds (e.g. `2.3.1`, `1.4.0-a1b2c3d4.42`), `0.0.` on branch builds. value: ${{ steps.determine.outputs.version }} numeric-version: description: Strict 4-field `major.minor.patch.` version with any pre-release/build suffix stripped; `` is the run number, or the version's own 4th field when it already has one; every field is wrapped into the valid assembly-metadata range (`% 65535`, max 65534). value: ${{ steps.determine.outputs.numeric-version }} + product-version: + description: MSI ProductVersion; stable three-part tags use `major.minor.build`, while prereleases, branches, and explicit four-part tags retain numeric-version's fourth field. + value: ${{ steps.determine.outputs.product-version }} + product-version-valid: + description: Whether ProductVersion satisfies MSI limits (major/minor <= 255 and build <= 65535). + value: ${{ steps.determine.outputs.product-version-valid }} runs: using: composite diff --git a/.github/actions/determine-version/determine-version.ps1 b/.github/actions/determine-version/determine-version.ps1 index 704a7ea..9f25090 100644 --- a/.github/actions/determine-version/determine-version.ps1 +++ b/.github/actions/determine-version/determine-version.ps1 @@ -49,9 +49,33 @@ $numericVersion = '{0}.{1}.{2}.{3}' -f ` ([long]$match.Groups[3].Value % 65535), ` ($fourthField % 65535) -Write-Host "Determined version '$version' and numeric-version '$numericVersion' (ref-type: $refType, run-number: $runNumber)." +# Windows Installer evaluates major.minor.build and ignores a fourth field. We retain +# that fourth field for prerelease/branch identification in our package conventions, +# while stable three-part tags use the natural three-field ProductVersion. +# MSI limits differ from assembly metadata: major/minor <= 255 and build <= 65535. +$productMajor = [long]$match.Groups[1].Value +$productMinor = [long]$match.Groups[2].Value +$productBuild = [long]$match.Groups[3].Value +$isStableThreePartTag = $refType -eq 'tag' -and -not $version.Contains('-') -and -not $match.Groups[4].Success +$productVersion = if ($isStableThreePartTag) { + '{0}.{1}.{2}' -f $productMajor, $productMinor, $productBuild +} elseif ($refType -eq 'tag') { + $numericFourthField = $numericVersion.Split('.')[3] + '{0}.{1}.{2}.{3}' -f $productMajor, $productMinor, $productBuild, $numericFourthField +} else { + $numericVersion +} +$productVersionValid = if ($refType -eq 'tag') { + $productMajor -le 255 -and $productMinor -le 255 -and $productBuild -le 65535 +} else { + $true +} + +Write-Host "Determined version '$version', numeric-version '$numericVersion', and product-version '$productVersion' (valid: $($productVersionValid.ToString().ToLowerInvariant()), ref-type: $refType, run-number: $runNumber)." @( "version=$version" "numeric-version=$numericVersion" + "product-version=$productVersion" + "product-version-valid=$($productVersionValid.ToString().ToLowerInvariant())" ) | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 diff --git a/.github/actions/determine-version/test.ps1 b/.github/actions/determine-version/test.ps1 index b4b7d79..d0ab1a5 100644 --- a/.github/actions/determine-version/test.ps1 +++ b/.github/actions/determine-version/test.ps1 @@ -41,7 +41,9 @@ function Invoke-Case { [AllowEmptyString()][string]$RefName, [Parameter(Mandatory)][string]$RunNumber, [Parameter(Mandatory)][string]$ExpectedVersion, - [Parameter(Mandatory)][string]$ExpectedNumericVersion + [Parameter(Mandatory)][string]$ExpectedNumericVersion, + [Parameter(Mandatory)][string]$ExpectedProductVersion, + [bool]$ExpectedProductVersionValid = $true ) $outputFile = Join-Path -Path $script:temporaryDirectory -ChildPath "$([System.Guid]::NewGuid()).txt" @@ -57,9 +59,13 @@ function Invoke-Case { $version = Get-OutputValue -Name 'version' -Path $outputFile $numericVersion = Get-OutputValue -Name 'numeric-version' -Path $outputFile + $productVersion = Get-OutputValue -Name 'product-version' -Path $outputFile + $productVersionValid = Get-OutputValue -Name 'product-version-valid' -Path $outputFile Assert-Equal -Actual $version -Expected $ExpectedVersion -Label "${Name} (version)" Assert-Equal -Actual $numericVersion -Expected $ExpectedNumericVersion -Label "${Name} (numeric-version)" + Assert-Equal -Actual $productVersion -Expected $ExpectedProductVersion -Label "${Name} (product-version)" + Assert-Equal -Actual $productVersionValid -Expected $ExpectedProductVersionValid.ToString().ToLowerInvariant() -Label "${Name} (product-version-valid)" Write-Host "PASS: ${Name}" } catch { @@ -103,26 +109,33 @@ function Invoke-FailureCase { try { # Branch builds keep the legacy 0.0. in all modes. - Invoke-Case -Name 'Branch build' -RefType 'branch' -RefName 'dev/my-feature' -RunNumber '42' -ExpectedVersion '0.0.42' -ExpectedNumericVersion '0.0.42.42' - Invoke-Case -Name 'Branch build, large run number' -RefType 'branch' -RefName 'main' -RunNumber '70000' -ExpectedVersion '0.0.70000' -ExpectedNumericVersion '0.0.4465.4465' + Invoke-Case -Name 'Branch build' -RefType 'branch' -RefName 'dev/my-feature' -RunNumber '42' -ExpectedVersion '0.0.42' -ExpectedNumericVersion '0.0.42.42' -ExpectedProductVersion '0.0.42.42' + Invoke-Case -Name 'Branch build, large run number' -RefType 'branch' -RefName 'main' -RunNumber '70000' -ExpectedVersion '0.0.70000' -ExpectedNumericVersion '0.0.4465.4465' -ExpectedProductVersion '0.0.4465.4465' # Tag builds use the tag name; numeric-version strips the suffix + appends the run number. - Invoke-Case -Name 'Final release tag' -RefType 'tag' -RefName '1.2.3' -RunNumber '7' -ExpectedVersion '1.2.3' -ExpectedNumericVersion '1.2.3.7' - Invoke-Case -Name 'Pre-release tag' -RefType 'tag' -RefName '1.4.0-dev-myfeature.42' -RunNumber '99' -ExpectedVersion '1.4.0-dev-myfeature.42' -ExpectedNumericVersion '1.4.0.99' - Invoke-Case -Name 'Build-metadata tag' -RefType 'tag' -RefName '2.3.1+build.5' -RunNumber '3' -ExpectedVersion '2.3.1+build.5' -ExpectedNumericVersion '2.3.1.3' - Invoke-Case -Name 'Pre-release + metadata tag' -RefType 'tag' -RefName '1.2.3-rc.1+meta' -RunNumber '8' -ExpectedVersion '1.2.3-rc.1+meta' -ExpectedNumericVersion '1.2.3.8' - Invoke-Case -Name 'Leading v prefix tolerated' -RefType 'tag' -RefName 'v1.2.3' -RunNumber '5' -ExpectedVersion 'v1.2.3' -ExpectedNumericVersion '1.2.3.5' + Invoke-Case -Name 'Final release tag' -RefType 'tag' -RefName '1.2.3' -RunNumber '7' -ExpectedVersion '1.2.3' -ExpectedNumericVersion '1.2.3.7' -ExpectedProductVersion '1.2.3' + Invoke-Case -Name 'Pre-release tag' -RefType 'tag' -RefName '1.4.0-a1b2c3d4.42' -RunNumber '99' -ExpectedVersion '1.4.0-a1b2c3d4.42' -ExpectedNumericVersion '1.4.0.99' -ExpectedProductVersion '1.4.0.99' + Invoke-Case -Name 'Build-metadata tag' -RefType 'tag' -RefName '2.3.1+build.5' -RunNumber '3' -ExpectedVersion '2.3.1+build.5' -ExpectedNumericVersion '2.3.1.3' -ExpectedProductVersion '2.3.1' + Invoke-Case -Name 'Pre-release + metadata tag' -RefType 'tag' -RefName '1.2.3-rc.1+meta' -RunNumber '8' -ExpectedVersion '1.2.3-rc.1+meta' -ExpectedNumericVersion '1.2.3.8' -ExpectedProductVersion '1.2.3.8' + Invoke-Case -Name 'Leading v prefix tolerated' -RefType 'tag' -RefName 'v1.2.3' -RunNumber '5' -ExpectedVersion 'v1.2.3' -ExpectedNumericVersion '1.2.3.5' -ExpectedProductVersion '1.2.3' # 4-part cores (e.g. date-based tags) keep their own 4th field — the run number is not appended. - Invoke-Case -Name 'Four-part date tag' -RefType 'tag' -RefName '2026.07.08.230' -RunNumber '44' -ExpectedVersion '2026.07.08.230' -ExpectedNumericVersion '2026.7.8.230' - Invoke-Case -Name 'Four-part tag keeps 4th field' -RefType 'tag' -RefName '1.2.3.4' -RunNumber '99' -ExpectedVersion '1.2.3.4' -ExpectedNumericVersion '1.2.3.4' - Invoke-Case -Name 'Four-part tag wraps 4th field' -RefType 'tag' -RefName '1.2.3.70000' -RunNumber '7' -ExpectedVersion '1.2.3.70000' -ExpectedNumericVersion '1.2.3.4465' + Invoke-Case -Name 'Four-part date tag' -RefType 'tag' -RefName '2026.07.08.230' -RunNumber '44' -ExpectedVersion '2026.07.08.230' -ExpectedNumericVersion '2026.7.8.230' -ExpectedProductVersion '2026.7.8.230' -ExpectedProductVersionValid $false + Invoke-Case -Name 'Four-part tag keeps 4th field' -RefType 'tag' -RefName '1.2.3.4' -RunNumber '99' -ExpectedVersion '1.2.3.4' -ExpectedNumericVersion '1.2.3.4' -ExpectedProductVersion '1.2.3.4' + Invoke-Case -Name 'Four-part tag wraps 4th field' -RefType 'tag' -RefName '1.2.3.70000' -RunNumber '7' -ExpectedVersion '1.2.3.70000' -ExpectedNumericVersion '1.2.3.4465' -ExpectedProductVersion '1.2.3.4465' # Fields must be < 65535 (assembly-metadata max 65534): wrap-around % 65535, never a clamp. - Invoke-Case -Name 'Run number 65534 fits' -RefType 'tag' -RefName '1.0.0' -RunNumber '65534' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.65534' - Invoke-Case -Name 'Run number wraps at 65535' -RefType 'tag' -RefName '1.0.0' -RunNumber '65535' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.0' - Invoke-Case -Name 'Run number 65536 wraps to 1' -RefType 'tag' -RefName '1.0.0' -RunNumber '65536' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.1' - Invoke-Case -Name 'Run number above 65536' -RefType 'tag' -RefName '1.0.0' -RunNumber '70000' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.4465' + Invoke-Case -Name 'Run number 65534 fits' -RefType 'tag' -RefName '1.0.0' -RunNumber '65534' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.65534' -ExpectedProductVersion '1.0.0' + Invoke-Case -Name 'Run number wraps at 65535' -RefType 'tag' -RefName '1.0.0' -RunNumber '65535' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.0' -ExpectedProductVersion '1.0.0' + Invoke-Case -Name 'Run number 65536 wraps to 1' -RefType 'tag' -RefName '1.0.0' -RunNumber '65536' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.1' -ExpectedProductVersion '1.0.0' + Invoke-Case -Name 'Run number above 65536' -RefType 'tag' -RefName '1.0.0' -RunNumber '70000' -ExpectedVersion '1.0.0' -ExpectedNumericVersion '1.0.0.4465' -ExpectedProductVersion '1.0.0' + + # Windows Installer ProductVersion bounds: major/minor <= 255; build <= 65535. + Invoke-Case -Name 'ProductVersion maximum fields' -RefType 'tag' -RefName '255.255.65535' -RunNumber '1' -ExpectedVersion '255.255.65535' -ExpectedNumericVersion '255.255.0.1' -ExpectedProductVersion '255.255.65535' + Invoke-Case -Name 'Prerelease ProductVersion maximum' -RefType 'tag' -RefName '255.255.65535-rc.1' -RunNumber '2' -ExpectedVersion '255.255.65535-rc.1' -ExpectedNumericVersion '255.255.0.2' -ExpectedProductVersion '255.255.65535.2' + Invoke-Case -Name 'ProductVersion major out of range' -RefType 'tag' -RefName '256.1.1' -RunNumber '1' -ExpectedVersion '256.1.1' -ExpectedNumericVersion '256.1.1.1' -ExpectedProductVersion '256.1.1' -ExpectedProductVersionValid $false + Invoke-Case -Name 'ProductVersion minor out of range' -RefType 'tag' -RefName '1.256.1' -RunNumber '1' -ExpectedVersion '1.256.1' -ExpectedNumericVersion '1.256.1.1' -ExpectedProductVersion '1.256.1' -ExpectedProductVersionValid $false + Invoke-Case -Name 'ProductVersion build out of range' -RefType 'tag' -RefName '1.1.65536' -RunNumber '1' -ExpectedVersion '1.1.65536' -ExpectedNumericVersion '1.1.1.1' -ExpectedProductVersion '1.1.65536' -ExpectedProductVersionValid $false # Failure cases: tags that have no major.minor.patch[.build] core cannot yield a numeric-version. Invoke-FailureCase -Name 'Non-SemVer tag rejected' -RefType 'tag' -RefName 'release-1' -RunNumber '1' diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index e4a8c74..5247750 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -221,6 +221,8 @@ jobs: outputs: version: ${{ steps.determine-version.outputs.version }} numeric-version: ${{ steps.determine-version.outputs.numeric-version }} + product-version: ${{ steps.determine-version.outputs.product-version }} + product-version-valid: ${{ steps.determine-version.outputs.product-version-valid }} steps: - name: Determine version id: determine-version @@ -439,6 +441,15 @@ jobs: with: repository: ${{ github.repository }} + - name: Validate MSI ProductVersion + if: needs.discover_projects.outputs.has-wix-projects == 'true' && needs.determine_version.outputs.product-version-valid != 'true' + env: + PRODUCT_VERSION: ${{ needs.determine_version.outputs.product-version }} + run: | + echo "::error::MSI ProductVersion '$PRODUCT_VERSION' is out of range. Major and minor must be <= 255; build must be <= 65535." + exit 1 + shell: bash + # ────────────────────────────────────────────────────────────── # B. SonarCloud analysis (skipped for Dependabot) # ────────────────────────────────────────────────────────────── @@ -758,12 +769,13 @@ jobs: SOLUTION_PATH: ${{ needs.discover_projects.outputs.solution-path }} VERSION: ${{ needs.determine_version.outputs.version }} NUMERIC_VERSION: ${{ needs.determine_version.outputs.numeric-version }} + PRODUCT_VERSION: ${{ needs.determine_version.outputs.product-version }} CONFIGURATION: ${{ inputs.configuration }} DEBUG_FLAG: ${{ inputs.debug }} run: | # NuGet/DataMiner packaging uses the full (suffix-bearing) version; assemblies and - # the WiX MSI ProductVersion use the strict 4-field numeric version (WiX projects - # reference it as $(ProductVersion) in ). + # assemblies use the strict 4-field numeric version. WiX uses the dedicated + # ProductVersion (three fields for stable SemVer tags). # DefineConstants comes from the job-level env var so the WiX projects can keep # their own preprocessor constants (a -p: global would clobber them). dotnet build "$SOLUTION_PATH" \ @@ -771,7 +783,7 @@ jobs: -p:PackageVersion="$VERSION" \ -p:AssemblyVersion="$NUMERIC_VERSION" \ -p:FileVersion="$NUMERIC_VERSION" \ - -p:ProductVersion="$NUMERIC_VERSION" \ + -p:ProductVersion="$PRODUCT_VERSION" \ --configuration "$CONFIGURATION" \ -p:CatalogPublishKeyName="DATAMINER_TOKEN" \ -p:CatalogDefaultDownloadKeyName="OVERRIDE_CATALOG_DOWNLOAD_TOKEN" \ @@ -828,6 +840,7 @@ jobs: SOLUTION_PATH: ${{ needs.discover_projects.outputs.solution-path }} VERSION: ${{ needs.determine_version.outputs.version }} NUMERIC_VERSION: ${{ needs.determine_version.outputs.numeric-version }} + PRODUCT_VERSION: ${{ needs.determine_version.outputs.product-version }} CONFIGURATION: ${{ inputs.configuration }} run: | $wixProjectDirs = dotnet sln "$env:SOLUTION_PATH" list | Select-String '\.wixproj' | ForEach-Object { Split-Path -Path $_.ToString().Trim() -Parent } @@ -840,7 +853,7 @@ jobs: -p:PackageVersion="$env:VERSION" ` -p:AssemblyVersion="$env:NUMERIC_VERSION" ` -p:FileVersion="$env:NUMERIC_VERSION" ` - -p:ProductVersion="$env:NUMERIC_VERSION" ` + -p:ProductVersion="$env:PRODUCT_VERSION" ` -p:GeneratePackageOnBuild=false ` -p:GenerateDataMinerPackage=false ` --configuration "$env:CONFIGURATION" @@ -873,6 +886,7 @@ jobs: SOLUTION_PATH: ${{ needs.discover_projects.outputs.solution-path }} VERSION: ${{ needs.determine_version.outputs.version }} NUMERIC_VERSION: ${{ needs.determine_version.outputs.numeric-version }} + PRODUCT_VERSION: ${{ needs.determine_version.outputs.product-version }} CONFIGURATION: ${{ inputs.configuration }} run: | # Preserve the original solution's configuration/platform mappings while @@ -891,7 +905,7 @@ jobs: dotnet build "$temporarySolutionPath" ` -p:Version="$env:VERSION" ` - -p:ProductVersion="$env:NUMERIC_VERSION" ` + -p:ProductVersion="$env:PRODUCT_VERSION" ` -p:BuildProjectReferences=false ` --configuration "$env:CONFIGURATION" } finally { diff --git a/.github/workflows/Test composite actions.yml b/.github/workflows/Test composite actions.yml index 0ee18e4..bf32e54 100644 --- a/.github/workflows/Test composite actions.yml +++ b/.github/workflows/Test composite actions.yml @@ -635,7 +635,7 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Run 18-case version corpus + - name: Run 23-case version corpus shell: pwsh run: ./.github/actions/determine-version/test.ps1 @@ -644,9 +644,17 @@ jobs: uses: ./.github/actions/determine-version with: ref-type: tag - ref-name: 1.4.0-dev-myfeature.42 + ref-name: 1.4.0-a1b2c3d4.42 run-number: 99 + - name: Stable tag build smoke test + id: stable-tag-build + uses: ./.github/actions/determine-version + with: + ref-type: tag + ref-name: 1.4.0 + run-number: 100 + - name: Branch build smoke test id: branch-build uses: ./.github/actions/determine-version @@ -659,13 +667,23 @@ jobs: env: TAG_VERSION: ${{ steps.tag-build.outputs.version }} TAG_NUMERIC: ${{ steps.tag-build.outputs.numeric-version }} + TAG_PRODUCT: ${{ steps.tag-build.outputs.product-version }} + TAG_PRODUCT_VALID: ${{ steps.tag-build.outputs.product-version-valid }} + STABLE_NUMERIC: ${{ steps.stable-tag-build.outputs.numeric-version }} + STABLE_PRODUCT: ${{ steps.stable-tag-build.outputs.product-version }} BRANCH_VERSION: ${{ steps.branch-build.outputs.version }} BRANCH_NUMERIC: ${{ steps.branch-build.outputs.numeric-version }} + BRANCH_PRODUCT: ${{ steps.branch-build.outputs.product-version }} run: | - test "$TAG_VERSION" = "1.4.0-dev-myfeature.42" + test "$TAG_VERSION" = "1.4.0-a1b2c3d4.42" test "$TAG_NUMERIC" = "1.4.0.99" + test "$TAG_PRODUCT" = "1.4.0.99" + test "$TAG_PRODUCT_VALID" = "true" + test "$STABLE_NUMERIC" = "1.4.0.100" + test "$STABLE_PRODUCT" = "1.4.0" test "$BRANCH_VERSION" = "0.0.70000" test "$BRANCH_NUMERIC" = "0.0.4465.4465" + test "$BRANCH_PRODUCT" = "0.0.4465.4465" set-repo-type: name: set-repo-type From 6db52e486b33a463f8e390d7b5d7727fa259436c Mon Sep 17 00:00:00 2001 From: Michiel Oda Date: Fri, 31 Jul 2026 14:02:44 +0200 Subject: [PATCH 9/9] Use branch version of action --- .github/workflows/Master Workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Master Workflow.yml b/.github/workflows/Master Workflow.yml index 5247750..678a2ce 100644 --- a/.github/workflows/Master Workflow.yml +++ b/.github/workflows/Master Workflow.yml @@ -226,7 +226,7 @@ jobs: steps: - name: Determine version id: determine-version - uses: SkylineCommunications/_ReusableWorkflows/.github/actions/determine-version@main + uses: SkylineCommunications/_ReusableWorkflows/.github/actions/determine-version@DxMRelease with: ref-type: ${{ github.ref_type }} ref-name: ${{ github.ref_name }}