#Requires -Version 7 <# .SYNOPSIS Packs all MxAccessGateway clients into a single dist/ directory. .DESCRIPTION Runs each language client's native packaging command: .NET -> dotnet pack (NuGet) Python -> python -m build (sdist + wheel) Rust -> cargo package (.crate) Java -> gradle assemble + jars (jar + sources + javadoc + pom) Go -> skipped; use scripts/tag-go-module.ps1 All artifacts land in -OutputDir (default: dist/). With -Publish, each language pushes its package to the internal Gitea feed. Requires GITEA_USERNAME and GITEA_TOKEN env vars. .PARAMETER OutputDir Where to drop the packed artifacts. Default: ./dist .PARAMETER Languages Subset of languages to pack. Default: all five. Values: dotnet, python, rust, java, go .PARAMETER Publish After packing, upload to Gitea feeds. Requires: GITEA_USERNAME GITEA_TOKEN Will refuse to publish if either is missing. .PARAMETER SkipTests Skip per-language regression tests before packing. Default: false. .EXAMPLE pwsh scripts/pack-clients.ps1 pwsh scripts/pack-clients.ps1 -Languages dotnet,python pwsh scripts/pack-clients.ps1 -Publish #> [CmdletBinding()] param( [string]$OutputDir = (Join-Path $PSScriptRoot '..' 'dist'), [string[]]$Languages = @('dotnet', 'python', 'rust', 'java', 'go'), [switch]$Publish, [switch]$SkipTests ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Normalize comma-separated strings that shells may pass as a single element. $validLanguages = @('dotnet', 'python', 'rust', 'java', 'go') $Languages = @($Languages | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim().ToLowerInvariant() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) foreach ($lang in $Languages) { if ($validLanguages -notcontains $lang) { throw "Unsupported language '$lang'. Supported values: $($validLanguages -join ', ')." } } if ($Languages.Count -eq 0) { throw "At least one language is required. Supported values: $($validLanguages -join ', ')." } # Resolve absolute output dir $OutputDir = [System.IO.Path]::GetFullPath($OutputDir) $RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null } if ($Publish) { if ([string]::IsNullOrEmpty($env:GITEA_USERNAME)) { throw 'Publish requires GITEA_USERNAME env var.' } if ([string]::IsNullOrEmpty($env:GITEA_TOKEN)) { throw 'Publish requires GITEA_TOKEN env var.' } } $GiteaNugetFeed = 'https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json' $GiteaPypiFeed = 'https://gitea.dohertylan.com/api/packages/dohertj2/pypi' $JavaHome = '/Users/dohertj2/.local/jdks/jdk-21.0.11+10/Contents/Home' # Generic Gitea package registry API (https://gitea.dohertylan.com/api/v1/packages/{owner}/{type}/{name}/{version}): # returns 200 when that exact name+version already exists in the given feed # type, 404 when it does not. Used as a pre-publish collision guard (CLI-39) # so a re-run of this script can never silently overwrite a published artifact. $GiteaPackageApiBase = 'https://gitea.dohertylan.com/api/v1/packages/dohertj2' function Write-Header { param([string]$Text) Write-Host '' Write-Host '=== ' -NoNewline -ForegroundColor Cyan Write-Host $Text -ForegroundColor Cyan } function Test-GiteaPackageExists { <# .SYNOPSIS Queries the Gitea package API for an existing name+version in a feed. .OUTPUTS $true if the package/version already exists, $false if it does not. Throws if the registry cannot be reached or returns anything other than 200/404 — callers must treat "cannot verify" as "do not publish". #> param( [Parameter(Mandatory)][string]$Type, [Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Version ) $uri = "$GiteaPackageApiBase/$Type/$Name/$Version" $headers = @{} if (-not [string]::IsNullOrEmpty($env:GITEA_TOKEN)) { $user = if ([string]::IsNullOrEmpty($env:GITEA_USERNAME)) { 'dohertj2' } else { $env:GITEA_USERNAME } $pair = "$($user):$($env:GITEA_TOKEN)" $basic = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $headers['Authorization'] = "Basic $basic" } try { $response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Get -UseBasicParsing -ErrorAction Stop return ($response.StatusCode -eq 200) } catch { $statusCode = $null if ($_.Exception.PSObject.Properties['Response'] -and $_.Exception.Response) { $statusCode = [int]$_.Exception.Response.StatusCode } if ($statusCode -eq 404) { return $false } throw "Unable to query the Gitea package API for '$Type/$Name/$Version' ($uri): $($_.Exception.Message). Refusing to publish without a collision check — verify manually (or check GITEA_USERNAME/GITEA_TOKEN/network) and retry." } } function Assert-GiteaPackageNotPublished { <# .SYNOPSIS Aborts the script if $Name/$Version already exists in the $Type feed. Never force-overwrites a published artifact (CLI-39). #> param( [Parameter(Mandatory)][string]$Type, [Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Version ) Write-Host "Checking Gitea '$Type' feed for existing '$Name' $Version..." if (Test-GiteaPackageExists -Type $Type -Name $Name -Version $Version) { throw "Gitea package '$Name' version '$Version' already exists in the '$Type' feed. Bump the client version before publishing — this script never force-overwrites a published artifact." } Write-Host " Not found in the '$Type' feed — safe to publish '$Name' $Version." -ForegroundColor Green } # -------- .NET -------- function Invoke-PackDotnet { Write-Header '.NET' if (-not $SkipTests) { Write-Host 'Running .NET client tests...' $testProject = Join-Path $RepoRoot 'clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/ZB.MOM.WW.MxGateway.Client.Tests.csproj' & dotnet test $testProject --no-restore if ($LASTEXITCODE -ne 0) { throw '.NET tests failed.' } } Write-Host 'Packing ZB.MOM.WW.MxGateway.Contracts...' & dotnet pack (Join-Path $RepoRoot 'src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj') ` -c Release -o $OutputDir if ($LASTEXITCODE -ne 0) { throw '.NET Contracts pack failed.' } Write-Host 'Packing ZB.MOM.WW.MxGateway.Client...' & dotnet pack (Join-Path $RepoRoot 'clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj') ` -c Release -o $OutputDir if ($LASTEXITCODE -ne 0) { throw '.NET Client pack failed.' } Write-Host "Packed .NET artifacts -> $OutputDir" -ForegroundColor Green if ($Publish) { Write-Host 'Publishing .NET packages to Gitea...' -ForegroundColor Yellow Get-ChildItem $OutputDir -Filter 'ZB.MOM.WW.MxGateway.*.nupkg' | ForEach-Object { $fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) if ($fileBaseName -notmatch '^(?.+?)\.(?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$') { throw "Could not parse a NuGet package id/version out of '$($_.Name)'." } Assert-GiteaPackageNotPublished -Type 'nuget' -Name $Matches.id -Version $Matches.version & dotnet nuget push $_.FullName --source $GiteaNugetFeed --api-key $env:GITEA_TOKEN if ($LASTEXITCODE -ne 0) { throw "dotnet nuget push failed for '$($_.Name)'." } } } } # -------- Python -------- function Invoke-PackPython { Write-Header 'Python' # Use a persistent venv in /tmp so repeated runs skip reinstall. $Venv = '/tmp/mxgw-py' if (-not (Test-Path "$Venv/bin/python")) { Write-Host "Creating Python venv at $Venv..." & python3 -m venv $Venv if ($LASTEXITCODE -ne 0) { throw 'python3 -m venv failed.' } & "$Venv/bin/pip" install --quiet --upgrade pip & "$Venv/bin/pip" install --quiet build twine & "$Venv/bin/pip" install --quiet -e (Join-Path $RepoRoot 'clients/python[dev]') } if (-not $SkipTests) { Write-Host 'Running Python tests...' Push-Location (Join-Path $RepoRoot 'clients/python') try { & "$Venv/bin/python" -m pytest -q if ($LASTEXITCODE -ne 0) { throw 'Python tests failed.' } } finally { Pop-Location } } Write-Host 'Building Python sdist + wheel...' & "$Venv/bin/python" -m build (Join-Path $RepoRoot 'clients/python') --outdir $OutputDir if ($LASTEXITCODE -ne 0) { throw 'Python build failed.' } Write-Host "Packed Python artifacts -> $OutputDir" -ForegroundColor Green if ($Publish) { $pyprojectPath = Join-Path $RepoRoot 'clients/python/pyproject.toml' $pyprojectContent = Get-Content $pyprojectPath -Raw if ($pyprojectContent -notmatch '(?m)^\s*version\s*=\s*"([^"]+)"') { throw "Could not find [project].version in '$pyprojectPath'." } Assert-GiteaPackageNotPublished -Type 'pypi' -Name 'zb-mom-ww-mxaccess-gateway-client' -Version $Matches[1] Write-Host 'Publishing Python distribution to Gitea...' -ForegroundColor Yellow $wheels = @(Get-ChildItem $OutputDir -Filter 'zb_mom_ww_mxaccess_gateway_client-*.whl') $sdists = @(Get-ChildItem $OutputDir -Filter 'zb_mom_ww_mxaccess_gateway_client-*.tar.gz') $files = ($wheels + $sdists) | ForEach-Object { $_.FullName } & "$Venv/bin/python" -m twine upload ` --repository-url $GiteaPypiFeed ` -u $env:GITEA_USERNAME ` -p $env:GITEA_TOKEN ` @files if ($LASTEXITCODE -ne 0) { throw 'twine upload failed.' } } } # -------- Rust -------- function Invoke-PackRust { Write-Header 'Rust' $rustDir = Join-Path $RepoRoot 'clients/rust' Push-Location $rustDir try { if (-not $SkipTests) { Write-Host 'Running Rust tests...' & cargo test --workspace if ($LASTEXITCODE -ne 0) { throw 'Rust tests failed.' } } Write-Host 'Running cargo package...' # No --no-verify: the crate vendors its protos (clients/rust/protos) so # the verify build proves it compiles standalone from the tarball (CLI-02). & cargo package if ($LASTEXITCODE -ne 0) { throw 'cargo package failed.' } $packageDir = Join-Path $rustDir 'target/package' $crates = @(Get-ChildItem $packageDir -Filter '*.crate') if ($crates.Count -eq 0) { throw 'cargo package produced no .crate files.' } foreach ($crate in $crates) { Copy-Item $crate.FullName -Destination $OutputDir -Force Write-Host " Copied $($crate.Name)" } } finally { Pop-Location } Write-Host "Packed Rust artifacts -> $OutputDir" -ForegroundColor Green if ($Publish) { $cargoTomlPath = Join-Path $rustDir 'Cargo.toml' $cargoTomlContent = Get-Content $cargoTomlPath -Raw if ($cargoTomlContent -notmatch '(?m)^\s*version\s*=\s*"([^"]+)"') { throw "Could not find [package] version in '$cargoTomlPath'." } Assert-GiteaPackageNotPublished -Type 'cargo' -Name 'zb-mom-ww-mxgateway-client' -Version $Matches[1] Write-Host 'Publishing Rust crate to Gitea...' -ForegroundColor Yellow Push-Location (Join-Path $RepoRoot 'clients/rust') try { & cargo publish --registry dohertj2-gitea if ($LASTEXITCODE -ne 0) { throw 'cargo publish failed.' } } finally { Pop-Location } } } # -------- Java -------- function Invoke-PackJava { Write-Header 'Java' $env:JAVA_HOME = $JavaHome $javaDir = Join-Path $RepoRoot 'clients/java' Push-Location $javaDir try { if (-not $SkipTests) { Write-Host 'Running Java tests...' & gradle ':zb-mom-ww-mxgateway-client:test' --no-daemon if ($LASTEXITCODE -ne 0) { throw 'Java tests failed.' } } Write-Host 'Assembling Java jars + pom...' & gradle ` ':zb-mom-ww-mxgateway-client:assemble' ` ':zb-mom-ww-mxgateway-client:sourcesJar' ` ':zb-mom-ww-mxgateway-client:javadocJar' ` ':zb-mom-ww-mxgateway-client:generatePomFileForMavenPublication' ` --no-daemon if ($LASTEXITCODE -ne 0) { throw 'Java assemble failed.' } $libsDir = Join-Path $javaDir 'zb-mom-ww-mxgateway-client/build/libs' $jars = @(Get-ChildItem $libsDir -Filter 'zb-mom-ww-mxgateway-client-*.jar') if ($jars.Count -eq 0) { throw "No jars found under '$libsDir'." } foreach ($jar in $jars) { Copy-Item $jar.FullName -Destination $OutputDir -Force Write-Host " Copied $($jar.Name)" } $pomSrc = Join-Path $javaDir 'zb-mom-ww-mxgateway-client/build/publications/maven/pom-default.xml' if (Test-Path $pomSrc) { # Derive the version from the jar filename (e.g. zb-mom-ww-mxgateway-client-0.1.0.jar). $versionJar = $jars | Where-Object { $_.Name -notmatch '-(sources|javadoc)\.jar$' } | Select-Object -First 1 $version = if ($versionJar) { [System.IO.Path]::GetFileNameWithoutExtension($versionJar.Name) -replace '^zb-mom-ww-mxgateway-client-', '' } else { '0.1.0' } $pomDest = Join-Path $OutputDir "zb-mom-ww-mxgateway-client-$version.pom" Copy-Item $pomSrc -Destination $pomDest -Force Write-Host " Copied pom -> $([System.IO.Path]::GetFileName($pomDest))" } else { Write-Warning "POM not found at '$pomSrc'; skipping." } } finally { Pop-Location } Write-Host "Packed Java artifacts -> $OutputDir" -ForegroundColor Green if ($Publish) { $buildGradlePath = Join-Path $javaDir 'build.gradle' $buildGradleContent = Get-Content $buildGradlePath -Raw if ($buildGradleContent -notmatch "(?m)^\s*group\s*=\s*'([^']+)'") { throw "Could not find subprojects { group = '...' } in '$buildGradlePath'." } $javaGroup = $Matches[1] if ($buildGradleContent -notmatch "(?m)^\s*version\s*=\s*'([^']+)'") { throw "Could not find subprojects { version = '...' } in '$buildGradlePath'." } $javaVersion = $Matches[1] # Gitea's Maven package API identifies the package as "groupId:artifactId", # not the bare artifact id — passing just the artifact id here would query # a name that never exists and silently defeat the guard. Assert-GiteaPackageNotPublished -Type 'maven' -Name "$javaGroup`:zb-mom-ww-mxgateway-client" -Version $javaVersion Write-Host 'Publishing Java artifacts to Gitea Maven feed...' -ForegroundColor Yellow Push-Location $javaDir try { & gradle ':zb-mom-ww-mxgateway-client:publish' --no-daemon if ($LASTEXITCODE -ne 0) { throw 'gradle publish failed.' } } finally { Pop-Location } } } # -------- Go -------- function Invoke-PackGo { Write-Header 'Go' Write-Host 'Go modules are released by git-tagging — no artifact to pack.' -ForegroundColor Yellow Write-Host 'To publish a Go release, run:' -ForegroundColor Yellow Write-Host ' pwsh scripts/tag-go-module.ps1 -Version v0.1.0 -Push' -ForegroundColor Yellow Write-Host '(skipping)' -ForegroundColor DarkGray } # -------- Dispatch -------- $wanted = @{} foreach ($lang in $Languages) { $wanted[$lang.ToLower()] = $true } if ($wanted.ContainsKey('dotnet')) { Invoke-PackDotnet } if ($wanted.ContainsKey('python')) { Invoke-PackPython } if ($wanted.ContainsKey('rust')) { Invoke-PackRust } if ($wanted.ContainsKey('java')) { Invoke-PackJava } if ($wanted.ContainsKey('go')) { Invoke-PackGo } # -------- Summary -------- Write-Header 'Summary' $artifacts = @(Get-ChildItem $OutputDir) if ($artifacts.Count -eq 0) { Write-Host ' (no artifacts)' -ForegroundColor DarkGray } else { foreach ($a in $artifacts) { Write-Host (' {0,10} {1}' -f $a.Length, $a.Name) } } Write-Host '' Write-Host "All artifacts in: $OutputDir" -ForegroundColor Green