mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-16 21:02:51 +08:00
0374e19121
# Description _Describe the changes here to communicate to the maintainers why they should accept this pull request. By default - this will become the Commit message after merging and thus define history._ Add provider-backed MachineTool, bundled WMTP, GMS and CranesHoists companions using the existing Machinery/MachineVision fluent, DI and typed-client approach. **The PR is open/non-draft by user choice. Current head `ecc4a7d6ca3e8004cf28ab54da605da302d026bf` is mergeable and has 108 successful checks, six intentionally skipped checks, zero failures and zero pending checks. All five primary workflows completed successfully at that exact head.** | Family | Released specification/model | Packaging | | --- | --- | --- | | MachineTool | OPC 40501-1, 1.02.0 | Model, Server, Client and MCP; normal root versions | | WMTP | OPC 40505, 1.0.0 | Bundled in MachineTool; distinct CLR/wire namespace and independent WMTP-only hosting | | GMS | OPC 40210 reference 1.0.1, normative model 1.0.0 | Model, Server, Client and MCP; normal root versions | | CranesHoists | OPC 40020-1, 1.00 | Model, Server, Client and MCP; all four plus Debug variants forced preview | There are **12 new packable projects**. No separate WMTP package/profile/guide, root version bump, tag, release or package publication is included. Mandatory instance structure is the default; selected optional features require explicit providers. The implementation reuses Machinery results/jobs/files/ProcessValues and Robotics topology, including retained MachineTool Production, defined GMS jobs/parts, modern ISA95/Machinery jobs, shared-owner hosting and isolated instances alongside MachineVision. Runtime and integration fixes preserve raw quality/timestamps, coherent acquisition, namespace ownership, accepted-configuration state, borrowed-resource lifetime, session/node/file cleanup, terminal-value publication, event propagation and endpoint-aware capability publication. Firmware/licence transfer means provider acceptance, not installation/activation. Cancellation does not silently abort another caller's operation. The later upstream MCP expansion is integrated without losing either family set or its default-off privileged/live execution policy. ### Conformance and scope Canonical model inputs are pinned to UA-Nodeset revision `96a08945425d0295edb3607d2a38f6841ef44046`. **GMS upstream exception:** [Annex A.1](https://reference.opcfoundation.org/specs/OPC-40210/annex-a) makes the released NodeSet normative, but §12.3.2.7 requires undefined `GMS_ProductionStatisticsType`. Inherited statistics and defined jobs/parts are implemented without inventing a standard type/NodeId; the complete GMS Production facet is withheld. The requirement ledger records **102 positively evidenced CU rows, 96 directly negative-tested rows and six explicit negative-evidence limitations**. All 648 test-class/name/TRX links were independently resolved to passing cases. The six limitations concern Stacklight, retained legacy PowerOnDuration, Machinery PowerOnDuration, WorkingUnit, Obligation and MaintenanceMode. They are not claims that every invalid-input or hardware-control behavior was tested. The undefined GMS requirement is outside the 102 defined CUs. No external certification claim is made. Retained baseline complete-assembly line coverage, with generated code retained and no source exclusions: | Family | Model | Server | Client | | --- | ---: | ---: | ---: | | MachineTool/WMTP | 83.80% | 91.98% | 84.74% | | GMS | 95.51% | 90.63% | 88.38% | | CranesHoists | 92.76% | 94.45% | 92.24% | These nine measurements predate the later focused review fixes and merges; they are not relabelled as new `ecc4a7d6c` percentages. The current head's separate CI code-coverage check succeeded. No new exclusions or threshold relaxations were introduced. ## Related Issues - Fixes [#4764](https://github.com/OPCFoundation/UA-.NETStandard/issues/4764), the implementation/design and acceptance tracker. ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [ ] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [x] I have addressed **all** PR feedback received.
468 lines
19 KiB
PowerShell
468 lines
19 KiB
PowerShell
function Test-PreviewPackageId {
|
|
param([Parameter(Mandatory)][string]$PackageId)
|
|
|
|
$baseId = $PackageId -replace '\.Debug$', ''
|
|
return $baseId -match '^OPCFoundation\.NetStandard\.Opc\.Ua\.(XRegistry|WotCon|Vision\.OpenUsd|CranesHoists|Robotics|Redundancy|Positioning|OpenUsd|AI)(\.|$)' -or
|
|
$baseId -in @(
|
|
'OPCFoundation.NetStandard.Opc.Ua.Mcp.Robotics',
|
|
'OPCFoundation.NetStandard.Opc.Ua.Mcp.CranesHoists',
|
|
'OPCFoundation.NetStandard.Opc.Ua.OpenUsd.Connector',
|
|
'OPCFoundation.NetStandard.Opc.Ua.OpenUsd.Connector.Viewer')
|
|
}
|
|
|
|
function Get-PreviewPackageBuildNumber {
|
|
<#
|
|
.SYNOPSIS
|
|
Reads the single source of truth for the numbered "-preview.N"
|
|
suffix (see preview-version.props) that version.targets applies to
|
|
the preview-only package families whenever the root package version
|
|
is an exact stable release with no prerelease label of its own to
|
|
reuse.
|
|
#>
|
|
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 package build number: '$propsPath' was not found."
|
|
}
|
|
|
|
[xml]$props = Get-Content -LiteralPath $propsPath -Raw
|
|
$node = $props.SelectSingleNode('//*[local-name()="PreviewPackageBuildNumber"]')
|
|
if ($null -eq $node -or [string]::IsNullOrWhiteSpace($node.InnerText)) {
|
|
throw "'$propsPath' does not define a PreviewPackageBuildNumber value."
|
|
}
|
|
return $node.InnerText.Trim()
|
|
}
|
|
|
|
function ConvertTo-PreviewPackageVersion {
|
|
param(
|
|
[Parameter(Mandatory)][string]$Version,
|
|
[string]$PreviewPackageBuildNumber = (Get-PreviewPackageBuildNumber)
|
|
)
|
|
|
|
if ($Version -match '-preview(?:[.+-]|$)') {
|
|
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('-')) {
|
|
return $Version.Replace('-', '-preview.')
|
|
}
|
|
if ($Version.Contains('+')) {
|
|
return $Version.Replace('+', "-preview.$PreviewPackageBuildNumber+")
|
|
}
|
|
return "$Version-preview.$PreviewPackageBuildNumber"
|
|
}
|
|
|
|
function Get-ExpectedPackageVersion {
|
|
param(
|
|
[Parameter(Mandatory)][string]$PackageId,
|
|
[Parameter(Mandatory)][string]$BaseVersion
|
|
)
|
|
|
|
if (Test-PreviewPackageId $PackageId) {
|
|
return ConvertTo-PreviewPackageVersion -Version $BaseVersion
|
|
}
|
|
return $BaseVersion
|
|
}
|
|
|
|
function Get-BlockingPublishedPreviewVersions {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the already-published versions that a synthesized
|
|
"<BaseVersion>-preview.N" candidate does not sort strictly above.
|
|
|
|
.DESCRIPTION
|
|
Whenever the root version is an exact stable release it carries no
|
|
git-height placeholder to reuse, so version.targets pins the
|
|
preview-only families to the explicitly committed number in
|
|
preview-version.props instead. That number is a manual release
|
|
decision, so it goes stale on its own: ordinary development keeps
|
|
publishing "<BaseVersion>-preview.<height>" packages with a growing
|
|
height, and once the height passes the committed number the released
|
|
package would sort *below* a CI package under SemVer 2 precedence.
|
|
NuGet versions are immutable, so that mistake cannot be corrected
|
|
after the fact - consumers tracking the latest prerelease would
|
|
simply never see the release.
|
|
|
|
This predicate is deliberately pure: callers supply the published
|
|
version list they fetched from a feed, which keeps it deterministic
|
|
and offline-testable. It fails closed - a published version sharing
|
|
the same "<BaseVersion>-preview" base whose ordering cannot be
|
|
decided numerically is reported as blocking and requires an explicit
|
|
decision rather than being assumed lower.
|
|
|
|
One published version is deliberately *not* blocking: the candidate's
|
|
own exact "<BaseVersion>-preview.<N>" identity. NuGet versions are
|
|
immutable, so a promotion that fails part-way through leaves some of
|
|
the candidate's own packages on the feed, and the documented recovery
|
|
is to re-run the same candidate (see docs/ReleaseProcess.md). Treating
|
|
that as a violation would make the recovery path unreachable. It is
|
|
safe because release.yml proves the already-published bytes really are
|
|
this candidate's, through assert-published-packages-match.ps1, before
|
|
it pushes anything - a foreign package that happens to occupy the
|
|
candidate's version fails there instead. Any version that merely
|
|
*starts* with the candidate's number carries extra prerelease
|
|
identifiers, sorts strictly above it under SemVer 2, and still blocks.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$BaseVersion,
|
|
[string[]]$PublishedVersions = @(),
|
|
[string]$PreviewPackageBuildNumber = (Get-PreviewPackageBuildNumber)
|
|
)
|
|
|
|
if ($PreviewPackageBuildNumber -notmatch '^\d+$') {
|
|
throw "The preview package build number '$PreviewPackageBuildNumber' is not a non-negative integer."
|
|
}
|
|
if (-not (Test-StablePackageVersion -Version $BaseVersion)) {
|
|
throw (
|
|
"Preview ordering is only defined for an exact stable base version such as '2.0.0'; " +
|
|
"got '$BaseVersion'.")
|
|
}
|
|
|
|
$candidate = [int]$PreviewPackageBuildNumber
|
|
$prefix = "$BaseVersion-preview"
|
|
$numbered = [regex]::new(
|
|
'^' + [regex]::Escape($prefix) + '\.(?<number>\d+)(?<rest>[.+\-].*)?$',
|
|
[System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
|
|
|
$blocking = foreach ($published in $PublishedVersions) {
|
|
if ([string]::IsNullOrWhiteSpace($published)) {
|
|
continue
|
|
}
|
|
$value = $published.Trim()
|
|
if (-not $value.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
continue
|
|
}
|
|
if ($value.Length -eq $prefix.Length) {
|
|
# Exactly "<base>-preview": under SemVer 2 a prerelease with
|
|
# fewer dot-separated identifiers always sorts below one that
|
|
# shares its prefix and adds more, so this can never block.
|
|
continue
|
|
}
|
|
|
|
$match = $numbered.Match($value)
|
|
if (-not $match.Success) {
|
|
# For example a legacy "<base>-preview20240131" label: the
|
|
# ordering cannot be decided numerically, so surface it.
|
|
$value
|
|
continue
|
|
}
|
|
|
|
$number = [int]$match.Groups['number'].Value
|
|
if ($number -gt $candidate) {
|
|
$value
|
|
continue
|
|
}
|
|
if ($number -lt $candidate) {
|
|
continue
|
|
}
|
|
|
|
# Same number as the candidate. A bare "<base>-preview.<N>" - or one
|
|
# carrying only "+build" metadata, which SemVer 2 and NuGet both
|
|
# ignore for precedence - is the candidate's own identity and is
|
|
# allowed so a partially completed promotion can be re-run. Extra
|
|
# prerelease identifiers ("-preview.<N>.gabc123") sort above it.
|
|
$rest = $match.Groups['rest'].Value
|
|
if ($rest -ne '' -and -not $rest.StartsWith('+')) {
|
|
$value
|
|
}
|
|
}
|
|
|
|
return , @($blocking | Sort-Object -Unique)
|
|
}
|
|
|
|
function Get-NuGetPackageContentDigest {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns a stable SHA-256 digest over a .nupkg's *content*, ignoring
|
|
the package signature.
|
|
|
|
.DESCRIPTION
|
|
A raw file hash cannot be used to compare a local candidate with the
|
|
same version already sitting on a feed: nuget.org repository-signs
|
|
every package at ingestion, so the bytes it serves are never
|
|
byte-identical to the bytes that were pushed even when nothing else
|
|
changed. Hashing each entry except the signature part compares what
|
|
actually has to match - the assemblies, the nuspec and every other
|
|
file - while tolerating the signature the feed legitimately added.
|
|
|
|
The digest is computed over a canonical "<entry>\t<sha256>" listing
|
|
sorted by ordinal entry name, so zip ordering differences do not
|
|
change the result.
|
|
#>
|
|
param([Parameter(Mandatory)][string]$Path)
|
|
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
$archive = [System.IO.Compression.ZipFile]::OpenRead($Path)
|
|
try {
|
|
$entryHash = [System.Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$lines = [System.Collections.Generic.List[string]]::new()
|
|
foreach ($entry in $archive.Entries) {
|
|
# ".signature.p7s" is the only part a feed rewrites.
|
|
if ($entry.FullName -ceq '.signature.p7s' -or $entry.FullName.EndsWith('/')) {
|
|
continue
|
|
}
|
|
$stream = $entry.Open()
|
|
try {
|
|
$hash = $entryHash.ComputeHash($stream)
|
|
}
|
|
finally {
|
|
$stream.Dispose()
|
|
}
|
|
$lines.Add(
|
|
"$($entry.FullName)`t" +
|
|
[System.BitConverter]::ToString($hash).Replace('-', ''))
|
|
}
|
|
}
|
|
finally {
|
|
$entryHash.Dispose()
|
|
}
|
|
}
|
|
finally {
|
|
$archive.Dispose()
|
|
}
|
|
|
|
if ($lines.Count -eq 0) {
|
|
throw "'$Path' contains no package content to compare."
|
|
}
|
|
|
|
# Ordinal, not culture-aware: the digest has to be reproducible on every
|
|
# runner locale.
|
|
$lines.Sort([System.StringComparer]::Ordinal)
|
|
|
|
$listingHash = [System.Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$bytes = [System.Text.Encoding]::UTF8.GetBytes(($lines -join "`n"))
|
|
return [System.BitConverter]::ToString($listingHash.ComputeHash($bytes)).
|
|
Replace('-', '').ToLowerInvariant()
|
|
}
|
|
finally {
|
|
$listingHash.Dispose()
|
|
}
|
|
}
|
|
|
|
function Invoke-FeedRequest {
|
|
<#
|
|
.SYNOPSIS
|
|
Invoke-WebRequest that retries transient failures only and returns
|
|
the final response for the caller to judge by status code.
|
|
|
|
.DESCRIPTION
|
|
Invoke-WebRequest's own -MaximumRetryCount retries every status from
|
|
400 to 599, so each 404 - the normal "this id/version is not
|
|
published" answer the release gates ask for once per package - cost
|
|
three pointless retries and ~15 s. Across a full package set on two
|
|
feeds that turned a read-only check into an hour-long step.
|
|
|
|
Only a network failure, 408, 429 or 5xx is retried here. Every other
|
|
status is returned straight away; after the last retry the final
|
|
response is returned (or the network error rethrown), so callers keep
|
|
failing closed on anything they do not recognise.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Uri,
|
|
[hashtable]$Headers = @{},
|
|
[string]$OutFile,
|
|
[ValidateRange(0, 10)][int]$MaximumRetryCount = 3,
|
|
[ValidateRange(0, 60)][int]$RetryIntervalSec = 5
|
|
)
|
|
|
|
$parameters = @{
|
|
Uri = $Uri
|
|
Headers = $Headers
|
|
SkipHttpErrorCheck = $true
|
|
}
|
|
if ($OutFile) {
|
|
$parameters['OutFile'] = $OutFile
|
|
$parameters['PassThru'] = $true
|
|
}
|
|
|
|
for ($attempt = 0; ; $attempt++) {
|
|
try {
|
|
$response = Invoke-WebRequest @parameters
|
|
}
|
|
catch {
|
|
# -SkipHttpErrorCheck means only failures without an HTTP
|
|
# status (DNS, connection reset, timeout) reach this block.
|
|
if ($attempt -ge $MaximumRetryCount) {
|
|
throw
|
|
}
|
|
Start-Sleep -Seconds $RetryIntervalSec
|
|
continue
|
|
}
|
|
|
|
$status = [int]$response.StatusCode
|
|
$transient = $status -eq 408 -or $status -eq 429 -or $status -ge 500
|
|
if (-not $transient -or $attempt -ge $MaximumRetryCount) {
|
|
return $response
|
|
}
|
|
Start-Sleep -Seconds $RetryIntervalSec
|
|
}
|
|
}
|
|
|
|
function Get-PreviousReleaseTag {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns the release tag GitHub should generate release notes from:
|
|
the greatest semantic version of the same major that is strictly
|
|
lower than $Tag, or $null when there is none.
|
|
|
|
.DESCRIPTION
|
|
Without a previous tag GitHub diffs against the latest release, which
|
|
can be a 1.5.378 maintenance release on another branch; the 2.0.0
|
|
notes generated that way exceeded the 125000-character body limit.
|
|
Choosing by version instead of publication time keeps a servicing
|
|
promotion (2.0.1 after 2.1.0) on its own line. Tags that are not
|
|
semantic versions, such as the four-component 1.5.378.x, are ignored.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Tag,
|
|
[AllowEmptyCollection()][string[]]$ReleaseTags = @()
|
|
)
|
|
|
|
$target = $null
|
|
if (-not [System.Management.Automation.SemanticVersion]::TryParse($Tag, [ref]$target)) {
|
|
throw "Release tag '$Tag' is not a semantic version."
|
|
}
|
|
|
|
$best = $null
|
|
$bestTag = $null
|
|
foreach ($candidateTag in $ReleaseTags) {
|
|
$candidate = $null
|
|
if (-not [System.Management.Automation.SemanticVersion]::TryParse($candidateTag, [ref]$candidate)) {
|
|
continue
|
|
}
|
|
if ($candidate.Major -ne $target.Major -or $candidate -ge $target) {
|
|
continue
|
|
}
|
|
if ($null -eq $best -or $candidate -gt $best) {
|
|
$best = $candidate
|
|
$bestTag = $candidateTag
|
|
}
|
|
}
|
|
return $bestTag
|
|
}
|
|
|
|
function Test-StablePackageVersion {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns $true when a package version is an exact stable
|
|
major.minor.patch release, for example "2.0.0". The predicate
|
|
deliberately rejects prerelease/build metadata and an old
|
|
four-component NBGV build version such as "2.0.0.7": package
|
|
patches are explicit release decisions, never git height.
|
|
#>
|
|
param([Parameter(Mandatory)][string]$Version)
|
|
|
|
return $Version -match '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$'
|
|
}
|
|
|
|
function Test-PromotablePreviewPackageVersion {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns $true when a package version is a public release-line
|
|
preview, exactly "major.minor.patch-preview.N", for example
|
|
"2.0.0-preview.6". This is the only preview shape release.yml may
|
|
promote to nuget.org.
|
|
.DESCRIPTION
|
|
A canonical release/M.m branch is a public release for
|
|
Nerdbank.GitVersioning, so its previews carry no commit id. Every
|
|
other shape is rejected: "-preview.N.gabc123" and "+gabc123" are
|
|
non-public builds (master, PR and feature branches), and anything
|
|
else ("-rc.1", "-preview", a four-component version) is not a
|
|
numbered preview this repository ever produces. The label is matched
|
|
case-sensitively (-cmatch): "-PREVIEW.6" is not the shape NBGV emits.
|
|
#>
|
|
param([Parameter(Mandatory)][string]$Version)
|
|
|
|
return $Version -cmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-preview\.(0|[1-9]\d*)$'
|
|
}
|
|
|
|
function Test-CanonicalReleaseBranchRef {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns $true only for the exact maintained release-line branch
|
|
naming this repository publishes stable packages from, e.g.
|
|
"refs/heads/release/2.0" or "refs/heads/release/2.1". Rejects the
|
|
historical three-component "release/2.0.0" naming, any other
|
|
"release/*" branch, and every non-release ref (including master).
|
|
.DESCRIPTION
|
|
The expression must stay byte-identical to version.json's
|
|
publicReleaseRefSpec (pinned by
|
|
VersionJsonPublicReleaseRefSpecMatchesThePolicyFunction) and to the
|
|
copy release.yml inlines before it is allowed to check out repository
|
|
code. Two properties of that expression are load-bearing:
|
|
|
|
- The leading "(?-i)" forces a case-sensitive match. PowerShell's
|
|
-match is case-insensitive by default while Nerdbank.GitVersioning
|
|
matches publicReleaseRefSpec case-sensitively (measured: nbgv
|
|
3.7.115 reports PublicRelease=False on "Release/2.0"). Without it
|
|
this helper would report "refs/heads/Release/2.0" as canonical and
|
|
nuget-publish.yml would publish previews from a branch that
|
|
version.json does not recognize as a release line at all.
|
|
- Each component is "(0|[1-9]\d*)", not "\d+", so a leading zero is
|
|
not a second spelling of a release line. "release/02.0" otherwise
|
|
makes nbgv treat the branch as a public release (measured), which
|
|
gates the stable Docker version/latest aliases, while
|
|
Test-CanonicalReleaseBranchForPackageVersion compares the branch
|
|
components to the version as text and rejects it - a branch that
|
|
can move stable image aliases but never ship the matching package.
|
|
#>
|
|
param([Parameter(Mandatory)][string]$Ref)
|
|
|
|
return $Ref -match '(?-i)^refs/heads/release/(0|[1-9]\d*)\.(0|[1-9]\d*)$'
|
|
}
|
|
|
|
function Test-CanonicalReleaseBranchForPackageVersion {
|
|
<#
|
|
.SYNOPSIS
|
|
Returns $true only when a canonical release/M.m branch matches the
|
|
major/minor components of an exact stable M.m.p package version.
|
|
This prevents a 2.1.0 candidate being built or promoted from
|
|
release/2.0 (or the inverse).
|
|
.DESCRIPTION
|
|
Both sides are restricted to canonical decimal components so the
|
|
comparison below can stay textual: "release/02.0" is not an alternate
|
|
spelling of the 2.0 line, it is simply not a release line. See
|
|
Test-CanonicalReleaseBranchRef for why case and leading zeroes matter.
|
|
|
|
-AllowPreview additionally accepts a public release-line preview
|
|
(Test-PromotablePreviewPackageVersion, e.g. "2.0.0-preview.6") under
|
|
the same major/minor rule. Only release.yml's explicit preview
|
|
promotion passes it; the stable gates in nuget-publish.yml and
|
|
release.yml never do.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)][string]$Ref,
|
|
[Parameter(Mandatory)][string]$Version,
|
|
[switch]$AllowPreview
|
|
)
|
|
|
|
$isStable = Test-StablePackageVersion -Version $Version
|
|
$isPreview = $AllowPreview -and (Test-PromotablePreviewPackageVersion -Version $Version)
|
|
if (-not ($isStable -or $isPreview)) {
|
|
return $false
|
|
}
|
|
|
|
$branchMatch = [regex]::Match(
|
|
$Ref, '(?-i)^refs/heads/release/(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)$')
|
|
if (-not $branchMatch.Success) {
|
|
return $false
|
|
}
|
|
|
|
$versionMatch = [regex]::Match(
|
|
$Version, '^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-preview\.(?:0|[1-9]\d*))?$')
|
|
return $versionMatch.Success -and
|
|
$branchMatch.Groups['major'].Value -eq $versionMatch.Groups['major'].Value -and
|
|
$branchMatch.Groups['minor'].Value -eq $versionMatch.Groups['minor'].Value
|
|
}
|