fix(shims): add Linux fallback for venv Python path in all PS shims
All shims defaulted to F:\CI\python\venv\Scripts\python.exe when CI_VENV_PYTHON was not set. On Linux host this caused instant failure. Now uses $IsWindows (PS 6+ automatic var; absent on PS 5.1 = Windows) to select /opt/ci/venv/bin/python on Linux. Windows paths unchanged. Affects: Invoke-CIJob, Get-BuildArtifacts, Cleanup-OrphanedBuildVMs, New-BuildVM, Remove-BuildVM, Wait-VMReady, Get-CIJobSummary, Invoke-RemoteBuild, Watch-DiskSpace, Watch-RunnerHealth, Set-CIGuestCredential, Test-CIGuestWinRM. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,44 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator vm cleanup @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator vm cleanup @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -57,6 +57,13 @@ if ($Credential) {
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator artifacts collect @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
+222
-215
@@ -1,215 +1,222 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator report job @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
|
||||
.DESCRIPTION
|
||||
Scans F:\CI\Logs\ (or a custom path) for invoke-ci.jsonl files and
|
||||
prints a compact table with job ID, status, total elapsed time, and the
|
||||
last error message (if any).
|
||||
|
||||
Each row corresponds to one completed (or failed) CI job.
|
||||
Use -JobId to inspect a single job in detail.
|
||||
|
||||
.PARAMETER LogDir
|
||||
Base directory containing per-job log subdirectories.
|
||||
Default: F:\CI\Logs
|
||||
|
||||
.PARAMETER Last
|
||||
Show only the N most recent jobs (sorted by job-start timestamp).
|
||||
Default: 20
|
||||
|
||||
.PARAMETER JobId
|
||||
When specified, shows a detailed phase-by-phase breakdown for that job.
|
||||
|
||||
.PARAMETER Failed
|
||||
When specified, filters to failed jobs only.
|
||||
|
||||
.EXAMPLE
|
||||
# Show last 20 jobs
|
||||
.\Get-CIJobSummary.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Show last 5 failed jobs
|
||||
.\Get-CIJobSummary.ps1 -Last 5 -Failed
|
||||
|
||||
.EXAMPLE
|
||||
# Detailed breakdown for a specific job
|
||||
.\Get-CIJobSummary.ps1 -JobId 'run-12345'
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $LogDir = 'F:\CI\Logs',
|
||||
|
||||
[ValidateRange(1, 1000)]
|
||||
[int] $Last = 20,
|
||||
|
||||
[string] $JobId = '',
|
||||
|
||||
[switch] $Failed
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
function Format-SecToHMS {
|
||||
param([int] $Seconds)
|
||||
if ($Seconds -lt 0) { return '?' }
|
||||
$h = [math]::Floor($Seconds / 3600)
|
||||
$m = [math]::Floor(($Seconds % 3600) / 60)
|
||||
$s = $Seconds % 60
|
||||
if ($h -gt 0) { return '{0}h{1:00}m{2:00}s' -f $h, $m, $s }
|
||||
return '{0}m{1:00}s' -f $m, $s
|
||||
}
|
||||
|
||||
function Read-JobEvents {
|
||||
param([string] $JsonlPath)
|
||||
$events = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
foreach ($line in (Get-Content $JsonlPath -Encoding UTF8 -ErrorAction SilentlyContinue)) {
|
||||
$line = $line.Trim()
|
||||
if (-not $line) { continue }
|
||||
try {
|
||||
$events.Add(($line | ConvertFrom-Json))
|
||||
} catch {
|
||||
Write-Debug "[Get-CIJobSummary] Skipping malformed JSONL line in $JsonlPath"
|
||||
}
|
||||
}
|
||||
return $events
|
||||
}
|
||||
|
||||
# ── Validate log dir ────────────────────────────────────────────────────────
|
||||
if (-not (Test-Path $LogDir -PathType Container)) {
|
||||
Write-Warning "Log directory not found: $LogDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Single-job detail mode ──────────────────────────────────────────────────
|
||||
if ($JobId -ne '') {
|
||||
$jsonlPath = Join-Path $LogDir (Join-Path $JobId 'invoke-ci.jsonl')
|
||||
if (-not (Test-Path $jsonlPath)) {
|
||||
Write-Warning "No JSONL log found for job '$JobId' at: $jsonlPath"
|
||||
exit 1
|
||||
}
|
||||
$events = Read-JobEvents -JsonlPath $jsonlPath
|
||||
if ($events.Count -eq 0) {
|
||||
Write-Warning "JSONL file is empty or unreadable: $jsonlPath"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "`nJob: $JobId"
|
||||
Write-Host ('=' * 60)
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f 'Phase', 'Status', 'Timestamp')
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f ('-'*35), ('-'*10), ('-'*24))
|
||||
foreach ($ev in $events) {
|
||||
$ts = if ($ev.ts) { $ev.ts } else { '' }
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f $ev.phase, $ev.status, $ts)
|
||||
}
|
||||
# Print error if present
|
||||
$failEvent = $events | Where-Object { $_.status -eq 'failure' } | Select-Object -Last 1
|
||||
if ($failEvent -and $failEvent.data -and $failEvent.data.error) {
|
||||
Write-Host "`nError: $($failEvent.data.error)"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Scan all job JSONL files ─────────────────────────────────────────────────
|
||||
$jsonlFiles = Get-ChildItem -Path $LogDir -Recurse -Filter 'invoke-ci.jsonl' -ErrorAction SilentlyContinue |
|
||||
Sort-Object { $_.LastWriteTime } -Descending
|
||||
|
||||
if ($jsonlFiles.Count -eq 0) {
|
||||
Write-Host "No invoke-ci.jsonl files found under $LogDir"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$rows = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
foreach ($file in $jsonlFiles) {
|
||||
$events = Read-JobEvents -JsonlPath $file.FullName
|
||||
if ($events.Count -eq 0) { continue }
|
||||
|
||||
$jobEvent = $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'start' } | Select-Object -First 1
|
||||
$successEvent= $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'success' } | Select-Object -First 1
|
||||
$failEvent = $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'failure' } | Select-Object -First 1
|
||||
|
||||
$jId = if ($events[0].jobId) { $events[0].jobId } else { (Split-Path (Split-Path $file.FullName -Parent) -Leaf) }
|
||||
$started = if ($jobEvent -and $jobEvent.ts) { $jobEvent.ts } else { '' }
|
||||
|
||||
if ($successEvent) {
|
||||
$status = 'success'
|
||||
$elSec = if ($successEvent.data -and $null -ne $successEvent.data.elapsedSec) { [int]$successEvent.data.elapsedSec } else { -1 }
|
||||
$errMsg = ''
|
||||
} elseif ($failEvent) {
|
||||
$status = 'FAILED'
|
||||
$elSec = if ($failEvent.data -and $null -ne $failEvent.data.elapsedSec) { [int]$failEvent.data.elapsedSec } else { -1 }
|
||||
$errMsg = if ($failEvent.data -and $failEvent.data.error) { "$($failEvent.data.error)" } else { '' }
|
||||
# Truncate error for table display
|
||||
if ($errMsg.Length -gt 60) { $errMsg = $errMsg.Substring(0, 57) + '...' }
|
||||
} else {
|
||||
$status = 'in-progress'
|
||||
$elSec = -1
|
||||
$errMsg = ''
|
||||
}
|
||||
|
||||
$rows.Add([PSCustomObject]@{
|
||||
JobId = $jId
|
||||
Status = $status
|
||||
Elapsed = Format-SecToHMS -Seconds $elSec
|
||||
Started = $started
|
||||
Error = $errMsg
|
||||
})
|
||||
}
|
||||
|
||||
# ── Apply filters + limit ────────────────────────────────────────────────────
|
||||
if ($Failed) {
|
||||
$rows = @($rows | Where-Object { $_.Status -eq 'FAILED' })
|
||||
}
|
||||
|
||||
$rows = @($rows | Select-Object -First $Last)
|
||||
|
||||
if ($rows.Count -eq 0) {
|
||||
Write-Host "No matching jobs found."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Print table ──────────────────────────────────────────────────────────────
|
||||
Write-Host "`nCI Job Summary (last $($rows.Count) jobs):"
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f 'JobId', 'Status', 'Elapsed', 'Started', 'Error')
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f ('-'*40), ('-'*12), ('-'*10), ('-'*26), ('-'*30))
|
||||
foreach ($r in $rows) {
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f $r.JobId, $r.Status, $r.Elapsed, $r.Started, $r.Error)
|
||||
}
|
||||
Write-Host ''
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator report job @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
|
||||
.DESCRIPTION
|
||||
Scans F:\CI\Logs\ (or a custom path) for invoke-ci.jsonl files and
|
||||
prints a compact table with job ID, status, total elapsed time, and the
|
||||
last error message (if any).
|
||||
|
||||
Each row corresponds to one completed (or failed) CI job.
|
||||
Use -JobId to inspect a single job in detail.
|
||||
|
||||
.PARAMETER LogDir
|
||||
Base directory containing per-job log subdirectories.
|
||||
Default: F:\CI\Logs
|
||||
|
||||
.PARAMETER Last
|
||||
Show only the N most recent jobs (sorted by job-start timestamp).
|
||||
Default: 20
|
||||
|
||||
.PARAMETER JobId
|
||||
When specified, shows a detailed phase-by-phase breakdown for that job.
|
||||
|
||||
.PARAMETER Failed
|
||||
When specified, filters to failed jobs only.
|
||||
|
||||
.EXAMPLE
|
||||
# Show last 20 jobs
|
||||
.\Get-CIJobSummary.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Show last 5 failed jobs
|
||||
.\Get-CIJobSummary.ps1 -Last 5 -Failed
|
||||
|
||||
.EXAMPLE
|
||||
# Detailed breakdown for a specific job
|
||||
.\Get-CIJobSummary.ps1 -JobId 'run-12345'
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $LogDir = 'F:\CI\Logs',
|
||||
|
||||
[ValidateRange(1, 1000)]
|
||||
[int] $Last = 20,
|
||||
|
||||
[string] $JobId = '',
|
||||
|
||||
[switch] $Failed
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
function Format-SecToHMS {
|
||||
param([int] $Seconds)
|
||||
if ($Seconds -lt 0) { return '?' }
|
||||
$h = [math]::Floor($Seconds / 3600)
|
||||
$m = [math]::Floor(($Seconds % 3600) / 60)
|
||||
$s = $Seconds % 60
|
||||
if ($h -gt 0) { return '{0}h{1:00}m{2:00}s' -f $h, $m, $s }
|
||||
return '{0}m{1:00}s' -f $m, $s
|
||||
}
|
||||
|
||||
function Read-JobEvents {
|
||||
param([string] $JsonlPath)
|
||||
$events = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
foreach ($line in (Get-Content $JsonlPath -Encoding UTF8 -ErrorAction SilentlyContinue)) {
|
||||
$line = $line.Trim()
|
||||
if (-not $line) { continue }
|
||||
try {
|
||||
$events.Add(($line | ConvertFrom-Json))
|
||||
} catch {
|
||||
Write-Debug "[Get-CIJobSummary] Skipping malformed JSONL line in $JsonlPath"
|
||||
}
|
||||
}
|
||||
return $events
|
||||
}
|
||||
|
||||
# ── Validate log dir ────────────────────────────────────────────────────────
|
||||
if (-not (Test-Path $LogDir -PathType Container)) {
|
||||
Write-Warning "Log directory not found: $LogDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Single-job detail mode ──────────────────────────────────────────────────
|
||||
if ($JobId -ne '') {
|
||||
$jsonlPath = Join-Path $LogDir (Join-Path $JobId 'invoke-ci.jsonl')
|
||||
if (-not (Test-Path $jsonlPath)) {
|
||||
Write-Warning "No JSONL log found for job '$JobId' at: $jsonlPath"
|
||||
exit 1
|
||||
}
|
||||
$events = Read-JobEvents -JsonlPath $jsonlPath
|
||||
if ($events.Count -eq 0) {
|
||||
Write-Warning "JSONL file is empty or unreadable: $jsonlPath"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "`nJob: $JobId"
|
||||
Write-Host ('=' * 60)
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f 'Phase', 'Status', 'Timestamp')
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f ('-'*35), ('-'*10), ('-'*24))
|
||||
foreach ($ev in $events) {
|
||||
$ts = if ($ev.ts) { $ev.ts } else { '' }
|
||||
Write-Host ('{0,-35} {1,-10} {2}' -f $ev.phase, $ev.status, $ts)
|
||||
}
|
||||
# Print error if present
|
||||
$failEvent = $events | Where-Object { $_.status -eq 'failure' } | Select-Object -Last 1
|
||||
if ($failEvent -and $failEvent.data -and $failEvent.data.error) {
|
||||
Write-Host "`nError: $($failEvent.data.error)"
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Scan all job JSONL files ─────────────────────────────────────────────────
|
||||
$jsonlFiles = Get-ChildItem -Path $LogDir -Recurse -Filter 'invoke-ci.jsonl' -ErrorAction SilentlyContinue |
|
||||
Sort-Object { $_.LastWriteTime } -Descending
|
||||
|
||||
if ($jsonlFiles.Count -eq 0) {
|
||||
Write-Host "No invoke-ci.jsonl files found under $LogDir"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$rows = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
foreach ($file in $jsonlFiles) {
|
||||
$events = Read-JobEvents -JsonlPath $file.FullName
|
||||
if ($events.Count -eq 0) { continue }
|
||||
|
||||
$jobEvent = $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'start' } | Select-Object -First 1
|
||||
$successEvent= $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'success' } | Select-Object -First 1
|
||||
$failEvent = $events | Where-Object { $_.phase -eq 'job' -and $_.status -eq 'failure' } | Select-Object -First 1
|
||||
|
||||
$jId = if ($events[0].jobId) { $events[0].jobId } else { (Split-Path (Split-Path $file.FullName -Parent) -Leaf) }
|
||||
$started = if ($jobEvent -and $jobEvent.ts) { $jobEvent.ts } else { '' }
|
||||
|
||||
if ($successEvent) {
|
||||
$status = 'success'
|
||||
$elSec = if ($successEvent.data -and $null -ne $successEvent.data.elapsedSec) { [int]$successEvent.data.elapsedSec } else { -1 }
|
||||
$errMsg = ''
|
||||
} elseif ($failEvent) {
|
||||
$status = 'FAILED'
|
||||
$elSec = if ($failEvent.data -and $null -ne $failEvent.data.elapsedSec) { [int]$failEvent.data.elapsedSec } else { -1 }
|
||||
$errMsg = if ($failEvent.data -and $failEvent.data.error) { "$($failEvent.data.error)" } else { '' }
|
||||
# Truncate error for table display
|
||||
if ($errMsg.Length -gt 60) { $errMsg = $errMsg.Substring(0, 57) + '...' }
|
||||
} else {
|
||||
$status = 'in-progress'
|
||||
$elSec = -1
|
||||
$errMsg = ''
|
||||
}
|
||||
|
||||
$rows.Add([PSCustomObject]@{
|
||||
JobId = $jId
|
||||
Status = $status
|
||||
Elapsed = Format-SecToHMS -Seconds $elSec
|
||||
Started = $started
|
||||
Error = $errMsg
|
||||
})
|
||||
}
|
||||
|
||||
# ── Apply filters + limit ────────────────────────────────────────────────────
|
||||
if ($Failed) {
|
||||
$rows = @($rows | Where-Object { $_.Status -eq 'FAILED' })
|
||||
}
|
||||
|
||||
$rows = @($rows | Select-Object -First $Last)
|
||||
|
||||
if ($rows.Count -eq 0) {
|
||||
Write-Host "No matching jobs found."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Print table ──────────────────────────────────────────────────────────────
|
||||
Write-Host "`nCI Job Summary (last $($rows.Count) jobs):"
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f 'JobId', 'Status', 'Elapsed', 'Started', 'Error')
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f ('-'*40), ('-'*12), ('-'*10), ('-'*26), ('-'*30))
|
||||
foreach ($r in $rows) {
|
||||
Write-Host ('{0,-40} {1,-12} {2,-10} {3,-26} {4}' -f $r.JobId, $r.Status, $r.Elapsed, $r.Started, $r.Error)
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
@@ -32,6 +32,13 @@ for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator job @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -86,6 +86,13 @@ if ($Credential) {
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator build run @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -31,6 +31,13 @@ $pyArgs = @(
|
||||
)
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator vm new @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -35,6 +35,13 @@ for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator vm remove @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -68,6 +68,7 @@ param(
|
||||
[System.Security.SecureString] $Password,
|
||||
|
||||
[string] $VenvPython = $(if ($env:CI_VENV_PYTHON) { $env:CI_VENV_PYTHON }
|
||||
elseif ($null -ne $IsWindows -and $IsWindows -eq $false) { '/opt/ci/venv/bin/python' }
|
||||
else { 'F:\CI\python\venv\Scripts\python.exe' }),
|
||||
|
||||
[switch] $SkipVerify
|
||||
|
||||
@@ -74,6 +74,7 @@ param(
|
||||
[string[]] $AuthOrder = @('ntlm', 'negotiate'),
|
||||
|
||||
[string] $VenvPython = $(if ($env:CI_VENV_PYTHON) { $env:CI_VENV_PYTHON }
|
||||
elseif ($null -ne $IsWindows -and $IsWindows -eq $false) { '/opt/ci/venv/bin/python' }
|
||||
else { 'F:\CI\python\venv\Scripts\python.exe' }),
|
||||
|
||||
[ValidateRange(5, 300)]
|
||||
|
||||
+44
-37
@@ -1,37 +1,44 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator wait-ready @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator wait-ready @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
+44
-37
@@ -1,37 +1,44 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator monitor disk @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator monitor disk @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator monitor runner @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
#Requires -Version 5.1
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
# PowerShell common params (-ErrorAction etc.) must not be forwarded
|
||||
# to the Python CLI. Value-taking ones also consume the next token.
|
||||
$commonWithValue = @{
|
||||
erroraction = $true; warningaction = $true; informationaction = $true
|
||||
progressaction = $true; errorvariable = $true; warningvariable = $true
|
||||
informationvariable = $true; outvariable = $true; outbuffer = $true
|
||||
pipelinevariable = $true
|
||||
}
|
||||
$commonSwitch = @{ verbose = $true; debug = $true; whatif = $true; confirm = $true }
|
||||
|
||||
$pyArgs = @()
|
||||
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||
$a = $args[$i]
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$lname = $name.ToLower()
|
||||
if ($commonSwitch.ContainsKey($lname)) { continue }
|
||||
if ($commonWithValue.ContainsKey($lname)) { $i++; continue }
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) {
|
||||
# $IsWindows exists only on PS 6+; absent on PS 5.1 (always Windows)
|
||||
if ($null -ne $IsWindows -and $IsWindows -eq $false) {
|
||||
$venvPython = '/opt/ci/venv/bin/python'
|
||||
} else {
|
||||
$venvPython = 'F:\CI\python\venv\Scripts\python.exe'
|
||||
}
|
||||
}
|
||||
& $venvPython -m ci_orchestrator monitor runner @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
Reference in New Issue
Block a user