Public/Update-Toolkit.ps1

function Update-Toolkit {
    <#
    .SYNOPSIS
        Self-update the PowerShell Dev Toolkit from its git remote.
 
    .DESCRIPTION
        Pulls the latest changes from the toolkit's git repository, shows a
        summary of what changed, and re-imports the module so new commands and
        aliases take effect immediately.
 
    .PARAMETER CheckOnly
        Only check whether updates are available without applying them.
 
    .PARAMETER Force
        Skip the confirmation prompt and apply updates immediately.
 
    .EXAMPLE
        Update-Toolkit
    .EXAMPLE
        Update-Toolkit -CheckOnly
    .EXAMPLE
        Update-Toolkit -Force
    #>

    [CmdletBinding()]
    param(
        [switch]$CheckOnly,
        [switch]$Force
    )

    $toolkitDir = $script:ToolkitRoot

    if (-not (Test-Path (Join-Path $toolkitDir ".git"))) {
        Write-Error "Toolkit directory is not a git repository: $toolkitDir"
        return
    }

    $git = Get-Command git -ErrorAction SilentlyContinue
    if (-not $git) {
        Write-Error "Git is not installed or not in PATH."
        return
    }

    Push-Location $toolkitDir
    try {
        $currentBranch = git rev-parse --abbrev-ref HEAD 2>$null
        if (-not $currentBranch) {
            Write-Error "Failed to determine current git branch."
            return
        }

        Write-Host "Checking for updates..." -ForegroundColor Cyan
        git fetch origin $currentBranch --quiet 2>$null

        $localHead  = git rev-parse HEAD 2>$null
        $remoteHead = git rev-parse "origin/$currentBranch" 2>$null

        if ($localHead -eq $remoteHead) {
            Write-Host "Already up to date." -ForegroundColor Green
            $manifest = Import-PowerShellDataFile (Join-Path $toolkitDir "PowerShellDevToolkit\PowerShellDevToolkit.psd1")
            Write-Host " Version: $($manifest.ModuleVersion)" -ForegroundColor Gray
            Write-Host " Branch: $currentBranch" -ForegroundColor Gray
            return
        }

        $behind = git rev-list --count "HEAD..origin/$currentBranch" 2>$null
        Write-Host "$behind new commit(s) available on $currentBranch" -ForegroundColor Yellow
        Write-Host ""

        $log = git log --oneline "HEAD..origin/$currentBranch" 2>$null
        if ($log) {
            Write-Host "Changes:" -ForegroundColor Cyan
            $log | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
            Write-Host ""
        }

        $diffStat = git diff --stat "HEAD..origin/$currentBranch" 2>$null
        if ($diffStat) {
            Write-Host "Files changed:" -ForegroundColor Cyan
            $diffStat | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
            Write-Host ""
        }

        if ($CheckOnly) { return }

        if (-not $Force) {
            Write-Host "Apply update? (Y/N): " -NoNewline -ForegroundColor Yellow
            $response = Read-Host
            if ($response -ne 'Y' -and $response -ne 'y') {
                Write-Host "Update cancelled." -ForegroundColor Gray
                return
            }
        }

        Write-Host "Pulling changes..." -ForegroundColor Cyan
        $pullOutput = git pull origin $currentBranch 2>&1
        $pullExitCode = $LASTEXITCODE

        if ($pullExitCode -ne 0) {
            Write-Host "Git pull failed:" -ForegroundColor Red
            $pullOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
            Write-Host ""
            Write-Host "You may need to resolve conflicts manually." -ForegroundColor Yellow
            return
        }

        $pullOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }

        Write-Host ""
        Write-Host "Re-importing module..." -ForegroundColor Cyan
        Import-Module (Join-Path $toolkitDir "PowerShellDevToolkit") -Force -Global -DisableNameChecking

        $manifest = Import-PowerShellDataFile (Join-Path $toolkitDir "PowerShellDevToolkit\PowerShellDevToolkit.psd1")
        Write-Host ""
        Write-Host "Updated to version $($manifest.ModuleVersion)" -ForegroundColor Green
        Write-Host "All commands and aliases are now current." -ForegroundColor Green

        Set-ToolkitUpdateTimestamp
    } finally {
        Pop-Location
    }
}

function Test-ToolkitUpdate {
    <#
    .SYNOPSIS
        Silently check if toolkit updates are available (used on shell startup).
 
    .DESCRIPTION
        Compares the local HEAD against the remote. Returns $true if there are
        commits to pull. Respects the updateCheckDays setting in config.json
        so it only hits the network at the configured frequency.
    #>

    [CmdletBinding()]
    param()

    $toolkitDir = $script:ToolkitRoot

    if (-not (Test-Path (Join-Path $toolkitDir ".git"))) { return }
    if (-not (Get-Command git -ErrorAction SilentlyContinue)) { return }

    $stampFile = Join-Path $toolkitDir ".last-update-check"
    $config = Get-ScriptConfig -ErrorAction SilentlyContinue
    $intervalDays = 1
    if ($config -and $config.toolkit -and $null -ne $config.toolkit.updateCheckDays) {
        $intervalDays = [int]$config.toolkit.updateCheckDays
    }

    if ($intervalDays -le 0) { return }

    if (Test-Path $stampFile) {
        $lastCheck = (Get-Item $stampFile).LastWriteTime
        if (([datetime]::Now - $lastCheck).TotalDays -lt $intervalDays) { return }
    }

    Push-Location $toolkitDir
    try {
        $branch = git rev-parse --abbrev-ref HEAD 2>$null
        if (-not $branch) { return }

        git fetch origin $branch --quiet 2>$null
        $local  = git rev-parse HEAD 2>$null
        $remote = git rev-parse "origin/$branch" 2>$null

        Set-ToolkitUpdateTimestamp

        if ($local -ne $remote) {
            $behind = git rev-list --count "HEAD..origin/$branch" 2>$null
            Write-Host ""
            Write-Host "PowerShell Dev Toolkit: $behind update(s) available. Run " -NoNewline -ForegroundColor Yellow
            Write-Host "Update-Toolkit" -NoNewline -ForegroundColor Cyan
            Write-Host " to update." -ForegroundColor Yellow
        }
    } finally {
        Pop-Location
    }
}

function Set-ToolkitUpdateTimestamp {
    <# Touches the .last-update-check stamp file. #>
    [CmdletBinding()]
    param()
    $stampFile = Join-Path $script:ToolkitRoot ".last-update-check"
    [IO.File]::WriteAllText($stampFile, (Get-Date -Format 'o'))
}