Skip to content
Open
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
85 changes: 47 additions & 38 deletions scripts/install-global.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,39 @@ try {
} catch { Write-DotbotCommand "Parse skipped: $_" }
$env:DOTBOT_VERSION = $DotbotVersion

# Parses raw CLI tokens into a named-parameter hashtable.
# $PositionalNames maps positional index to param name (e.g. @('Name','Source')).
function ConvertTo-SplatArg {
param(
[string[]]$Tokens,
[string[]]$PositionalNames = @()
)
$splat = @{}
$positional = @()
$i = 0
while ($i -lt $Tokens.Count) {
if ($Tokens[$i] -match '^--?(.+)$') {
$pname = $Matches[1]
if (($i + 1) -lt $Tokens.Count -and $Tokens[$i+1] -notmatch '^--?') {
$splat[$pname] = $Tokens[$i+1]; $i += 2
} else {
$splat[$pname] = $true; $i++
}
} else {
$positional += $Tokens[$i]; $i++
}
}
for ($j = 0; $j -lt [Math]::Min($positional.Count, $PositionalNames.Count); $j++) {
$splat[$PositionalNames[$j]] = $positional[$j]
}
if ($positional.Count -gt $PositionalNames.Count) {
$unexpected = $positional[$PositionalNames.Count..($positional.Count - 1)] -join ', '
Write-DotbotError "Unexpected argument(s): $unexpected"
exit 1
}
return $splat
Comment thread
IBondarenko-iwg marked this conversation as resolved.
}

