Functions/Public/Export-OrionHelpers.ps1

<#
================================================================================
ORION DESIGN - POWERSHELL UI FRAMEWORK | Export-OrionHelpers Function
================================================================================
Author: Sune Alexandersen Narud
Date: January 27, 2026
Module: OrionDesign v2.0.0
Category: Utility / Bundling
Dependencies: OrionDesign Module
 
FUNCTION PURPOSE:
Analyzes a script that uses OrionDesign functions and creates a self-contained
helper file with all required function definitions. Makes scripts portable
without requiring the OrionDesign module to be installed.
 
HLD INTEGRATION:
┌─ SCRIPT ANALYSIS ─┐ ┌─ DEPENDENCY RESOLUTION ─┐ ┌─ OUTPUT ─┐
│ Export-OrionHelpers│◄──►│ Parse Script │───►│ Helper │
│ • Script Path │ │ Find Function Calls │ │ File │
│ • Output Path │ │ Resolve Dependencies │ │ Bundled │
│ • Auto-Comment │ │ Include Private Funcs │ │ Portable │
└────────────────────┘ └────────────────────────┘ └──────────┘
================================================================================
#>


<#
.SYNOPSIS
Exports used OrionDesign functions to a self-contained helper script.
 
.DESCRIPTION
The Export-OrionHelpers function analyzes a PowerShell script that imports
the OrionDesign module, identifies all OrionDesign functions used in the script,
and creates a consolidated helper file containing all required function definitions.
 
This makes scripts portable and self-contained without requiring the OrionDesign
module to be installed on target systems.
 
The function will:
1. Parse the target script to find all OrionDesign function calls
2. Resolve dependencies (including private helper functions)
3. Export all required functions to OrionDesign-Helpers.ps1
4. Optionally comment out the Import-Module OrionDesign line
 
.PARAMETER ScriptPath
The path to the PowerShell script to analyze.
 
.PARAMETER OutputPath
The directory where OrionDesign-Helpers.ps1 will be created.
Defaults to the same directory as the script.
 
.PARAMETER CommentOutImport
When specified, comments out the Import-Module OrionDesign line in the script.
Default is $true.
 
.PARAMETER IncludeTheme
When specified, includes a default theme initialization in the helper file.
Default is $true.
 
.PARAMETER WhatIf
Shows what would be done without making any changes.
 
.EXAMPLE
Export-OrionHelpers -ScriptPath "C:\Scripts\MyScript.ps1"
 
Analyzes MyScript.ps1, creates OrionDesign-Helpers.ps1 in C:\Scripts\,
and comments out the Import-Module line.
 
.EXAMPLE
Export-OrionHelpers -ScriptPath ".\Deploy.ps1" -OutputPath ".\lib"
 
Creates the helper file in a 'lib' subdirectory.
 
.EXAMPLE
Export-OrionHelpers -ScriptPath ".\MyScript.ps1" -CommentOutImport:$false
 
Creates helper file but leaves the Import-Module line unchanged.
 
.EXAMPLE
Export-OrionHelpers -ScriptPath ".\MyScript.ps1" -WhatIf
 
Shows which functions would be exported without creating any files.
 
.NOTES
After running this function, add the following line to your script (before using any OrionDesign functions):
. "$PSScriptRoot\OrionDesign-Helpers.ps1"
 
The helper file includes all necessary private functions that public functions depend on.
#>

