src/host/qbuild.ps1
|
function Invoke-QBuild { [CmdletBinding()] param( [ArgumentCompleter({ param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) try { $mapPath = if ($fakeBoundParameters.map) { $fakeBoundParameters.map } else { "./.build.map.ps1" } $localMapExists = Test-Path $mapPath $userMap = try { Import-ConfigMap -Map $fakeBoundParameters.map -Fallback "./.build.map.ps1" -ErrorAction Stop } catch { if ($_.Exception.Message -match '^map file .* not found$|^No map provided and fallback .* not found$|^map is null and defaultMapFile is not provided$') { $null } else { throw } } if ($userMap) { $userMap = $userMap | Assert-ConfigMap } $completions = [System.Collections.Generic.List[string]]::new() if ($userMap) { foreach ($c in @(Get-EntryCompletion $userMap -language "build" @PSBoundParameters)) { if ($c) { $completions.Add([string]$c) | Out-Null } } } $configMapModule = (Get-Command Invoke-QBuild).Module $internalCommands = & $configMapModule { Get-ConfigMapInternalCommands -Language build } foreach ($item in $internalCommands.GetEnumerator()) { if (-not ([string]$item.Key).StartsWith($wordToComplete)) { continue } $includeCompletion = & $configMapModule { param($commandEntry, $mapExists) Test-ConfigMapCommandCompletion -Entry $commandEntry -LocalMapExists $mapExists } $item.Value $localMapExists if (-not $includeCompletion) { continue } if (-not $completions.Contains([string]$item.Key)) { $completions.Add([string]$item.Key) | Out-Null } } try { $resolvedMapPath = Resolve-MapPath -Map $userMap if ($resolvedMapPath) { foreach ($procName in @(Get-ConfigMapProcessNamesInMap -Map $userMap)) { $procWords = Get-ConfigMapProcessCompleteWord ` -ProcessName $procName ` -MapPath $resolvedMapPath ` -Word $wordToComplete if ($null -eq $procWords) { continue } foreach ($w in @($procWords)) { if ($w -and -not $completions.Contains([string]$w)) { $completions.Add([string]$w) | Out-Null } } } } } catch { # No file path / process discovery failure → keep map-only completions } return @($completions) } catch { return "ERROR [-entry]: $($_.Exception.Message) $($_.ScriptStackTrace)" } })] [Parameter(Position = 0)] $entry = $null, [Parameter(DontShow)] $command = "exec", [Parameter(DontShow)] $map = "./.build.map.ps1", # Not positional. With dynamic parameters present, PowerShell binds these # slots out of order and treats the unbound strings as empty arguments. [Parameter(DontShow)] [ArgumentCompleter({ param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) try { $module = Get-Module ConfigMap | Select-Object -First 1 if (-not $module) { return @() } return & $module { param($Ast, $Bound, $Word) Get-QBuildProcessArgumentCompletion ` -CommandAst $Ast ` -FakeBoundParameters $Bound ` -Word $Word ` -Position Action } $commandAst $fakeBoundParameters $wordToComplete } catch { return @() } })] [string]$ProcessAction, [Parameter(DontShow)] [ArgumentCompleter({ param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) try { $module = Get-Module ConfigMap | Select-Object -First 1 if (-not $module) { return @() } return & $module { param($Ast, $Bound, $Word) Get-QBuildProcessArgumentCompletion ` -CommandAst $Ast ` -FakeBoundParameters $Bound ` -Word $Word ` -Position Value } $commandAst $fakeBoundParameters $wordToComplete } catch { return @() } })] [string]$ProcessValue, [Parameter(ValueFromRemainingArguments = $true, DontShow = $true)] [ArgumentCompleter({ param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) try { $module = Get-Module ConfigMap | Select-Object -First 1 if (-not $module) { return @() } return & $module { param($Ast, $Bound, $Word) $position = Get-QBuildRemainingArgumentPosition ` -CommandAst $Ast ` -FakeBoundParameters $Bound ` -Word $Word if (-not $position) { return @() } Get-QBuildProcessArgumentCompletion ` -CommandAst $Ast ` -FakeBoundParameters $Bound ` -Word $Word ` -Position $position } $commandAst $fakeBoundParameters $wordToComplete } catch { return @() } })] [string[]]$RemainingArguments ) dynamicparam { if (-not $entry) { return } try { $dynUserMap = try { . $ImportConfigMap -Map $map -Fallback "./.build.map.ps1" | Assert-ConfigMap } catch { if ($_.Exception.Message -match '^map file .* not found$|^No map provided and fallback .* not found$|^map is null and defaultMapFile is not provided$') { $null } else { throw } } $dynMap = Merge-ConfigMapCommands -Map $dynUserMap -Language build if (Test-ConfigMapInternalCommand -Name $entry -Language build) { return Get-EntryDynamicParam $dynMap $entry $command -skip 0 -bound $PSBoundParameters } if (!$dynUserMap) { return } $settings = Get-ConfigMapSettingsForPath -Map $dynMap -Path $entry $handler = Get-ConfigMapProcessHandler -Name $settings.Process if ($null -ne $handler) { if ($null -ne $handler.GetDynamicParam) { $context = New-ConfigMapProcessContext ` -Map $dynMap ` -UserMap $dynUserMap ` -MapArgument $map ` -Entry $entry ` -Command $command ` -Invocation $MyInvocation ` -BoundParameters $PSBoundParameters ` -ProcessAction $ProcessAction ` -ProcessValue $ProcessValue ` -RemainingArguments $RemainingArguments return & $handler.GetDynamicParam $context } return Get-EntryDynamicParam $dynMap $entry $command -skip 0 -bound $PSBoundParameters } if (Test-ConfigMapProcessCannotDelegate -ProcessName $settings.Process) { return Get-EntryDynamicParam $dynMap $entry $command -skip 0 -bound $PSBoundParameters } $mapPath = Resolve-MapPath -Map $dynMap $followHint = [bool]$PSBoundParameters['Follow'] if (-not $mapPath) { return Get-ProcessDynamicParam -Map $dynMap -ProcessKey $entry -Follow:$followHint } return New-ConfigMapProcessDynamicParam ` -ProcessName $settings.Process ` -MapPath $mapPath ` -Map $dynMap ` -Entry $entry ` -Follow:$followHint ` -ProcessAction $ProcessAction ` -ProcessValue $ProcessValue ` -RemainingArguments $RemainingArguments } catch { return "ERROR [dynamic]: $($_.Exception.Message) $($_.ScriptStackTrace)" } } process { $mapPath = $map $userMap = try { . $ImportConfigMap -Map $map -Fallback "./.build.map.ps1" | Assert-ConfigMap } catch { if ($_.Exception.Message -match '^map file .* not found$|^No map provided and fallback .* not found$|^map is null and defaultMapFile is not provided$') { $null } else { throw } } $map = Merge-ConfigMapCommands -Map $userMap -Language build if (-not $entry) { $entry = 'list' } $isInternalCommand = Test-ConfigMapInternalCommand -Name $entry -Language build if (!$userMap -and !$isInternalCommand) { Write-Help -invocation $MyInvocation -mapPath "./.build.map.ps1" return } $operationContext = New-ConfigMapOperationContext $context = New-ConfigMapProcessContext ` -Map $map ` -UserMap $userMap ` -MapArgument $mapPath ` -Entry $entry ` -Command $command ` -Invocation $MyInvocation ` -OperationContext $operationContext ` -BoundParameters $PSBoundParameters ` -ProcessAction $ProcessAction ` -ProcessValue $ProcessValue ` -RemainingArguments $RemainingArguments if (-not $isInternalCommand) { $processSettings = Get-ConfigMapSettingsForPath -Map $map -Path $entry -OperationContext $operationContext $handler = Get-ConfigMapProcessHandler -Name $processSettings.Process if ($null -ne $handler) { if ($null -eq $handler.Invoke) { throw "Process handler '$($processSettings.Process)' has no Invoke implementation." } & $handler.Invoke $context return } Invoke-ConfigMapExternalProcess ` -ProcessName $processSettings.Process ` -Entry $entry ` -Map $map ` -BoundParameters $PSBoundParameters ` -ProcessAction $ProcessAction ` -ProcessValue $ProcessValue ` -RemainingArguments $RemainingArguments return } Invoke-ConfigMapExecProcess -Context $context } } Set-Alias -Name "qbuild" -Value "Invoke-QBuild" -Force |