440e7cf03d
Code review of the CLI-39 branch caught an Important gap: Contracts.csproj was left at the already-published 0.1.2 while the .NET Client moved to 0.2.0. Invoke-PackDotnet in scripts/pack-clients.ps1 packs and publishes both ZB.MOM.WW.MxGateway.Contracts and .Client through the same -Publish loop, and the new collision guard runs every nupkg it finds through Assert-GiteaPackageNotPublished. Left as-is, the next real .NET publish would pack Contracts at 0.1.2, the guard would correctly refuse to republish it, and the loop would abort mid-way with Client (alphabetically first) possibly already pushed -- the two packages permanently out of lockstep. - src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj: <Version> 0.1.2 -> 0.2.0, matching the .NET Client (they have always released together). - src/Directory.Build.props: corrected a comment that was now stale -- it claimed the repo-wide 0.1.2 default was kept to match the Contracts package, which is no longer true now that Contracts.csproj overrides it. The <Version> value itself is unchanged; Server/Worker/Tests staying at 0.1.2 is a separate, not-yet-made decision, out of scope for CLI-39. - docs/ClientPackaging.md: Contracts.csproj added as a fifth manifest in the Versioning section, with the near-miss recorded. Also hardened scripts/pack-clients.ps1 per the same review: the Python (pyproject.toml) and Rust (Cargo.toml) version-extraction regexes now scope to the [project]/[package] section header instead of matching the first "version = ..." line anywhere in the file (Cargo.toml has an identical second one under [workspace.package] -- matching whichever came first was luck of ordering, not correctness). One-line comment added on the nuget filename-parse regex. Verified live against the real Gitea registry: Contracts and Client both still refuse at 0.1.2 and both now pass at 0.2.0, including running the actual Invoke-PackDotnet filename-parse-then-guard logic against two freshly packed real .nupkg files. dotnet build of Contracts.csproj and the client slnx both clean. No publish performed.
431 lines
17 KiB
PowerShell
431 lines
17 KiB
PowerShell
#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 {
|
|
# nupkg filenames are '<PackageId>.<Version>.nupkg'; the id itself contains
|
|
# dots (e.g. 'ZB.MOM.WW.MxGateway.Client'), so the id capture is lazy and the
|
|
# version capture anchors on the leading digit to split at the right dot.
|
|
$fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
|
|
if ($fileBaseName -notmatch '^(?<id>.+?)\.(?<version>\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
|
|
# Scope to the [project] section (not just the first "version = ..." line
|
|
# in the file) — [build-system]/[tool.*] sections can carry their own
|
|
# version-shaped keys, and matching the file's first hit would be luck
|
|
# of ordering, not correctness.
|
|
if ($pyprojectContent -notmatch '(?ms)^\[project\](?<section>.*?)(?=^\[|\z)') {
|
|
throw "Could not find a [project] section in '$pyprojectPath'."
|
|
}
|
|
if ($Matches.section -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
|
|
# Scope to the [package] section specifically — Cargo.toml also carries a
|
|
# [workspace.package] section with its own "version = ..." line (today
|
|
# identical, by convention, not by anything this regex can rely on), and
|
|
# matching whichever comes first in the file is luck of ordering.
|
|
if ($cargoTomlContent -notmatch '(?ms)^\[package\](?<section>.*?)(?=^\[|\z)') {
|
|
throw "Could not find a [package] section in '$cargoTomlPath'."
|
|
}
|
|
if ($Matches.section -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
|