< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Utilities.PowerShellModuleLocator
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/PowerShellModuleLocator.cs
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
28%
Covered lines: 15
Uncovered lines: 37
Coverable lines: 52
Total lines: 129
Line coverage: 28.8%
Branch coverage
35%
Covered branches: 7
Total branches: 20
Branch coverage: 35%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetPSModulePathsViaPwsh()0%2040%
LocateKestrunModule()25%431240%
FindFileUpwards(...)100%44100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/PowerShellModuleLocator.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Reflection;
 3using Serilog;
 4namespace Kestrun.Utilities;
 5
 6/// <summary>
 7/// Utility class to locate the Kestrun PowerShell module.
 8/// It searches for the module in both development and production environments.
 9/// </summary>
 10public static class PowerShellModuleLocator
 11{
 12    /// <summary>
 13    /// Retrieves the PowerShell module paths using pwsh.
 14    /// This method executes a PowerShell command to get the PSModulePath environment variable,
 15    /// splits it by the path separator, and returns the individual paths as an array.
 16    /// </summary>
 17    /// <returns>Array of PowerShell module paths.</returns>
 18    private static string[] GetPSModulePathsViaPwsh()
 19    {
 20        try
 21        {
 022            var psi = new ProcessStartInfo
 023            {
 024                FileName = "pwsh",
 025                Arguments = "-NoProfile -Command \"$env:PSModulePath -split [IO.Path]::PathSeparator\"",
 026                RedirectStandardOutput = true,
 027                RedirectStandardError = true,
 028                UseShellExecute = false,
 029                CreateNoWindow = true
 030            };
 31
 032            using var proc = Process.Start(psi);
 033            if (proc == null)
 34            {
 035                Log.Error("❌ Failed to start pwsh process.");
 036                return [];
 37            }
 38
 039            var output = proc.StandardOutput.ReadToEnd();
 040            var error = proc.StandardError.ReadToEnd();
 41
 042            proc.WaitForExit();
 43
 044            if (proc.ExitCode != 0)
 45            {
 046                Log.Error("❌ pwsh exited with code {ExitCode}. Error:\n{Error}", proc.ExitCode, error);
 047                return [];
 48            }
 49
 050            return output
 051                .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
 52        }
 053        catch (Exception ex)
 54        {
 055            Log.Error("⚠️ Exception during pwsh invocation: {Message}", ex.Message);
 056            return [];
 57        }
 058    }
 59
 60    /// <summary>
 61    /// Locates the Kestrun module path.
 62    /// It first attempts to find the module in the development environment by searching upwards from the current direct
 63    /// If not found, it will then check the production environment using PowerShell.
 64    /// </summary>
 65    /// <returns>The full path to the Kestrun module if found, otherwise null.</returns>
 66    public static string? LocateKestrunModule()
 67    {
 68        // 1. Try development search
 11769        var asm = Assembly.GetExecutingAssembly();
 11770        var dllPath = asm.Location;
 71        // Get full InformationalVersion
 11772        var fullVersion = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
 73
 74        // Strip build metadata if present (everything after and including '+')
 11775        var semver = fullVersion?.Split('+')[0];
 76
 11777        var devPath = FindFileUpwards(Path.GetDirectoryName(dllPath)!, Path.Combine("src", "PowerShell", "Kestrun", "Kes
 78
 11779        if (devPath != null)
 80        {
 11781            Log.Information("🌿 Development module found.");
 11782            return devPath;
 83        }
 084        if (semver == null)
 85        {
 086            Log.Error("🚫 Unable to determine assembly version for Kestrun module lookup.");
 087            return null;
 88        }
 089        Log.Information("🔍 Searching for Kestrun PowerShell module version: {Semver}", semver);
 90        // 2. Production mode - ask pwsh
 091        Log.Information("🛰  Switching to production lookup via pwsh...");
 092        foreach (var path in GetPSModulePathsViaPwsh())
 93        {
 094            var full = Path.Combine(path, "Kestrun", semver, "Kestrun.psm1");
 095            if (File.Exists(full))
 96            {
 097                Console.WriteLine($"✅ Found production module: {full}");
 098                return full;
 99            }
 100        }
 101
 0102        Log.Error("🚫 Kestrun.psm1 not found in any known location.");
 0103        return null;
 104    }
 105
 106    /// <summary>
 107    /// Finds a file upwards from the current directory.
 108    /// </summary>
 109    /// <param name="startDir">The starting directory to search from.</param>
 110    /// <param name="relativeTarget">The relative path of the target file.</param>
 111    /// <returns>The full path to the file if found, otherwise null.</returns>
 112    private static string? FindFileUpwards(string startDir, string relativeTarget)
 113    {
 119114        var current = startDir;
 115
 824116        while (current != null)
 117        {
 823118            var candidate = Path.Combine(current, relativeTarget);
 823119            if (File.Exists(candidate))
 120            {
 118121                return candidate;
 122            }
 123
 705124            current = Path.GetDirectoryName(current);
 125        }
 126
 1127        return null;
 128    }
 129}