function Export-OrionHelpers {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Position = 0)]
        [string]$ScriptPath,

        [Parameter(Position = 1)]
        [string]$OutputPath = "",

        [bool]$CommentOutImport = $true,

        [bool]$IncludeTheme = $true,

        [switch]$Demo
    )

    if ($Demo) {
        Write-Host ''
        Write-Host ' Export-OrionHelpers Demo' -ForegroundColor Cyan
        Write-Host ' ========================' -ForegroundColor DarkGray
        Write-Host ''
        Write-Host ' Makes scripts portable by bundling required OrionDesign functions.' -ForegroundColor DarkGray
        Write-Host ' Analyzes which OrionDesign functions your script uses and exports' -ForegroundColor DarkGray
        Write-Host ' them to a standalone OrionDesign-Helpers.ps1 file.' -ForegroundColor DarkGray
        Write-Host ''
        Write-Host ' Usage examples:' -ForegroundColor DarkGray
        Write-Host ' Export-OrionHelpers -ScriptPath ".\MyScript.ps1"' -ForegroundColor Green
        Write-Host ' # Scans MyScript.ps1 and creates OrionDesign-Helpers.ps1 alongside it' -ForegroundColor DarkGray
        Write-Host ''
        Write-Host ' Export-OrionHelpers -ScriptPath ".\Deploy.ps1" -OutputPath ".\lib"' -ForegroundColor Green
        Write-Host ' # Creates the helper file in a lib subdirectory' -ForegroundColor DarkGray
        Write-Host ''
        Write-Host ' Export-OrionHelpers -ScriptPath ".\MyScript.ps1" -WhatIf' -ForegroundColor Green
        Write-Host ' # Preview what would be exported without writing any files' -ForegroundColor DarkGray
        Write-Host ''
        Write-Host ' What happens:' -ForegroundColor DarkGray
        Write-Host ' 1 Parse script to find all OrionDesign function calls' -ForegroundColor White
        Write-Host ' 2 Resolve dependencies (including private helpers)' -ForegroundColor White
        Write-Host ' 3 Export all found functions to OrionDesign-Helpers.ps1' -ForegroundColor White
        Write-Host ' 4 Optionally comment out the Import-Module OrionDesign line' -ForegroundColor White
        Write-Host ''
        return
    }

    # If no ScriptPath provided, show interactive file selection
    if (-not $ScriptPath) {
        $currentDir = Get-Location
        $ps1Files = Get-ChildItem -Path $currentDir -Filter "*.ps1" -File -ErrorAction SilentlyContinue | 
                    Where-Object { $_.Name -ne "OrionDesign-Helpers.ps1" }
        
        if ($ps1Files.Count -eq 0) {
            Write-Host ""
            Write-Host " ⚠️ No PowerShell scripts (.ps1) found in current directory" -ForegroundColor Yellow
            Write-Host " Directory: $currentDir" -ForegroundColor Gray
            Write-Host ""
            Write-Host " Usage: Export-OrionHelpers -ScriptPath <path-to-script>" -ForegroundColor Cyan
            Write-Host ""
            return
        }

        Write-Host ""
        Write-Host " OrionDesign Helper Export" -ForegroundColor Cyan
        Write-Host " ═════════════════════════════════════════════════════════════" -ForegroundColor DarkGray
        Write-Host " Select a PowerShell script to scan for OrionDesign functions:" -ForegroundColor White
        Write-Host ""

        $index = 1
        $fileList = @()
        foreach ($file in $ps1Files) {
            $fileList += $file
            $sizeKB = [math]::Round($file.Length / 1KB, 1)
            Write-Host " [$index] " -NoNewline -ForegroundColor Yellow
            Write-Host $file.Name -NoNewline -ForegroundColor Green
            Write-Host " ($sizeKB KB)" -ForegroundColor DarkGray
            $index++
        }
        
        Write-Host ""
        Write-Host " [0] " -NoNewline -ForegroundColor Yellow
        Write-Host "Cancel" -ForegroundColor Red
        Write-Host ""
        Write-Host " ─────────────────────────────────────────────────────────────" -ForegroundColor DarkGray

        $selection = Read-Host " Enter selection (1-$($ps1Files.Count))"
        
        if ($selection -eq "0" -or [string]::IsNullOrWhiteSpace($selection)) {
            Write-Host ""
            Write-Host " ❌ Operation cancelled." -ForegroundColor Red
            Write-Host ""
            return
        }

        $selectedIndex = 0
        if (-not [int]::TryParse($selection, [ref]$selectedIndex) -or $selectedIndex -lt 1 -or $selectedIndex -gt $ps1Files.Count) {
            Write-Host ""
            Write-Host " ❌ Invalid selection. Please enter a number between 1 and $($ps1Files.Count)" -ForegroundColor Red
            Write-Host ""
            return
        }

        $ScriptPath = $fileList[$selectedIndex - 1].FullName
        Write-Host ""
        Write-Host " ✅ Selected: " -NoNewline -ForegroundColor Green
        Write-Host $fileList[$selectedIndex - 1].Name -ForegroundColor White
        Write-Host ""
    }

    # Validate the script path exists
    if (-not (Test-Path $ScriptPath -PathType Leaf)) {
        Write-Host ""
        Write-Host " ❌ File not found: $ScriptPath" -ForegroundColor Red
        Write-Host ""
        return
    }

    # Resolve full paths
    $ScriptPath = Resolve-Path $ScriptPath | Select-Object -ExpandProperty Path
    $scriptDir = Split-Path $ScriptPath -Parent
    $scriptName = Split-Path $ScriptPath -Leaf

    if (-not $OutputPath) {
        $OutputPath = $scriptDir
    }
    if (-not (Test-Path $OutputPath)) {
        if ($PSCmdlet.ShouldProcess($OutputPath, "Create directory")) {
            New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
        }
    }
    $OutputPath = Resolve-Path $OutputPath | Select-Object -ExpandProperty Path

    # Get module path
    $modulePath = Split-Path $PSScriptRoot -Parent | Split-Path -Parent
    $publicPath = Join-Path $modulePath "Functions\Public"
    $privatePath = Join-Path $modulePath "Functions\Private"

    # Get all available OrionDesign functions
    $publicFunctions = Get-ChildItem -Path $publicPath -Filter "*.ps1" -ErrorAction SilentlyContinue | 
                       Where-Object { $_.BaseName -ne "Export-OrionHelpers" }
    $privateFunctions = Get-ChildItem -Path $privatePath -Filter "*.ps1" -ErrorAction SilentlyContinue

    $publicFuncNames = $publicFunctions | ForEach-Object { $_.BaseName }
    $privateFuncNames = $privateFunctions | ForEach-Object { $_.BaseName }
    $totalPublicAvailable = $publicFuncNames.Count
    $totalPrivateAvailable = $privateFuncNames.Count

    $allFunctionNames = @()
    $allFunctionNames += $publicFuncNames
    $allFunctionNames += $privateFuncNames

    # Read the target script
    $scriptContent = Get-Content -Path $ScriptPath -Raw

    # Find all OrionDesign functions used in the script
    $usedFunctions = @()
    foreach ($funcName in $allFunctionNames) {
        # Match function calls: FunctionName followed by space, newline, or parameters
        if ($scriptContent -match "\b$funcName\b") {
            $usedFunctions += $funcName
        }
    }

    # Remove duplicates and sort
    $usedFunctions = $usedFunctions | Sort-Object -Unique

    if ($usedFunctions.Count -eq 0) {
        Write-Warning "No OrionDesign functions found in '$scriptName'"
        return
    }

    # Resolve dependencies - check which private functions are needed
    $requiredPrivate = @()
    $privateFuncNames = $privateFunctions | ForEach-Object { $_.BaseName }

    foreach ($pubFunc in $usedFunctions) {
        $pubFuncPath = Join-Path $publicPath "$pubFunc.ps1"
        if (Test-Path $pubFuncPath) {
            $pubFuncContent = Get-Content -Path $pubFuncPath -Raw
            foreach ($privFunc in $privateFuncNames) {
                if ($pubFuncContent -match "\b$privFunc\b") {
                    $requiredPrivate += $privFunc
                }
            }
        }
    }

    # Also check if private functions call other private functions
    foreach ($privFunc in $requiredPrivate) {
        $privFuncPath = Join-Path $privatePath "$privFunc.ps1"
        if (Test-Path $privFuncPath) {
            $privFuncContent = Get-Content -Path $privFuncPath -Raw
            foreach ($otherPriv in $privateFuncNames) {
                if ($privFuncContent -match "\b$otherPriv\b" -and $otherPriv -ne $privFunc) {
                    $requiredPrivate += $otherPriv
                }
            }
        }
    }

    $requiredPrivate = $requiredPrivate | Sort-Object -Unique

    # Calculate usage statistics
    $usedPublicCount = ($usedFunctions | Where-Object { $_ -in $publicFuncNames }).Count
    $usagePercent = [math]::Round(($usedPublicCount / $totalPublicAvailable) * 100, 0)

    # Display what will be exported
    Write-Host ""
    Write-Host "OrionDesign Export Analysis" -ForegroundColor Cyan
    Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor DarkGray
    Write-Host " Script: " -NoNewline -ForegroundColor Gray
    Write-Host $scriptName -ForegroundColor White
    Write-Host " Output: " -NoNewline -ForegroundColor Gray
    Write-Host (Join-Path $OutputPath "OrionDesign-Helpers.ps1") -ForegroundColor White
    Write-Host ""
    Write-Host " Coverage: " -NoNewline -ForegroundColor Gray
    Write-Host "$usedPublicCount" -NoNewline -ForegroundColor Cyan
    Write-Host " of " -NoNewline -ForegroundColor Gray
    Write-Host "$totalPublicAvailable" -NoNewline -ForegroundColor White
    Write-Host " public functions used (" -NoNewline -ForegroundColor Gray
    if ($usagePercent -ge 50) {
        Write-Host "$usagePercent%" -NoNewline -ForegroundColor Green
    } elseif ($usagePercent -ge 25) {
        Write-Host "$usagePercent%" -NoNewline -ForegroundColor Yellow
    } else {
        Write-Host "$usagePercent%" -NoNewline -ForegroundColor DarkGray
    }
    Write-Host ")" -ForegroundColor Gray
    Write-Host ""
    Write-Host " Public Functions ($($usedFunctions.Count)):" -ForegroundColor Yellow
    foreach ($func in $usedFunctions) {
        Write-Host " • $func" -ForegroundColor Green
    }
    if ($requiredPrivate.Count -gt 0) {
        Write-Host ""
        Write-Host " Private Dependencies ($($requiredPrivate.Count)):" -ForegroundColor Yellow
        foreach ($func in $requiredPrivate) {
            Write-Host " • $func" -ForegroundColor DarkGreen
        }
    }
    Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor DarkGray
    Write-Host ""

    if ($WhatIfPreference) {
        Write-Host "[WhatIf] Would create OrionDesign-Helpers.ps1 with $($usedFunctions.Count + $requiredPrivate.Count) functions" -ForegroundColor Yellow
        if ($CommentOutImport) {
            Write-Host "[WhatIf] Would comment out Import-Module OrionDesign in $scriptName" -ForegroundColor Yellow
        }
        return
    }

    # Build the helper file content
    $helperContent = @"
