diff --git a/scripts/Invoke-CdevCli.ps1 b/scripts/Invoke-CdevCli.ps1 index 5026107..017075f 100644 --- a/scripts/Invoke-CdevCli.ps1 +++ b/scripts/Invoke-CdevCli.ps1 @@ -83,8 +83,16 @@ function Show-CdevHelp { Assert-CdevCommand -Name 'pwsh' $cliRepoRoot = Get-CdevRepoRoot -ScriptPath $PSCommandPath -$resolvedSurfaceRoot = Resolve-CdevSurfaceRoot -SurfaceRoot $SurfaceRoot -$argsMap = Convert-CdevArgsToMap -Args $CommandArgs +$argsMap = Convert-CdevArgsToMap -InputArgs $CommandArgs +$script:resolvedSurfaceRoot = $null + +function Get-CdevResolvedSurfaceRoot { + if ([string]::IsNullOrWhiteSpace([string]$script:resolvedSurfaceRoot)) { + $script:resolvedSurfaceRoot = Resolve-CdevSurfaceRoot -SurfaceRoot $SurfaceRoot + } + + return [string]$script:resolvedSurfaceRoot +} $group = if ($CommandArgs.Count -ge 1) { [string]$CommandArgs[0].ToLowerInvariant() } else { 'help' } $command = if ($CommandArgs.Count -ge 2) { [string]$CommandArgs[1].ToLowerInvariant() } else { '' } @@ -104,11 +112,11 @@ try { switch ($command) { 'list' { $manifestPath = if ($argsMap.ContainsKey('manifest-path')) { [string]$argsMap['manifest-path'] } else { '' } - $result = Invoke-CdevReposList -SurfaceRoot $resolvedSurfaceRoot -ManifestPath $manifestPath + $result = Invoke-CdevReposList -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -ManifestPath $manifestPath } 'doctor' { $workspaceRoot = if ($argsMap.ContainsKey('workspace-root')) { [string]$argsMap['workspace-root'] } else { 'C:\dev' } - $result = Invoke-CdevReposDoctor -SurfaceRoot $resolvedSurfaceRoot -WorkspaceRoot $workspaceRoot + $result = Invoke-CdevReposDoctor -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -WorkspaceRoot $workspaceRoot } default { throw "Unsupported repos command '$command'. Use 'repos list' or 'repos doctor'." @@ -119,7 +127,7 @@ try { switch ($command) { 'sync' { $ref = if ($argsMap.ContainsKey('ref')) { [string]$argsMap['ref'] } else { 'origin/main' } - $result = Invoke-CdevSurfaceSync -SurfaceRoot $resolvedSurfaceRoot -Ref $ref + $result = Invoke-CdevSurfaceSync -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -Ref $ref } default { throw "Unsupported surface command '$command'. Use 'surface sync'." @@ -130,10 +138,10 @@ try { switch ($command) { 'build' { $outputRoot = if ($argsMap.ContainsKey('output-root')) { [string]$argsMap['output-root'] } else { '' } - $result = Invoke-CdevInstallerBuild -SurfaceRoot $resolvedSurfaceRoot -OutputRoot $outputRoot + $result = Invoke-CdevInstallerBuild -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -OutputRoot $outputRoot } 'exercise' { - $result = Invoke-CdevInstallerExercise -SurfaceRoot $resolvedSurfaceRoot -PassThroughArgs $passThroughArgs + $result = Invoke-CdevInstallerExercise -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -PassThroughArgs $passThroughArgs } 'install' { if (-not $argsMap.ContainsKey('installer-path')) { @@ -161,7 +169,7 @@ try { 'linux' { switch ($command) { 'install' { - $result = Invoke-CdevLinuxInstall -CliRepoRoot $cliRepoRoot -SurfaceRoot $resolvedSurfaceRoot -PassThroughArgs $passThroughArgs + $result = Invoke-CdevLinuxInstall -CliRepoRoot $cliRepoRoot -SurfaceRoot (Get-CdevResolvedSurfaceRoot) -PassThroughArgs $passThroughArgs } 'deploy-ni' { $result = Invoke-CdevLinuxDeployNi -CliRepoRoot $cliRepoRoot -PassThroughArgs $passThroughArgs diff --git a/scripts/lib/Common.ps1 b/scripts/lib/Common.ps1 index 244db36..828bba5 100644 --- a/scripts/lib/Common.ps1 +++ b/scripts/lib/Common.ps1 @@ -39,16 +39,16 @@ function Assert-CdevCommand { } function Convert-CdevArgsToMap { - param([string[]]$Args) + param([string[]]$InputArgs) $map = @{} - if ($null -eq $Args) { + if ($null -eq $InputArgs) { return $map } $i = 0 - while ($i -lt $Args.Count) { - $token = [string]$Args[$i] + while ($i -lt $InputArgs.Count) { + $token = [string]$InputArgs[$i] if (-not $token.StartsWith('--')) { $i++ continue @@ -56,8 +56,8 @@ function Convert-CdevArgsToMap { $key = $token.Substring(2) $value = $true - if (($i + 1) -lt $Args.Count -and -not [string]$Args[$i + 1] -like '--*') { - $value = [string]$Args[$i + 1] + if (($i + 1) -lt $InputArgs.Count -and -not ([string]$InputArgs[$i + 1]).StartsWith('--')) { + $value = [string]$InputArgs[$i + 1] $i += 2 } else { $i += 1 diff --git a/scripts/lib/Release.Commands.ps1 b/scripts/lib/Release.Commands.ps1 index ef13410..1112d98 100644 --- a/scripts/lib/Release.Commands.ps1 +++ b/scripts/lib/Release.Commands.ps1 @@ -50,7 +50,21 @@ function Invoke-CdevReleasePackage { $zipPath = Join-Path $resolvedOutput 'cdev-cli-win-x64.zip' if (Test-Path -LiteralPath $zipPath -PathType Leaf) { Remove-Item -LiteralPath $zipPath -Force } - Compress-Archive -Path (Join-Path $stagingRoot 'win-x64\*') -DestinationPath $zipPath -CompressionLevel Optimal + + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue + $winArchiveSource = Join-Path $stagingRoot 'win-x64' + if (-not (Test-Path -LiteralPath $winArchiveSource -PathType Container)) { + throw "Windows staging path not found: $winArchiveSource" + } + [System.IO.Compression.ZipFile]::CreateFromDirectory( + $winArchiveSource, + $zipPath, + [System.IO.Compression.CompressionLevel]::Optimal, + $false + ) + if (-not (Test-Path -LiteralPath $zipPath -PathType Leaf)) { + throw "Failed to create zip package at $zipPath" + } $tarPath = Join-Path $resolvedOutput 'cdev-cli-linux-x64.tar.gz' if (Test-Path -LiteralPath $tarPath -PathType Leaf) { Remove-Item -LiteralPath $tarPath -Force } @@ -58,6 +72,9 @@ function Invoke-CdevReleasePackage { if ($LASTEXITCODE -ne 0) { throw "Failed to create tar.gz package at $tarPath" } + if (-not (Test-Path -LiteralPath $tarPath -PathType Leaf)) { + throw "Failed to create tar.gz package at $tarPath" + } $zipShaFile = "$zipPath.sha256" $tarShaFile = "$tarPath.sha256" diff --git a/tests/CdevCliContract.Tests.ps1 b/tests/CdevCliContract.Tests.ps1 index 8210d00..cd24671 100644 --- a/tests/CdevCliContract.Tests.ps1 +++ b/tests/CdevCliContract.Tests.ps1 @@ -49,6 +49,47 @@ Describe 'cdev CLI command contract' { $script:readme | Should -Match ([regex]::Escape('linux deploy-ni')) } + It 'runs help command without requiring a surface root path' { + $tempRoot = if ([string]::IsNullOrWhiteSpace($env:TEMP)) { [System.IO.Path]::GetTempPath() } else { $env:TEMP } + $reportPath = Join-Path $tempRoot ("cdev-cli-help-" + [Guid]::NewGuid().ToString('N') + '.json') + $previousSurfaceRoot = $env:CDEV_SURFACE_ROOT + + try { + Remove-Item Env:CDEV_SURFACE_ROOT -ErrorAction SilentlyContinue + & pwsh -NoProfile -File $script:entrypoint -ReportPath $reportPath help + $LASTEXITCODE | Should -Be 0 + (Test-Path -LiteralPath $reportPath -PathType Leaf) | Should -BeTrue + } finally { + if ($null -eq $previousSurfaceRoot) { + Remove-Item Env:CDEV_SURFACE_ROOT -ErrorAction SilentlyContinue + } else { + $env:CDEV_SURFACE_ROOT = $previousSurfaceRoot + } + Remove-Item -LiteralPath $reportPath -Force -ErrorAction SilentlyContinue + } + } + + It 'honors --output-root for release package command' { + $tempRoot = if ([string]::IsNullOrWhiteSpace($env:TEMP)) { [System.IO.Path]::GetTempPath() } else { $env:TEMP } + $outputRoot = Join-Path $tempRoot ("cdev-cli-release-" + [Guid]::NewGuid().ToString('N')) + $reportPath = Join-Path $tempRoot ("cdev-cli-release-report-" + [Guid]::NewGuid().ToString('N') + '.json') + $resolvedOutputRoot = [System.IO.Path]::GetFullPath($outputRoot) + + try { + & pwsh -NoProfile -File $script:entrypoint -ReportPath $reportPath release package --output-root $outputRoot + $LASTEXITCODE | Should -Be 0 + + $report = Get-Content -LiteralPath $reportPath -Raw | ConvertFrom-Json -ErrorAction Stop + [string]$report.status | Should -Be 'succeeded' + [string]$report.data.output_root | Should -Be $resolvedOutputRoot + (Test-Path -LiteralPath (Join-Path $resolvedOutputRoot 'cdev-cli-win-x64.zip') -PathType Leaf) | Should -BeTrue + (Test-Path -LiteralPath (Join-Path $resolvedOutputRoot 'cdev-cli-linux-x64.tar.gz') -PathType Leaf) | Should -BeTrue + } finally { + Remove-Item -LiteralPath $outputRoot -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $reportPath -Force -ErrorAction SilentlyContinue + } + } + It 'has parse-safe PowerShell syntax' { $tokens = $null $errors = $null