public/functions/Add-PowerStubToProfile.ps1

<#
.SYNOPSIS
    Adds an Import-Module line for PowerStub to a PowerShell profile.

.DESCRIPTION
    Makes PowerStub load in every new session. Safer than appending the line by hand:

    - Creates the profile file and its folder when they do not exist yet.
    - Does nothing when the profile already imports PowerStub.
    - Refuses to touch a profile that already has syntax errors, and checks that the
      profile still parses with the new line as its own statement before writing.
    - Appends in place, so the file's encoding is kept and a symbolic link to a
      dotfiles profile is followed rather than replaced.

    The line is 'Import-Module PowerStub' when the running module can be found by name
    on PSModulePath (a PowerShell Gallery install), otherwise an import of this module's
    manifest path (a source or archive install).

.PARAMETER Path
    The profile file to change. Defaults to $PROFILE.CurrentUserAllHosts, which every
    PowerShell host (console, Windows Terminal, VS Code) runs for the current user.
    Pass $PROFILE to change only the current host's profile.

.EXAMPLE
    Add-PowerStubToProfile

    Adds the import to the current user's all-hosts profile.

.EXAMPLE
    Add-PowerStubToProfile -Path $PROFILE -WhatIf

    Shows what would be added to the current host's profile without changing it.

.OUTPUTS
    PSCustomObject with Path, Line (the import line added or already present) and Added.
#>


function Add-PowerStubToProfile {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Path = $PROFILE.CurrentUserAllHosts
    )

    if (-not $Path) {
        throw 'This session has no $PROFILE path. Pass the profile file with -Path.'
    }
    $Path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)

    $manifest = Join-Path $Script:ModulePath 'PowerStub.psd1'
    $onModulePath = Get-Module -ListAvailable -Name PowerStub | Where-Object { $_.Path -eq $manifest }
    $importLine = if ($onModulePath) { 'Import-Module PowerStub' } else { "Import-Module '$($manifest.Replace("'", "''"))'" }

    $profileInfo = Read-PowerStubProfile -Path $Path
    if ($profileInfo.Errors) {
        $first = $profileInfo.Errors[0]
        throw "Profile '$Path' has syntax errors (line $($first.Extent.StartLineNumber): $($first.Message)). Fix them, then run Add-PowerStubToProfile again. Nothing was changed."
    }

    $existing = $profileInfo.Imports | Select-Object -First 1
    if ($existing) {
        Write-Verbose "Profile '$Path' already imports PowerStub on line $($existing.Extent.StartLineNumber)"
        return [PSCustomObject]@{ Path = $Path; Line = $existing.Extent.Text; Added = $false }
    }

    $text = $profileInfo.Text
    $newline = if ($text -match "`r`n") { "`r`n" } elseif ($text -match "`n") { "`n" } else { [Environment]::NewLine }
    $prefix = ''
    if ($text) {
        $prefix = if ($text.EndsWith("`n")) { $newline } else { $newline + $newline }
    }
    $addition = "$prefix# PowerStub$newline$importLine$newline"

    # The existing text parsed cleanly, but check the result too: the new line must stand
    # as the profile's last statement, not be absorbed into something before it.
    $tokens = $null
    $errors = $null
    $newAst = [System.Management.Automation.Language.Parser]::ParseInput($text + $addition, [ref]$tokens, [ref]$errors)
    $lastStatement = $newAst.EndBlock.Statements | Select-Object -Last 1
    if ($errors -or -not $lastStatement -or $lastStatement.Extent.Text -ne $importLine) {
        throw "Adding '$importLine' to profile '$Path' would not produce a separate statement. Add it by hand. Nothing was changed."
    }

    if (-not $PSCmdlet.ShouldProcess($Path, "Add '$importLine'")) {
        return
    }

    [System.IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null
    if ($profileInfo.Encoding) {
        [System.IO.File]::AppendAllText($Path, $addition, $profileInfo.Encoding)
    }
    else {
        [System.IO.File]::AppendAllText($Path, $addition)
    }

    Write-Verbose "Added '$importLine' to profile '$Path'. It takes effect in new sessions."
    [PSCustomObject]@{ Path = $Path; Line = $importLine; Added = $true }
}