<#
================================================================================
ORION DESIGN - HELPER FUNCTIONS (Auto-generated)
================================================================================
Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Source Script: $scriptName
Module: OrionDesign v2.0.0
Author: Sune Alexandersen Narud
 
This file contains OrionDesign functions extracted for portability.
Do not edit manually - regenerate using Export-OrionHelpers if needed.
 
Functions included:
$($usedFunctions | ForEach-Object { " - $_" } | Out-String)
================================================================================
#>
 
"@


    # Add theme initialization if requested
    if ($IncludeTheme) {
        $helperContent += @"
#region Theme Initialization
# Initialize default theme (required for color functions)
if (-not `$script:Theme) {
    `$script:Theme = @{
        Primary = 'Cyan'
        Secondary = 'DarkCyan'
        Success = 'Green'
        Warning = 'Yellow'
        Error = 'Red'
        Accent = 'Magenta'
        Text = 'White'
        Muted = 'DarkGray'
        Border = '─'
        Corner = '+'
        Divider = '─'
        UseAnsi = `$true
    }
    if (`$psISE) { `$script:Theme.UseAnsi = `$false }
}
 
# Initialize default max width
if (-not `$script:OrionMaxWidth) {
    `$script:OrionMaxWidth = 100
}
#endregion
 
"@

    }

    # Add private functions first (dependencies)
    if ($requiredPrivate.Count -gt 0) {
        $helperContent += @"
#region Private Helper Functions
"@

        foreach ($privFunc in $requiredPrivate) {
            $privFuncPath = Join-Path $privatePath "$privFunc.ps1"
            if (Test-Path $privFuncPath) {
                $funcContent = Get-Content -Path $privFuncPath -Raw
                # Extract just the function definition (skip header comments)
                if ($funcContent -match '(?s)(function\s+' + $privFunc + '\s*\{.+)') {
                    $helperContent += "`n# Private: $privFunc`n"
                    $helperContent += $Matches[1].Trim()
                    $helperContent += "`n"
                } else {
                    # Include entire file if pattern doesn't match
                    $helperContent += "`n# Private: $privFunc`n"
                    $helperContent += $funcContent
                    $helperContent += "`n"
                }
            }
        }
        $helperContent += @"
#endregion
 
"@

    }

    # Add public functions
    $helperContent += @"
