Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This repository is the control-plane CLI for deterministic `C:\dev` workspace or

## CLI Orchestration Contract
- CLI entrypoint: `scripts/Invoke-CdevCli.ps1`.
- Windows invocation contract: `powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 ...`.
- Linux invocation contract: `pwsh -NoProfile -File ./scripts/Invoke-CdevCli.ps1 ...`.
- CLI is the preferred operator interface for:
- repo topology inspection (`repos list`)
- governance checks (`repos doctor`)
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ Control-plane CLI for deterministic `C:\dev` workspace operations.
## Entrypoint

```powershell
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 <group> <command> [options]
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 <group> <command> [options]
```

On Linux, invoke the same entrypoint with `pwsh -NoProfile -File`.

## Commands

- `help [topic]`
Expand All @@ -26,23 +28,23 @@ pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 <group> <command> [options]
## Quick Start

```powershell
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 repos list
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 repos doctor --workspace-root C:\dev
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 installer exercise --mode fast --iterations 1
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 ci integration-gate --repo svelderrainruiz/labview-cdev-surface --branch main
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 repos list
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 repos doctor --workspace-root C:\dev
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 installer exercise --mode fast --iterations 1
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 ci integration-gate --repo svelderrainruiz/labview-cdev-surface --branch main
```

## Linux Flow (Docker Desktop Linux)

```powershell
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 linux install --workspace-root C:\dev-linux
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 linux deploy-ni --workspace-root C:\dev-linux --docker-context desktop-linux --image nationalinstruments/labview:latest-linux
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 linux install --workspace-root C:\dev-linux
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 linux deploy-ni --workspace-root C:\dev-linux --docker-context desktop-linux --image nationalinstruments/labview:latest-linux
```

## Release Packaging

```powershell
pwsh -NoProfile -File .\scripts\Invoke-CdevCli.ps1 release package --output-root .\artifacts\release\cli
powershell -NoProfile -ExecutionPolicy RemoteSigned -File .\scripts\Invoke-CdevCli.ps1 release package --output-root .\artifacts\release\cli
```

Release artifacts:
Expand Down
31 changes: 21 additions & 10 deletions scripts/Invoke-CdevCli.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#Requires -Version 7.0
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
Expand All @@ -8,12 +7,24 @@ param(
[string]$SurfaceRoot,

[Parameter()]
[string]$ReportPath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'artifacts\cli\cdev-cli-last-run.json')
[string]$ReportPath = ''
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

if ([string]::IsNullOrWhiteSpace([string]$ReportPath)) {
$scriptDirectory = if (-not [string]::IsNullOrWhiteSpace([string]$PSScriptRoot)) {
$PSScriptRoot
} elseif (-not [string]::IsNullOrWhiteSpace([string]$PSCommandPath)) {
Split-Path -Parent $PSCommandPath
} else {
(Get-Location).Path
}

$ReportPath = Join-Path (Split-Path -Parent $scriptDirectory) 'artifacts\cli\cdev-cli-last-run.json'
}

