Private/Resolve-ConfigPath.ps1

function Resolve-ConfigPath {
    <#
    .SYNOPSIS
        Locates the Envoke config.json file using a three-location search order.
 
    .DESCRIPTION
        Searches for config.json in the following priority order, returning the first
        match found:
 
          1. ExplicitPath parameter — if provided, this path must exist (throws if not)
          2. $HOME\.envoke\config.json — user data folder, survives module updates
          3. Module directory config.json — beside the module files, works for git-clone users
 
        If no config file is found at any location, throws an actionable error message
        naming the expected locations and directing the user to copy config.example.jsonc.
 
        Guards against .jsonc files being passed as the config path — ConvertFrom-Json
        does not support JSONC comment syntax and will fail to parse such files.
 
    .PARAMETER ExplicitPath
        Optional. Explicit path to a config.json file. When provided, this path takes
        priority over all search locations. Throws a terminating error if the path does
        not exist or points to a .jsonc file.
 
    .NOTES
        Author: Aaron AlAnsari
        Created: 2026-02-24
 
        The module directory search (location 3) uses $PSScriptRoot which resolves to
        the Private/ directory at dot-source time. One level up (..) reaches the module
        root where config.json would be placed by git-clone users.
    #>


    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter()]
        [string]$ExplicitPath
    )

    # Guard: reject .jsonc files — ConvertFrom-Json cannot parse JSONC comment syntax.
    if (-not [string]::IsNullOrWhiteSpace($ExplicitPath) -and $ExplicitPath.EndsWith('.jsonc')) {
        throw "Config file must be plain JSON (.json). Copy config.example.jsonc and save as config.json, then remove the // comment lines."
    }

    # Location 1: explicit parameter always wins when provided.
    if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
        if (-not (Test-Path -Path $ExplicitPath -PathType Leaf)) {
            throw "Config file not found at specified path: '$ExplicitPath'. Verify the path exists and is accessible."
        }
        Write-EnvkLog -Level 'DEBUG' -Message "Resolved config path (explicit parameter): $ExplicitPath"
        return $ExplicitPath
    }

    # Location 2: user data folder — survives PSGallery/module updates.
    $userConfig = Join-Path $HOME '.envoke\config.json'
    if (Test-Path -Path $userConfig -PathType Leaf) {
        Write-EnvkLog -Level 'DEBUG' -Message "Resolved config path (user data folder): $userConfig"
        return $userConfig
    }

    # Location 3: module directory — beside the module files for git-clone users.
    # $PSScriptRoot is the Private/ directory; '..' reaches the module root.
    $moduleConfig = Join-Path $PSScriptRoot '..\config.json'
    if (Test-Path -Path $moduleConfig -PathType Leaf) {
        Write-EnvkLog -Level 'DEBUG' -Message "Resolved config path (module directory): $moduleConfig"
        return $moduleConfig
    }

    # No config found at any location — provide an actionable error message.
    $userLocation   = Join-Path $HOME '.envoke\config.json'
    $moduleLocation = Join-Path $PSScriptRoot '..\config.json'
    throw (
        "No config.json found. Copy config.example.jsonc from the module directory, " +
        "save it as config.json (removing the // comment lines), and place it at one of these locations:`n" +
        " $userLocation`n" +
        " $moduleLocation"
    )
}