#region Public Functions
"@

    foreach ($pubFunc in $usedFunctions) {
        $pubFuncPath = Join-Path $publicPath "$pubFunc.ps1"
        if (Test-Path $pubFuncPath) {
            $funcContent = Get-Content -Path $pubFuncPath -Raw
            # Extract just the function definition (skip header comments)
            if ($funcContent -match '(?s)(function\s+' + $pubFunc + '\s*\{.+)') {
                $helperContent += "`n# $pubFunc`n"
                $helperContent += $Matches[1].Trim()
                $helperContent += "`n"
            } else {
                # Include entire file if pattern doesn't match
                $helperContent += "`n# $pubFunc`n"
                $helperContent += $funcContent
                $helperContent += "`n"
            }
        }
    }
    $helperContent += @"
#endregion
"@


    # Write the helper file
    $helperFilePath = Join-Path $OutputPath "OrionDesign-Helpers.ps1"
    if ($PSCmdlet.ShouldProcess($helperFilePath, "Create helper file")) {
        Set-Content -Path $helperFilePath -Value $helperContent -Encoding UTF8
        Write-Host "✅ Created: " -NoNewline -ForegroundColor Green
        Write-Host $helperFilePath -ForegroundColor White
    }

    # Comment out the Import-Module line and add dot-source line
    if ($CommentOutImport) {
        $importPatterns = @(
            'Import-Module\s+OrionDesign',
            'Import-Module\s+[''"].*OrionDesign[''"]',
            'Import-Module\s+.*OrionDesign\.psd1',
            'Import-Module\s+.*OrionDesign\.psm1'
        )

        $modified = $false
        $newContent = $scriptContent
        $dotSourceLine = '. "$PSScriptRoot\OrionDesign-Helpers.ps1"'

        foreach ($pattern in $importPatterns) {
            if ($newContent -match "(?m)^(\s*)($pattern.*)$") {
                $fullMatch = $Matches[0]
                $indent = $Matches[1]
                if ($fullMatch -notmatch '^\s*#') {
                    # Comment out old line and add dot-source line
                    $replacement = "$indent# $($Matches[2]) # Commented by Export-OrionHelpers`n$indent$dotSourceLine"
                    $newContent = $newContent -replace [regex]::Escape($fullMatch), $replacement
                    $modified = $true
                }
            }
        }

        if ($modified) {
            if ($PSCmdlet.ShouldProcess($ScriptPath, "Comment out Import-Module and add dot-source")) {
                Set-Content -Path $ScriptPath -Value $newContent -Encoding UTF8 -NoNewline
                Write-Host "✅ Updated: " -NoNewline -ForegroundColor Green
                Write-Host $scriptName -ForegroundColor White
                Write-Host " • Commented out Import-Module OrionDesign" -ForegroundColor Gray
                Write-Host " • Added: $dotSourceLine" -ForegroundColor Gray
            }
        } else {
            Write-Host "ℹ️ No Import-Module OrionDesign line found to comment out" -ForegroundColor Yellow
            Write-Host " Add this line manually: $dotSourceLine" -ForegroundColor Yellow
        }
    }

    # Show completion message
    Write-Host ""
    Write-Host "✅ Export complete!" -ForegroundColor Green
    Write-Host "───────────────────────────────────────────────────────────────" -ForegroundColor DarkGray
    Write-Host " The script is now portable and doesn't require OrionDesign module." -ForegroundColor Gray
    Write-Host "───────────────────────────────────────────────────────────────" -ForegroundColor DarkGray
    Write-Host ""

    # Return summary object
    [PSCustomObject]@{
        HelperFile       = $helperFilePath
        PublicFunctions  = $usedFunctions
        PrivateFunctions = $requiredPrivate
        TotalFunctions   = $usedFunctions.Count + $requiredPrivate.Count
        ScriptModified   = $modified
    }
}