PowerStub.psm1
|
$Script:ModulePath = $PSScriptRoot Write-Verbose "Initializing PowerStub" #enable verbose messaging in the psm1 file if ($MyInvocation.line -match '-verbose') { $VerbosePreference = 'continue' } #Get all files with functions in them Write-Verbose 'Finding functions' $privateFn = Get-ChildItem -Path $PSScriptRoot\Private\functions\*.ps1; $publicFn = Get-ChildItem -Path $PSScriptRoot\Public\functions\*.ps1; #If we are in PowerShell core, load any core specific functions if ($IsCoreCLR) { $pscorePath = Join-Path $PSScriptRoot 'Public\functions-pscore' if (Test-Path $pscorePath) { Write-Verbose 'PowerShell 7 specific commands enabled' $publicFn += Get-ChildItem -Path "$pscorePath\*.ps1" } } # Load all functions using 'dot' import Write-Verbose 'Dot-sourcing functions' ($publicFn + $privateFn) | ForEach-Object -Process { Write-Verbose $_.FullName; . $_.FullName } #load the configuration $Script:PSTBSettings = Get-PowerStubConfigurationDefaults Import-PowerStubConfiguration # Check for Git availability and set module-scoped flags Write-Verbose "Checking Git availability" $Script:GitAvailable = $null -ne (Get-Command git -ErrorAction SilentlyContinue) if ($Script:GitAvailable) { Write-Verbose "Git is available" Set-PowerStubConfigurationKey 'GitAvailable' $true # GitEnabled defaults to true if available, but can be overridden by config $configEnabled = Get-PowerStubConfigurationKey 'GitEnabled' $Script:GitEnabled = if ($null -eq $configEnabled) { $true } else { $configEnabled } } else { Write-Verbose "Git is not available" Set-PowerStubConfigurationKey 'GitAvailable' $false $Script:GitEnabled = $false } Write-Verbose "Git enabled: $Script:GitEnabled" #export public functions only [string[]]$exports = @($publicFn | Select-Object -ExpandProperty BaseName) Write-Verbose "Exporting $($exports.Count) functions" Export-ModuleMember -Function $exports # setup and export the main alias $alias = Get-PowerStubConfigurationKey 'InvokeAlias' Write-Verbose "Creating Invoke-PowerStubCommand alias as: $alias" New-Alias $alias Invoke-PowerStubCommand Export-ModuleMember -Alias $alias #setup the Invoke-PowerStubCommand argument completer for the STUB parameter Write-Verbose "Setting up argument completer for Invoke-PowerStubCommand STUB parameter" $StubCompleter = { param($commandName, $parameterName, $stringMatch, $commandAst, $fakeBoundParameters) # Virtual verbs (reserved commands) $virtualVerbs = @('search', 'help', 'update') $stubs = Get-PowerStubConfigurationKey 'Stubs' $allOptions = @($virtualVerbs) + @($stubs.Keys) if (!$stringMatch) { return $allOptions } $PartialMatches = @($allOptions | Where-Object { $_ -like "$stringMatch*" }) return $PartialMatches } # Register for both the function and the alias Register-ArgumentCompleter -CommandName Invoke-PowerStubCommand -ParameterName Stub -ScriptBlock $StubCompleter Register-ArgumentCompleter -CommandName $alias -ParameterName Stub -ScriptBlock $StubCompleter #setup the Invoke-PowerStubCommand argument completer for the COMMAND parameter Write-Verbose "Setting up argument completer for Invoke-PowerStubCommand COMMAND parameter" $CommandCompleter = { param($commandName, $parameterName, $stringMatch, $commandAst, $fakeBoundParameters) $stub = $fakeBoundParameters['Stub'] if (!$stub) { return @() } # Handle virtual verb completions if ($stub -eq 'help') { # For 'help' verb, the command parameter should show stub names $stubs = Get-PowerStubConfigurationKey 'Stubs' $stubNames = @($stubs.Keys) if (!$stringMatch) { return $stubNames } return @($stubNames | Where-Object { $_ -like "$stringMatch*" }) } if ($stub -eq 'search') { # For 'search' verb, no completion (user types query) return @() } if ($stub -eq 'update') { # For 'update' verb, the command parameter should show stub names (or empty for all) $stubs = Get-PowerStubConfigurationKey 'Stubs' $stubNames = @($stubs.Keys) if (!$stringMatch) { return $stubNames } return @($stubNames | Where-Object { $_ -like "$stringMatch*" }) } $commands = @(Find-PowerStubCommands $stub) if (!$commands) { return @() } # Get base names and strip alpha./beta. prefixes for user-friendly completion $commandNames = @($commands | ForEach-Object { $name = $_.BaseName # Strip alpha. or beta. prefix if present if ($name -match '^(alpha|beta)\.(.+)$') { $Matches[2] } else { $name } } | Select-Object -Unique) if (!$stringMatch) { return $commandNames } $PartialMatches = $commandNames | Where-Object { $_ -like "$stringMatch*" } return $PartialMatches } # Register for both the function and the alias Register-ArgumentCompleter -CommandName Invoke-PowerStubCommand -ParameterName Command -ScriptBlock $CommandCompleter Register-ArgumentCompleter -CommandName $alias -ParameterName Command -ScriptBlock $CommandCompleter # Ensure PSReadLine Tab completion is properly configured # PSReadLine is required for interactive tab completion in modern PowerShell terminals Write-Verbose "Checking PSReadLine Tab completion setup" $psrlModule = Get-Module PSReadLine if ($psrlModule) { # PSReadLine is loaded - check if Tab is bound to a completion function $tabHandler = Get-PSReadLineKeyHandler -Bound -ErrorAction SilentlyContinue | Where-Object { $_.Key -eq 'Tab' } if (-not $tabHandler) { Write-Verbose "Tab key not bound - setting up Tab completion" try { Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete -ErrorAction Stop Write-Verbose "Tab key bound to MenuComplete" } catch { Write-Warning "PowerStub: Could not configure Tab completion. You may need to add 'Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete' to your profile." } } else { Write-Verbose "Tab key already bound to: $($tabHandler.Function)" } } else { # PSReadLine not loaded - this is unusual in interactive sessions # Don't warn here as this might be a non-interactive context (tests, scripts, etc.) Write-Verbose "PSReadLine not loaded - Tab completion may not work interactively" } # NOTE: We intentionally do NOT override TabExpansion2 as it can break tab completion # for all commands in some environments. The core completion functionality (stub names, # command names, and dynamic parameters) works via Register-ArgumentCompleter which # doesn't require TabExpansion2. # # Trade-off: When using positional syntax like "pstb DevOps deploy -<Tab>", the # completions will still include -stub and -command even though they're already bound. # This is a minor UX issue that's preferable to potentially breaking all tab completion. # Re-register any saved direct aliases Write-Verbose "Registering saved direct aliases" $directAliases = Get-PowerStubConfigurationKey 'DirectAliases' if ($directAliases) { # Copy keys to array to avoid "Collection was modified" error during enumeration $aliasNames = @($directAliases.Keys) foreach ($aliasName in $aliasNames) { $stubName = $directAliases[$aliasName] # Only re-register if the stub still exists $stubs = Get-PowerStubConfigurationKey 'Stubs' if ($stubs.Keys -contains $stubName) { try { New-PowerStubDirectAlias -AliasName $aliasName -Stub $stubName -Force -ErrorAction Stop | Out-Null Write-Verbose "Registered direct alias '$aliasName' for stub '$stubName'" } catch { Write-Warning "Could not register direct alias '$aliasName': $_" } } else { Write-Verbose "Skipping alias '$aliasName' - stub '$stubName' no longer exists" } } } # Check Git status for stubs with configured repos if ($Script:GitEnabled) { Write-Verbose "Checking Git status for stubs" $stubs = Get-PowerStubConfigurationKey 'Stubs' # Copy keys to array to avoid "Collection was modified" error during enumeration $stubNames = @($stubs.Keys) foreach ($stubName in $stubNames) { $stubConfig = $stubs[$stubName] # Check if stub config is a hashtable with GitRepoUrl if ($stubConfig -is [hashtable] -and $stubConfig.GitRepoUrl) { $stubPath = $stubConfig.Path $gitInfo = Get-PowerStubGitInfo -Path $stubPath if ($gitInfo.IsRepo -and $gitInfo.BehindCount -gt 0) { Write-Host "Stub '$stubName' is $($gitInfo.BehindCount) commit(s) behind the remote repo. Run 'pstb update $stubName' to update." -ForegroundColor Yellow } } # Also check if it's just a path string and in a git repo elseif ($stubConfig -is [string]) { $gitInfo = Get-PowerStubGitInfo -Path $stubConfig if ($gitInfo.IsRepo -and $gitInfo.RemoteUrl -and $gitInfo.BehindCount -gt 0) { Write-Host "Stub '$stubName' is $($gitInfo.BehindCount) commit(s) behind the remote repo. Run 'pstb update $stubName' to update." -ForegroundColor Yellow } } } } Write-Verbose "PowerStub module loaded." |