$libRoot = Join-Path $PSScriptRoot 'lib'
foreach ($libFile in @(
'Common.ps1',
Expand All @@ -37,7 +48,7 @@ function Show-CdevHelp {
'cdev control-plane CLI',
'',
'Usage:',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 <group> <command> [options]',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 <group> <command> [options]',
'',
'Groups and commands:',
' help [topic]',
Expand All @@ -54,12 +65,12 @@ function Show-CdevHelp {
' release package',
'',
'Examples:',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 repos list',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 repos doctor --workspace-root C:\dev',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 installer exercise --mode fast --iterations 1',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 postactions collect --report-path C:\dev\artifacts\workspace-install-latest.json',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 linux install --workspace-root C:\dev-linux',
' pwsh -NoProfile -File scripts/Invoke-CdevCli.ps1 linux deploy-ni --workspace-root C:\dev-linux --docker-context desktop-linux'
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 repos list',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 repos doctor --workspace-root C:\dev',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 installer exercise --mode fast --iterations 1',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 postactions collect --report-path C:\dev\artifacts\workspace-install-latest.json',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 linux install --workspace-root C:\dev-linux',
' powershell -NoProfile -ExecutionPolicy RemoteSigned -File scripts/Invoke-CdevCli.ps1 linux deploy-ni --workspace-root C:\dev-linux --docker-context desktop-linux'
)

if ([string]::IsNullOrWhiteSpace($Topic)) {
Expand All @@ -81,7 +92,7 @@ function Show-CdevHelp {
}
}

Assert-CdevCommand -Name 'pwsh'
Resolve-CdevPowerShellHost | Out-Null
$cliRepoRoot = Get-CdevRepoRoot -ScriptPath $PSCommandPath
$argsMap = Convert-CdevArgsToMap -InputArgs $CommandArgs
$script:resolvedSurfaceRoot = $null
Expand Down
33 changes: 30 additions & 3 deletions scripts/lib/Common.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
Set-StrictMode -Version Latest

function Resolve-CdevPowerShellHost {
[CmdletBinding()]
param()

$isWindowsHost = [string]::Equals($env:OS, 'Windows_NT', [System.StringComparison]::OrdinalIgnoreCase)
$candidates = if ($isWindowsHost) {
@('powershell.exe', 'powershell', 'pwsh.exe', 'pwsh')
} else {
@('pwsh', 'pwsh.exe', 'powershell', 'powershell.exe')
}

foreach ($candidate in $candidates) {
$command = Get-Command -Name $candidate -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
if ($null -ne $command -and -not [string]::IsNullOrWhiteSpace($command.Source)) {
return $command.Source
}
}

throw "Unable to resolve a PowerShell host executable (tried: $($candidates -join ', '))."
}

function Get-CdevRepoRoot {
param([Parameter(Mandatory = $true)][string]$ScriptPath)
return (Resolve-Path -Path (Join-Path (Split-Path -Parent $ScriptPath) '..')).Path
Expand Down Expand Up @@ -78,16 +99,22 @@ function Invoke-CdevPwshScript {
throw "Script not found: $ScriptPath"
}

$argList = @('-NoProfile', '-File', $ScriptPath)
$powerShellHost = Resolve-CdevPowerShellHost
$argList = @('-NoProfile')
if ([string]::Equals($env:OS, 'Windows_NT', [System.StringComparison]::OrdinalIgnoreCase)) {
$argList += @('-ExecutionPolicy', 'RemoteSigned')
}
$argList += @('-File', $ScriptPath)
if ($null -ne $Arguments -and $Arguments.Count -gt 0) {
$argList += $Arguments
}

& pwsh @argList
$exitCode = $LASTEXITCODE
& $powerShellHost @argList
$exitCode = if ($LASTEXITCODE -ne $null) { [int]$LASTEXITCODE } else { 0 }

return [pscustomobject]@{
script = $ScriptPath
host = $powerShellHost
arguments = @($Arguments)
exit_code = $exitCode
status = if ($exitCode -eq 0) { 'succeeded' } else { 'failed' }
Expand Down
30 changes: 27 additions & 3 deletions tests/CdevCliContract.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
#Requires -Version 7.0
#Requires -Modules Pester

$ErrorActionPreference = 'Stop'

Describe 'cdev CLI command contract' {
BeforeAll {
$isWindowsHost = [string]::Equals($env:OS, 'Windows_NT', [System.StringComparison]::OrdinalIgnoreCase)
$candidateHosts = if ($isWindowsHost) {
@('powershell.exe', 'powershell', 'pwsh.exe', 'pwsh')
} else {
@('pwsh', 'pwsh.exe', 'powershell', 'powershell.exe')
}
$script:powerShellHost = $null
foreach ($candidate in $candidateHosts) {
$cmd = Get-Command -Name $candidate -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
if ($null -ne $cmd -and -not [string]::IsNullOrWhiteSpace($cmd.Source)) {
$script:powerShellHost = $cmd.Source
break
}
}
if ([string]::IsNullOrWhiteSpace([string]$script:powerShellHost)) {
throw "Unable to resolve a PowerShell host executable (tried: $($candidateHosts -join ', '))."
}

$script:powerShellInvocationArgs = @('-NoProfile')
if ($isWindowsHost) {
$script:powerShellInvocationArgs += @('-ExecutionPolicy', 'RemoteSigned')
}

$script:repoRoot = (Resolve-Path -Path (Join-Path $PSScriptRoot '..')).Path
$script:entrypoint = Join-Path $script:repoRoot 'scripts/Invoke-CdevCli.ps1'
$script:contractPath = Join-Path $script:repoRoot 'cli-contract.json'
Expand Down Expand Up @@ -56,7 +78,8 @@ Describe 'cdev CLI command contract' {

try {
Remove-Item Env:CDEV_SURFACE_ROOT -ErrorAction SilentlyContinue
& pwsh -NoProfile -File $script:entrypoint -ReportPath $reportPath help
$invokeArgs = @($script:powerShellInvocationArgs + @('-File', $script:entrypoint, '-ReportPath', $reportPath, 'help'))
& $script:powerShellHost @invokeArgs
$LASTEXITCODE | Should -Be 0
(Test-Path -LiteralPath $reportPath -PathType Leaf) | Should -BeTrue
} finally {
Expand All @@ -76,7 +99,8 @@ Describe 'cdev CLI command contract' {
$resolvedOutputRoot = [System.IO.Path]::GetFullPath($outputRoot)

try {
& pwsh -NoProfile -File $script:entrypoint -ReportPath $reportPath release package --output-root $outputRoot
$invokeArgs = @($script:powerShellInvocationArgs + @('-File', $script:entrypoint, '-ReportPath', $reportPath, 'release', 'package', '--output-root', $outputRoot))
& $script:powerShellHost @invokeArgs
$LASTEXITCODE | Should -Be 0

$report = Get-Content -LiteralPath $reportPath -Raw | ConvertFrom-Json -ErrorAction Stop
Expand Down