function Show-Help {
Write-DotbotBanner -Title "D O T B O T v$DotbotVersion" -Subtitle "Autonomous Development System"
Write-DotbotSection "COMMANDS"
Expand Down Expand Up @@ -293,17 +326,18 @@ function Invoke-Update {

function Invoke-Workflow {
$wfSubCmd = if ($SubArgs.Count -gt 0) { $SubArgs[0] } else { 'list' }
$wfName = if ($SubArgs.Count -gt 1) { $SubArgs[1] } else { '' }
[string[]]$wfExtra = @()
if ($SubArgs.Count -gt 2) { $wfExtra = @($SubArgs[2..($SubArgs.Count-1)]) }
$wfRest = if ($SubArgs.Count -gt 1) { @($SubArgs[1..($SubArgs.Count-1)]) } else { @() }

$wfScript = switch ($wfSubCmd) {
'add' { Join-Path $ScriptsDir 'workflow-add.ps1' }
'remove' { Join-Path $ScriptsDir 'workflow-remove.ps1' }
'list' { Join-Path $ScriptsDir 'workflow-list.ps1' }
default { $null }
}

if ($wfScript -and (Test-Path $wfScript)) {
if ($wfExtra.Count -gt 0) { & $wfScript $wfName @wfExtra } else { & $wfScript $wfName }
$wfSplat = ConvertTo-SplatArg -Tokens $wfRest -PositionalNames @('Name')
& $wfScript @wfSplat
} else {
Write-DotbotWarning "Usage: dotbot workflow [add|remove|list] [name] [--Force]"
}
Expand All @@ -323,36 +357,13 @@ function Invoke-Registry {
}

if ($regScript -and (Test-Path $regScript)) {
# Separate positional args from named flags
$regSplat = @{}
$positional = @()
$ri = 0
while ($ri -lt $regRest.Count) {
if ($regRest[$ri] -match '^--?(.+)$') {
$pname = $Matches[1]
if (($ri + 1) -lt $regRest.Count -and $regRest[$ri + 1] -notmatch '^--?') {
$regSplat[$pname] = $regRest[$ri + 1]
$ri += 2
} else {
$regSplat[$pname] = $true
$ri++
}
} else {
$positional += $regRest[$ri]
$ri++
}
}

# Map positional args to named parameters
if ($regSubCmd -eq 'add') {
if ($positional.Count -ge 1) { $regSplat['Name'] = $positional[0] }
if ($positional.Count -ge 2) { $regSplat['Source'] = $positional[1] }
} elseif ($regSubCmd -eq 'remove') {
if ($positional.Count -ge 1) { $regSplat['Name'] = $positional[0] }
} elseif ($regSubCmd -eq 'update') {
if ($positional.Count -ge 1) { $regSplat['Name'] = $positional[0] }
$regPositional = switch ($regSubCmd) {
'add' { @('Name', 'Source') }
'remove' { @('Name') }
'update' { @('Name') }
default { @() }
}

$regSplat = ConvertTo-SplatArg -Tokens $regRest -PositionalNames $regPositional
& $regScript @regSplat
} else {
Write-DotbotWarning "Usage: dotbot registry [add|list|update|remove] ..."
Expand All @@ -364,12 +375,10 @@ function Invoke-Registry {
}

function Invoke-Run {
$wfName = if ($SplatArgs.Count -gt 0) { $SplatArgs.Values | Select-Object -First 1 } else { '' }
# Get workflow name from positional args
$raw = if ($args.Count -gt 1) { $args[1] } else { $wfName }
$wfName = if ($SubArgs.Count -gt 0) { $SubArgs[0] } else { '' }
$runScript = Join-Path $ScriptsDir 'workflow-run.ps1'
if ($raw -and (Test-Path $runScript)) {
& $runScript -WorkflowName $raw
if ($wfName -and (Test-Path $runScript)) {
& $runScript -WorkflowName $wfName
} else {
Write-DotbotWarning "Usage: dotbot run <workflow-name>"
}
Expand Down
34 changes: 34 additions & 0 deletions tests/Test-WorkflowIntegration.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,13 @@ if ((Test-Path $cliScript) -and (Test-Path $startFromPromptWf)) {
$installedDir = Join-Path $testProjectCli ".bot\workflows\start-from-prompt"
Assert-PathExists -Name "CLI 'workflow add' installs workflow directory" -Path $installedDir

# Test: workflow add --Force via CLI dispatcher (regression: array splatting dropped switches)
$forceOutput = & pwsh -NoProfile -ExecutionPolicy Bypass -Command "Set-Location '$testProjectCli'; & '$cliScript' workflow add start-from-prompt --Force" 2>&1
$forceFailed = $forceOutput | Where-Object { $_ -match 'positional parameter cannot be found' -or $_ -match 'cannot be found that accepts argument' }
Assert-True -Name "CLI 'workflow add --Force' dispatches without splatting error" `
-Condition ($null -eq $forceFailed -or $forceFailed.Count -eq 0) `
-Message "Switch --Force not bound via CLI dispatcher: $forceFailed"

# Test: workflow remove also dispatches cleanly
$removeOutput = & pwsh -NoProfile -ExecutionPolicy Bypass -Command "Set-Location '$testProjectCli'; & '$cliScript' workflow remove start-from-prompt" 2>&1
$removeFailed = $removeOutput | Where-Object { $_ -match 'positional parameter cannot be found' -or $_ -match 'cannot be found that accepts argument' }
Expand All @@ -548,6 +555,33 @@ if ((Test-Path $cliScript) -and (Test-Path $startFromPromptWf)) {

Write-Host ""

# ═══════════════════════════════════════════════════════════════════
# CLI REGISTRY DISPATCH
# ═══════════════════════════════════════════════════════════════════

Write-Host " CLI REGISTRY DISPATCH" -ForegroundColor Cyan
Write-Host " ────────────────────────────────────────────" -ForegroundColor DarkGray

# Regression: Invoke-Registry used inline parse loop; ConvertTo-SplatArgs helper must bind
# named flags (--branch, --force) without "positional parameter cannot be found" error.
if (Test-Path $cliScript) {
$regCliProj = New-TestProjectFromGolden -Flavor 'default'
try {
# registry add with named flags via CLI dispatcher — must not throw splatting error
$regAddOutput = & pwsh -NoProfile -ExecutionPolicy Bypass -Command "& '$cliScript' registry add TestReg not-a-url --branch main --force" 2>&1
$regAddFailed = $regAddOutput | Where-Object { $_ -match 'positional parameter cannot be found' -or $_ -match 'cannot be found that accepts argument' }
Assert-True -Name "CLI 'registry add --branch --force' dispatches without splatting error" `
-Condition ($null -eq $regAddFailed -or $regAddFailed.Count -eq 0) `
-Message "Named flags not bound via CLI dispatcher: $regAddFailed"
Comment thread
IBondarenko-iwg marked this conversation as resolved.
} finally {
Remove-TestProject -Path $regCliProj.ProjectRoot
}
} else {
Write-TestResult -Name "CLI registry dispatch tests" -Status Skip -Message "dotbot CLI not found"
}

Write-Host ""

# ═══════════════════════════════════════════════════════════════════
# WORKFLOW ADD FUNCTIONALITY
# ═══════════════════════════════════════════════════════════════════
Expand Down
Loading