public/functions/New-PowerStubDirectAlias.ps1

<#
.SYNOPSIS
    Creates a direct alias for a PowerStub stub.
 
.DESCRIPTION
    Creates a PowerShell function that acts as a shortcut for a specific stub.
    Instead of typing 'pstb DevOps <command>', you can use a shorter alias like 'dv <command>'.
 
    The alias supports full tab completion for both command names and command parameters.
 
.PARAMETER AliasName
    The name of the alias to create (e.g., 'dv' for DevOps).
 
.PARAMETER Stub
    The name of the stub to create an alias for.
 
.PARAMETER Force
    Overwrites the alias if it already exists.
 
.EXAMPLE
    New-PowerStubDirectAlias -AliasName dv -Stub DevOps
 
    Creates an alias 'dv' for the DevOps stub. Now you can run:
        dv deploy -Environment prod
    Instead of:
        pstb DevOps deploy -Environment prod
 
.EXAMPLE
    New-PowerStubDirectAlias -AliasName dv -Stub DevOps -Force
 
    Overwrites an existing 'dv' alias.
 
.OUTPUTS
    PSCustomObject with alias information and usage instructions.
#>


function New-PowerStubDirectAlias {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-zA-Z][a-zA-Z0-9_-]*$')]
        [string]$AliasName,

        [Parameter(Mandatory = $true)]
        [string]$Stub,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )

    # Verify stub exists
    $stubs = Get-PowerStubConfigurationKey 'Stubs'
    if (-not ($stubs.Keys -contains $Stub)) {
        throw "Stub '$Stub' not found. Register it first with New-PowerStub."
    }

    # Check if function already exists
    $existingCmd = Get-Command $AliasName -ErrorAction SilentlyContinue
    if ($existingCmd -and -not $Force) {
        throw "A command named '$AliasName' already exists. Use -Force to overwrite."
    }

    # Create the function in global scope
    $functionBody = @"
function global:$AliasName {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = `$false, Position = 0)]
        [string]`$Command,
 
        [Parameter(DontShow = `$true, ValueFromRemainingArguments = `$true)]
        [object[]]`$RemainingArgs
    )
 
    DynamicParam {
        `$stubName = '$Stub'
        `$commandValue = `$PSBoundParameters['Command']
 
        if (`$commandValue -and (Get-Module PowerStub)) {
            `$module = Get-Module PowerStub
            `$RuntimeParamDic = & `$module { param(`$s, `$c) Get-PowerStubCommandDynamicParams `$s `$c } `$stubName `$commandValue
            return `$RuntimeParamDic
        }
 
        return New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    }
 
    end {
        `$stubName = '$Stub'
 
        if (-not `$Command) {
            # List commands
            `$module = Get-Module PowerStub
            & `$module { param(`$s) Find-PowerStubCommands `$s } `$stubName
            return
        }
 
        # Get the command object
        `$commandObj = Get-PowerStubCommand `$stubName `$Command
        if (-not `$commandObj) {
            throw "Command '`$stubName : `$Command' not found!"
        }
 
        # Parse arguments from invocation line
        `$line = `$MyInvocation.Line
        Write-Debug "line: `$line"
 
        # Find where the command name ends and arguments begin
        `$cmdArgs = `$null
        `$i = `$line.IndexOf(`$Command)
        if (`$i -ge 0) {
            `$cmdArgs = `$line.Substring(`$i + `$Command.Length).Trim()
        }
 
        `$cmd = `$commandObj.Path
        `$module = Get-Module PowerStub
 
        # Execute the command
        if (`$cmdArgs) {
            Write-Host "Invoking `$cmd with arguments: `$cmdArgs"
            & `$module { param(`$c, `$p, `$a, `$t) Invoke-CheckedCommandWithParams `$c `$p `$a `$t } `$cmd `$null `$cmdArgs `$true
        }
        elseif (`$RemainingArgs -and `$RemainingArgs.Count -gt 0) {
            Write-Host "Invoking `$cmd with positional arguments"
            & `$module { param(`$c, `$p, `$a, `$t) Invoke-CheckedCommandWithParams `$c `$p `$a `$t } `$cmd `$RemainingArgs `$null `$true
        }
        else {
            Write-Host "Invoking `$cmd"
            & `$module { param(`$c, `$p, `$a, `$t) Invoke-CheckedCommandWithParams `$c `$p `$a `$t } `$cmd `$null `$null `$true
        }
    }
}
"@


    # Create the function
    Invoke-Expression $functionBody

    # Register ArgumentCompleter for -Command parameter
    # Note: Must call Find-PowerStubCommands through the module since it's a private function
    $commandCompleter = {
        param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

        $module = Get-Module PowerStub
        if (-not $module) { return @() }

        $commands = @(& $module { param($s) Find-PowerStubCommands $s } $Stub)
        if (-not $commands) { return @() }

        $commandNames = @($commands | ForEach-Object {
            $name = $_.BaseName
            if ($name -match '^(alpha|beta)\.(.+)$') {
                $Matches[2]
            } else {
                $name
            }
        } | Select-Object -Unique)

        if (-not $wordToComplete) { return $commandNames }

        $commandNames | Where-Object { $_ -like "$wordToComplete*" }
    }.GetNewClosure()

    Register-ArgumentCompleter -CommandName $AliasName -ParameterName Command -ScriptBlock $commandCompleter

    # Store in config for re-registration on module load
    $directAliases = Get-PowerStubConfigurationKey 'DirectAliases'
    if (-not $directAliases) {
        $directAliases = @{}
    }
    $directAliases[$AliasName] = $Stub
    Set-PowerStubConfigurationKey 'DirectAliases' $directAliases

    # Return info object
    $stubConfig = $stubs[$Stub]
    $stubPath = Get-PowerStubPath -StubConfig $stubConfig
    [PSCustomObject]@{
        AliasName = $AliasName
        Stub      = $Stub
        StubPath  = $stubPath
        Usage     = @"
Alias '$AliasName' created for stub '$Stub'.
 
Usage:
    $AliasName # List available commands
    $AliasName <command> # Run a command
    $AliasName <command> <Tab> # Tab complete command names
    $AliasName <command> -<Tab> # Tab complete command parameters
 
To make persistent, add to your `$PROFILE:
    Import-Module PowerStub
 
The alias is automatically recreated when the PowerStub module loads.
"@

    }
}