mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-17 05:11:13 +08:00
Exempt unremovable preview.g<commit> versions from the preview ordering gate (#4691)
## Problem The stable 2.0.0 `NuGet Publish` run on `release/2.0` ([run 37567251061](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/37567251061)) fails in **Verify preview package ordering**. Two master runs on 2026-10-06 ([37461428995](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/37461428995), [37462705289](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/37462705289)) pushed `2.0.0-preview.gfd360271d9` and `2.0.0-preview.ge78c648295` of every preview-family package to GitHub Packages. At that point master briefly had the stable root version `2.0.0`, so Nerdbank.GitVersioning appended the commit id. SemVer 2 ranks an alphanumeric identifier above every numeric one, so **no** `PreviewPackageBuildNumber` can outrank these versions. Deleting them would need org package admin rights, and docs/ReleaseProcess.md forbids deleting packages to make room anyway. ## Change - `preview-version.props`: adds a new `PreviewPackageOrderingExemptions` list (`;`-separated exact versions) with the two versions above and a comment recording where they came from. - `package-version-policy.ps1`: adds `Get-PreviewPackageOrderingExemptions`. `Get-BlockingPublishedPreviewVersions` takes an optional `-ExemptVersions` and skips exact (case-insensitive) matches only. Every other undecidable or higher version still blocks, so the gate still fails closed. - `validate-preview-package-ordering.ps1`: passes the list in and logs what it ignores. `nuget-publish.yml` and `release.yml` both use it. - `docs/ReleaseProcess.md`: the troubleshooting row says when to use an exemption. - Tests: exemptions are exact-match only; the committed list parses. All 89 `PackageVersionPolicyTests` pass on net10.0. ## Trade-off On GitHub Packages only, the released `2.0.0-preview.200` packages still sort below the two hash builds. nuget.org never had them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -36,6 +36,33 @@ function Get-PreviewPackageBuildNumber {
|
|||||||
return $node.InnerText.Trim()
|
return $node.InnerText.Trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-PreviewPackageOrderingExemptions {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Reads the exact published versions (see preview-version.props) that
|
||||||
|
the preview ordering gate must not treat as blocking because no
|
||||||
|
numbered preview can ever outrank them and they cannot be removed
|
||||||
|
from the feed.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$propsPath = Join-Path $RepositoryRoot 'preview-version.props'
|
||||||
|
if (-not (Test-Path -LiteralPath $propsPath)) {
|
||||||
|
throw "Cannot resolve the preview ordering exemptions: '$propsPath' was not found."
|
||||||
|
}
|
||||||
|
|
||||||
|
[xml]$props = Get-Content -LiteralPath $propsPath -Raw
|
||||||
|
$node = $props.SelectSingleNode('//*[local-name()="PreviewPackageOrderingExemptions"]')
|
||||||
|
if ($null -eq $node) {
|
||||||
|
return , @()
|
||||||
|
}
|
||||||
|
return , @($node.InnerText -split ';' |
|
||||||
|
ForEach-Object { $_.Trim() } |
|
||||||
|
Where-Object { $_ -ne '' })
|
||||||
|
}
|
||||||
|
|
||||||
function ConvertTo-PreviewPackageVersion {
|
function ConvertTo-PreviewPackageVersion {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][string]$Version,
|
[Parameter(Mandatory)][string]$Version,
|
||||||
@@ -45,6 +72,13 @@ function ConvertTo-PreviewPackageVersion {
|
|||||||
if ($Version -match '-preview(?:[.+-]|$)') {
|
if ($Version -match '-preview(?:[.+-]|$)') {
|
||||||
return $Version
|
return $Version
|
||||||
}
|
}
|
||||||
|
if ($Version -cmatch '^[^-+]+-g[0-9a-f]+(\+.*)?$') {
|
||||||
|
# A commit id as the only prerelease identifier (a non-public build
|
||||||
|
# of a stable version.json). "-preview.g<commit>" would outrank every
|
||||||
|
# numbered preview under SemVer 2, so number it 0; version.targets
|
||||||
|
# applies the same rule.
|
||||||
|
return $Version -creplace '^([^-+]+)-(g[0-9a-f]+)', '$1-preview.0.$2'
|
||||||
|
}
|
||||||
if ($Version.Contains('-')) {
|
if ($Version.Contains('-')) {
|
||||||
return $Version.Replace('-', '-preview.')
|
return $Version.Replace('-', '-preview.')
|
||||||
}
|
}
|
||||||
@@ -108,7 +142,9 @@ function Get-BlockingPublishedPreviewVersions {
|
|||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][string]$BaseVersion,
|
[Parameter(Mandatory)][string]$BaseVersion,
|
||||||
[string[]]$PublishedVersions = @(),
|
[string[]]$PublishedVersions = @(),
|
||||||
[string]$PreviewPackageBuildNumber = (Get-PreviewPackageBuildNumber)
|
[string]$PreviewPackageBuildNumber = (Get-PreviewPackageBuildNumber),
|
||||||
|
# Exact versions that never block (see preview-version.props).
|
||||||
|
[string[]]$ExemptVersions = @()
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($PreviewPackageBuildNumber -notmatch '^\d+$') {
|
if ($PreviewPackageBuildNumber -notmatch '^\d+$') {
|
||||||
@@ -134,6 +170,11 @@ function Get-BlockingPublishedPreviewVersions {
|
|||||||
if (-not $value.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
if (-not $value.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if ($ExemptVersions -contains $value) {
|
||||||
|
# An explicitly listed, unremovable version; PowerShell's
|
||||||
|
# -contains compares case-insensitively like NuGet versions.
|
||||||
|
continue
|
||||||
|
}
|
||||||
if ($value.Length -eq $prefix.Length) {
|
if ($value.Length -eq $prefix.Length) {
|
||||||
# Exactly "<base>-preview": under SemVer 2 a prerelease with
|
# Exactly "<base>-preview": under SemVer 2 a prerelease with
|
||||||
# fewer dot-separated identifiers always sorts below one that
|
# fewer dot-separated identifiers always sorts below one that
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ $baseVersion = [string]$manifest.basePackageVersion
|
|||||||
$previewBuildNumber = Get-PreviewPackageBuildNumber
|
$previewBuildNumber = Get-PreviewPackageBuildNumber
|
||||||
$candidateVersion = ConvertTo-PreviewPackageVersion -Version $baseVersion `
|
$candidateVersion = ConvertTo-PreviewPackageVersion -Version $baseVersion `
|
||||||
-PreviewPackageBuildNumber $previewBuildNumber
|
-PreviewPackageBuildNumber $previewBuildNumber
|
||||||
|
$exemptVersions = Get-PreviewPackageOrderingExemptions
|
||||||
|
if ($exemptVersions.Count -gt 0) {
|
||||||
|
Write-Host (
|
||||||
|
"Ignoring $($exemptVersions.Count) exempt published version(s) listed in " +
|
||||||
|
"preview-version.props: $($exemptVersions -join ', ').")
|
||||||
|
}
|
||||||
|
|
||||||
$packageIds = @($manifest.archives |
|
$packageIds = @($manifest.archives |
|
||||||
Where-Object { $_.type -eq 'package' -and (Test-PreviewPackageId -PackageId $_.id) } |
|
Where-Object { $_.type -eq 'package' -and (Test-PreviewPackageId -PackageId $_.id) } |
|
||||||
@@ -171,7 +177,8 @@ foreach ($packageId in $packageIds) {
|
|||||||
$blocking = Get-BlockingPublishedPreviewVersions `
|
$blocking = Get-BlockingPublishedPreviewVersions `
|
||||||
-BaseVersion $baseVersion `
|
-BaseVersion $baseVersion `
|
||||||
-PublishedVersions $published[$feedName] `
|
-PublishedVersions $published[$feedName] `
|
||||||
-PreviewPackageBuildNumber $previewBuildNumber
|
-PreviewPackageBuildNumber $previewBuildNumber `
|
||||||
|
-ExemptVersions $exemptVersions
|
||||||
if ($blocking.Count -gt 0) {
|
if ($blocking.Count -gt 0) {
|
||||||
$violations += [pscustomobject]@{
|
$violations += [pscustomobject]@{
|
||||||
PackageId = $packageId
|
PackageId = $packageId
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ or [Approved promotion](#approved-promotion).
|
|||||||
|
|
||||||
| Symptom | Cause and safe next action |
|
| Symptom | Cause and safe next action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `validate-preview-package-ordering.ps1` lists package IDs whose published versions are not below the candidate's `-preview.N` | Ordinary `master` development published a higher `<base>-preview.{height}` while this release was being prepared. Raise `PreviewPackageBuildNumber` in `preview-version.props` above every listed version, via a normal PR against the release branch, then build and validate a **new** candidate. Never lower a published version or delete packages to make room. |
|
| `validate-preview-package-ordering.ps1` lists package IDs whose published versions are not below the candidate's `-preview.N` | Ordinary `master` development published a higher `<base>-preview.{height}` while this release was being prepared. Raise `PreviewPackageBuildNumber` in `preview-version.props` above every listed version, via a normal PR against the release branch, then build and validate a **new** candidate. Never lower a published version or delete packages to make room. If a listed version can never be outranked by any number - SemVer 2 sorts an alphanumeric identifier such as `<base>-preview.g<commit>` above every `-preview.N` - add that exact version to `PreviewPackageOrderingExemptions` in `preview-version.props` instead, with a comment recording where it came from. |
|
||||||
| The script throws while querying nuget.org or GitHub Packages (for example `401`/`403`, or any non-`404` error status) | The gate deliberately fails closed: it cannot prove ordering, so it refuses to let the release proceed. Do **not** bypass it. Confirm the workflow passes a token with `packages: read`/`packages: write` for the owner being queried, and that the feed is reachable, then re-run the same workflow. A genuinely unpublished package ID returns `404`, which the script treats as "nothing to compare" and allows. |
|
| The script throws while querying nuget.org or GitHub Packages (for example `401`/`403`, or any non-`404` error status) | The gate deliberately fails closed: it cannot prove ordering, so it refuses to let the release proceed. Do **not** bypass it. Confirm the workflow passes a token with `packages: read`/`packages: write` for the owner being queried, and that the feed is reachable, then re-run the same workflow. A genuinely unpublished package ID returns `404`, which the script treats as "nothing to compare" and allows. |
|
||||||
|
|
||||||
3. **If nuget.org already has some non-Debug packages, or GitHub Packages has
|
3. **If nuget.org already has some non-Debug packages, or GitHub Packages has
|
||||||
|
|||||||
@@ -36,5 +36,21 @@
|
|||||||
-->
|
-->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PreviewPackageBuildNumber Condition="'$(PreviewPackageBuildNumber)' == ''">200</PreviewPackageBuildNumber>
|
<PreviewPackageBuildNumber Condition="'$(PreviewPackageBuildNumber)' == ''">200</PreviewPackageBuildNumber>
|
||||||
|
<!--
|
||||||
|
Exact, already-published versions that the preview ordering gate
|
||||||
|
(.azurepipelines/validate-preview-package-ordering.ps1) must not treat
|
||||||
|
as blocking, separated by ";". Only for versions that no
|
||||||
|
PreviewPackageBuildNumber can ever outrank and that cannot be removed
|
||||||
|
from the feed: SemVer 2 ranks an alphanumeric identifier such as
|
||||||
|
"g<commit>" above every numeric one, so "<base>-preview.g<commit>"
|
||||||
|
sorts above every "<base>-preview.N".
|
||||||
|
|
||||||
|
2.0.0-preview.gfd360271d9 and 2.0.0-preview.ge78c648295 were pushed to
|
||||||
|
GitHub Packages (never nuget.org) by master CI runs 37461428995 and
|
||||||
|
37462705289 on 2026-10-06, while master briefly carried the stable
|
||||||
|
root version 2.0.0 and Nerdbank.GitVersioning appended the commit id.
|
||||||
|
They stay above the released 2.0.0-preview.N on that feed only.
|
||||||
|
-->
|
||||||
|
<PreviewPackageOrderingExemptions>2.0.0-preview.gfd360271d9;2.0.0-preview.ge78c648295</PreviewPackageOrderingExemptions>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -139,6 +139,9 @@ namespace Opc.Ua.Tools.Tests
|
|||||||
[TestCase("2.0.0-preview.1.gabc123def0", "2.0.0-preview.1.gabc123def0", Description = "Already preview with commit id: unchanged")]
|
[TestCase("2.0.0-preview.1.gabc123def0", "2.0.0-preview.1.gabc123def0", Description = "Already preview with commit id: unchanged")]
|
||||||
[TestCase("2.0.1-preview.3+build5", "2.0.1-preview.3+build5", Description = "Already preview with build metadata: unchanged")]
|
[TestCase("2.0.1-preview.3+build5", "2.0.1-preview.3+build5", Description = "Already preview with build metadata: unchanged")]
|
||||||
[TestCase("2.0.0-rc.1", "2.0.0-preview.rc.1", Description = "Other prerelease label: preview-prefixed")]
|
[TestCase("2.0.0-rc.1", "2.0.0-preview.rc.1", Description = "Other prerelease label: preview-prefixed")]
|
||||||
|
[TestCase("2.0.0-ge78c648295", "2.0.0-preview.0.ge78c648295", Description = "Commit id only: numbered 0")]
|
||||||
|
[TestCase("2.0.0-gabc123def0+b5", "2.0.0-preview.0.gabc123def0+b5", Description = "Commit id and metadata: numbered 0")]
|
||||||
|
[TestCase("2.0.0-gamma.1", "2.0.0-preview.gamma.1", Description = "A label that merely starts with 'g' is not a commit id")]
|
||||||
public async Task ConvertToPreviewPackageVersionIsIdempotentForExistingPrereleaseAsync(
|
public async Task ConvertToPreviewPackageVersionIsIdempotentForExistingPrereleaseAsync(
|
||||||
string input,
|
string input,
|
||||||
string expected)
|
string expected)
|
||||||
@@ -171,6 +174,117 @@ namespace Opc.Ua.Tools.Tests
|
|||||||
Assert.That(result.GetProperty("actual").GetString(), Is.EqualTo(expected));
|
Assert.That(result.GetProperty("actual").GetString(), Is.EqualTo(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task VersionTargetsAppliesTheSamePreviewVersionRulesAsConvertToPreviewPackageVersionAsync()
|
||||||
|
{
|
||||||
|
// ApplyPreviewPackageVersion in version.targets is what actually
|
||||||
|
// stamps package versions; ConvertTo-PreviewPackageVersion is an
|
||||||
|
// independent re-implementation the validators use. Evaluate the
|
||||||
|
// real target through MSBuild so the two cannot drift apart.
|
||||||
|
(string Input, string Expected)[] cases =
|
||||||
|
[
|
||||||
|
("2.0.0", "2.0.0-preview.42"),
|
||||||
|
("2.0.0+gabc123def0", "2.0.0-preview.42+gabc123def0"),
|
||||||
|
("2.0.0-preview.6", "2.0.0-preview.6"),
|
||||||
|
("2.0.1-preview.5.gfa06c66dbb", "2.0.1-preview.5.gfa06c66dbb"),
|
||||||
|
("2.0.0-rc.1", "2.0.0-preview.rc.1"),
|
||||||
|
("2.0.0-gamma.1", "2.0.0-preview.gamma.1"),
|
||||||
|
("2.0.0-ge78c648295", "2.0.0-preview.0.ge78c648295"),
|
||||||
|
("2.0.0-gabc123def0+b5", "2.0.0-preview.0.gabc123def0+b5"),
|
||||||
|
];
|
||||||
|
|
||||||
|
string fixtureDirectory = Path.Combine(
|
||||||
|
TestContext.CurrentContext.WorkDirectory,
|
||||||
|
"package-version-policy-fixtures",
|
||||||
|
Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(fixtureDirectory);
|
||||||
|
string projectPath = Path.Combine(fixtureDirectory, "versions.proj");
|
||||||
|
string resultPath = Path.Combine(fixtureDirectory, "versions.txt");
|
||||||
|
string versionTargetsPath = Path.Combine(FindRepositoryRoot(), "version.targets");
|
||||||
|
string items = string.Concat(cases.Select(c => $"<Case Include=\"{c.Input}\" />"));
|
||||||
|
|
||||||
|
// A plain (non-SDK) project imports no Directory.Build.* files, so
|
||||||
|
// only version.targets contributes. NBGV_PublicRelease skips the
|
||||||
|
// GetBuildVersion dependency, as on a release branch build.
|
||||||
|
await File.WriteAllTextAsync(
|
||||||
|
projectPath,
|
||||||
|
$$"""
|
||||||
|
<Project DefaultTargets="Run">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PackagePrefix>OPCFoundation.NetStandard</PackagePrefix>
|
||||||
|
<PackageId>OPCFoundation.NetStandard.Opc.Ua.AI</PackageId>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
<NBGV_PublicRelease>True</NBGV_PublicRelease>
|
||||||
|
<PreviewPackageBuildNumber>42</PreviewPackageBuildNumber>
|
||||||
|
<PackageVersion>$(InputVersion)</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>{{items}}</ItemGroup>
|
||||||
|
<Import Project="{{versionTargetsPath}}" />
|
||||||
|
<Target Name="Map" DependsOnTargets="ApplyPreviewPackageVersion" Returns="@(Mapped)">
|
||||||
|
<ItemGroup>
|
||||||
|
<Mapped Include="$(InputVersion)" Actual="$(PackageVersion)" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
<Target Name="Run">
|
||||||
|
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="Map" Properties="InputVersion=%(Case.Identity)">
|
||||||
|
<Output TaskParameter="TargetOutputs" ItemName="Result" />
|
||||||
|
</MSBuild>
|
||||||
|
<WriteLinesToFile File="{{resultPath}}" Lines="@(Result->'%(Identity)=%(Actual)')" Overwrite="true" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
|
""").ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var process = new Process();
|
||||||
|
process.StartInfo.FileName = "dotnet";
|
||||||
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
|
process.StartInfo.RedirectStandardError = true;
|
||||||
|
process.StartInfo.ArgumentList.Add("msbuild");
|
||||||
|
process.StartInfo.ArgumentList.Add(projectPath);
|
||||||
|
process.StartInfo.ArgumentList.Add("-nologo");
|
||||||
|
process.StartInfo.ArgumentList.Add("-nodeReuse:false");
|
||||||
|
|
||||||
|
Assert.That(process.Start(), Is.True);
|
||||||
|
Task<string> standardOutput = process.StandardOutput.ReadToEndAsync();
|
||||||
|
Task<string> standardError = process.StandardError.ReadToEndAsync();
|
||||||
|
using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2));
|
||||||
|
await process.WaitForExitAsync(timeout.Token).ConfigureAwait(false);
|
||||||
|
string output = await standardOutput.ConfigureAwait(false);
|
||||||
|
string error = await standardError.ConfigureAwait(false);
|
||||||
|
Assert.That(process.ExitCode, Is.Zero, $"dotnet msbuild failed:\n{output}\n{error}");
|
||||||
|
|
||||||
|
Dictionary<string, string> actual = (await File.ReadAllLinesAsync(resultPath).ConfigureAwait(false))
|
||||||
|
.Select(line => line.Split('=', 2))
|
||||||
|
.ToDictionary(parts => parts[0], parts => parts[1], StringComparer.Ordinal);
|
||||||
|
|
||||||
|
JsonElement policy = await RunPolicyScriptAsync(
|
||||||
|
$$"""
|
||||||
|
. '{{PolicyScriptPath}}'
|
||||||
|
$inputs = @({{string.Join(",", cases.Select(c => $"'{c.Input}'"))}})
|
||||||
|
$results = foreach ($input in $inputs) {
|
||||||
|
ConvertTo-PreviewPackageVersion -Version $input -PreviewPackageBuildNumber '42'
|
||||||
|
}
|
||||||
|
$results | ConvertTo-Json -AsArray
|
||||||
|
""").ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < cases.Length; i++)
|
||||||
|
{
|
||||||
|
(string input, string expected) = cases[i];
|
||||||
|
Assert.That(actual.TryGetValue(input, out string? mapped), Is.True, $"version.targets produced no result for '{input}'.");
|
||||||
|
Assert.That(mapped, Is.EqualTo(expected), $"version.targets mapped '{input}' incorrectly.");
|
||||||
|
Assert.That(policy[i].GetString(), Is.EqualTo(expected), $"ConvertTo-PreviewPackageVersion mapped '{input}' incorrectly.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(fixtureDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task GetExpectedPackageVersionAppliesPolicyOnlyToPreviewFamiliesAsync()
|
public async Task GetExpectedPackageVersionAppliesPolicyOnlyToPreviewFamiliesAsync()
|
||||||
{
|
{
|
||||||
@@ -613,6 +727,42 @@ namespace Opc.Ua.Tools.Tests
|
|||||||
"not silently assumed to be lower.");
|
"not silently assumed to be lower.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetBlockingPublishedPreviewVersionsSkipsOnlyExactExemptVersionsAsync()
|
||||||
|
{
|
||||||
|
JsonElement result = await RunBlockingPreviewVersionsAsync(
|
||||||
|
baseVersion: "2.0.0",
|
||||||
|
previewPackageBuildNumber: "200",
|
||||||
|
publishedVersions:
|
||||||
|
[
|
||||||
|
"2.0.0-preview.gfd360271d9",
|
||||||
|
// Exemptions match case-insensitively, like NuGet versions.
|
||||||
|
"2.0.0-PREVIEW.GE78C648295",
|
||||||
|
// Not listed: a commit-id label still blocks.
|
||||||
|
"2.0.0-preview.g0123456789",
|
||||||
|
"2.0.0-preview.201",
|
||||||
|
],
|
||||||
|
exemptVersions: ["2.0.0-preview.gfd360271d9", "2.0.0-preview.ge78c648295"]).ConfigureAwait(false);
|
||||||
|
|
||||||
|
string[] blocking = [.. result.EnumerateArray().Select(e => e.GetString()!)];
|
||||||
|
string[] expected = ["2.0.0-preview.201", "2.0.0-preview.g0123456789"];
|
||||||
|
Assert.That(blocking, Is.EquivalentTo(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetPreviewPackageOrderingExemptionsReadsTheCommittedListAsync()
|
||||||
|
{
|
||||||
|
JsonElement result = await RunPolicyScriptAsync(
|
||||||
|
$$"""
|
||||||
|
. '{{PolicyScriptPath}}'
|
||||||
|
ConvertTo-Json -InputObject (Get-PreviewPackageOrderingExemptions) -Depth 3
|
||||||
|
""").ConfigureAwait(false);
|
||||||
|
|
||||||
|
string[] exemptions = [.. result.EnumerateArray().Select(e => e.GetString()!)];
|
||||||
|
Assert.That(exemptions, Has.All.Matches<string>(v => v.Length > 0 && v.Trim() == v));
|
||||||
|
Assert.That(exemptions, Has.All.Contains("-preview."));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task GetBlockingPublishedPreviewVersionsRejectsANonStableBaseVersionAsync()
|
public async Task GetBlockingPublishedPreviewVersionsRejectsANonStableBaseVersionAsync()
|
||||||
{
|
{
|
||||||
@@ -1124,16 +1274,19 @@ namespace Opc.Ua.Tools.Tests
|
|||||||
private static Task<JsonElement> RunBlockingPreviewVersionsAsync(
|
private static Task<JsonElement> RunBlockingPreviewVersionsAsync(
|
||||||
string baseVersion,
|
string baseVersion,
|
||||||
string previewPackageBuildNumber,
|
string previewPackageBuildNumber,
|
||||||
string[] publishedVersions)
|
string[] publishedVersions,
|
||||||
|
string[]? exemptVersions = null)
|
||||||
{
|
{
|
||||||
string published = string.Join(",", publishedVersions.Select(v => $"'{v}'"));
|
string published = string.Join(",", publishedVersions.Select(v => $"'{v}'"));
|
||||||
|
string exempt = string.Join(",", (exemptVersions ?? []).Select(v => $"'{v}'"));
|
||||||
return RunPolicyScriptAsync(
|
return RunPolicyScriptAsync(
|
||||||
$$"""
|
$$"""
|
||||||
. '{{PolicyScriptPath}}'
|
. '{{PolicyScriptPath}}'
|
||||||
$blocking = Get-BlockingPublishedPreviewVersions `
|
$blocking = Get-BlockingPublishedPreviewVersions `
|
||||||
-BaseVersion '{{baseVersion}}' `
|
-BaseVersion '{{baseVersion}}' `
|
||||||
-PublishedVersions @({{published}}) `
|
-PublishedVersions @({{published}}) `
|
||||||
-PreviewPackageBuildNumber '{{previewPackageBuildNumber}}'
|
-PreviewPackageBuildNumber '{{previewPackageBuildNumber}}' `
|
||||||
|
-ExemptVersions @({{exempt}})
|
||||||
ConvertTo-Json -InputObject $blocking -Depth 3
|
ConvertTo-Json -InputObject $blocking -Depth 3
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,19 @@
|
|||||||
to do. -->
|
to do. -->
|
||||||
<_PreviewPackageVersion
|
<_PreviewPackageVersion
|
||||||
Condition="$([System.String]::Copy('$(_PreviewSourceVersion)').Contains('-preview'))">$(_PreviewSourceVersion)</_PreviewPackageVersion>
|
Condition="$([System.String]::Copy('$(_PreviewSourceVersion)').Contains('-preview'))">$(_PreviewSourceVersion)</_PreviewPackageVersion>
|
||||||
|
<!-- Case 2a: the root version's only prerelease identifier is a commit
|
||||||
|
id (e.g. "2.0.0-gabcdef0123"). Nerdbank.GitVersioning produces
|
||||||
|
this for a stable version.json built from a non-public-release
|
||||||
|
ref, such as master briefly carrying "2.0.0". Case 2 would turn it
|
||||||
|
into "2.0.0-preview.gabcdef0123", and SemVer 2 ranks the
|
||||||
|
alphanumeric "g..." identifier above every numeric one, so it
|
||||||
|
would outrank every "-preview.N" ever released from that base -
|
||||||
|
permanently, because published versions are immutable. Number it
|
||||||
|
0 instead so it sorts below any committed
|
||||||
|
PreviewPackageBuildNumber. ConvertTo-PreviewPackageVersion in
|
||||||
|
.azurepipelines/package-version-policy.ps1 applies the same rule. -->
|
||||||
|
<_PreviewPackageVersion
|
||||||
|
Condition="'$(_PreviewPackageVersion)' == '' AND $([System.Text.RegularExpressions.Regex]::IsMatch('$(_PreviewSourceVersion)', '^[^-+]+-g[0-9a-f]+(\+.*)?$'))">$([System.Text.RegularExpressions.Regex]::Replace('$(_PreviewSourceVersion)', '^([^-+]+)-(g[0-9a-f]+)', '$1-preview.0.$2'))</_PreviewPackageVersion>
|
||||||
<!-- Case 2: the root version carries some other prerelease label
|
<!-- Case 2: the root version carries some other prerelease label
|
||||||
(for example a hypothetical "-rc.1"). Preserve that label but
|
(for example a hypothetical "-rc.1"). Preserve that label but
|
||||||
mark the selected families as preview under it too. -->
|
mark the selected families as preview under it too. -->
|
||||||
|
|||||||
Reference in New Issue
Block a user