< 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@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
63%
Covered lines: 33
Uncovered lines: 19
Coverable lines: 52
Total lines: 129
Line coverage: 63.4%
Branch coverage
45%
Covered branches: 9
Total branches: 20
Branch coverage: 45%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/08/2025 - 20:34:03 Line coverage: 28.8% (15/52) Branch coverage: 35% (7/20) Total lines: 129 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7201/08/2026 - 08:19:25 Line coverage: 63.4% (33/52) Branch coverage: 45% (9/20) Total lines: 129 Tag: Kestrun/Kestrun@6ab94ca7560634c2ac58b36c2b98e2a9b1bf305d 09/08/2025 - 20:34:03 Line coverage: 28.8% (15/52) Branch coverage: 35% (7/20) Total lines: 129 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7201/08/2026 - 08:19:25 Line coverage: 63.4% (33/52) Branch coverage: 45% (9/20) Total lines: 129 Tag: Kestrun/Kestrun@6ab94ca7560634c2ac58b36c2b98e2a9b1bf305d

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetPSModulePathsViaPwsh()50%4472%
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        {
 122            var psi = new ProcessStartInfo
 123            {
 124                FileName = "pwsh",
 125                Arguments = "-NoProfile -Command \"$env:PSModulePath -split [IO.Path]::PathSeparator\"",
 126                RedirectStandardOutput = true,
 127                RedirectStandardError = true,
 128                UseShellExecute = false,
 129                CreateNoWindow = true
 130            };
 31
 132            using var proc = Process.Start(psi);
 133            if (proc == null)
 34            {
 035                Log.Error("❌ Failed to start pwsh process.");
 036                return [];
 37            }
 38
 139            var output = proc.StandardOutput.ReadToEnd();
 140            var error = proc.StandardError.ReadToEnd();
 41
 142            proc.WaitForExit();
 43
 144            if (proc.ExitCode != 0)
 45            {
 046                Log.Error("❌ pwsh exited with code {ExitCode}. Error:\n{Error}", proc.ExitCode, error);
 047                return [];
 48            }
 49
 150            return output
 151                .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        }
 158    }
 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
 58769        var asm = Assembly.GetExecutingAssembly();
 58770        var dllPath = asm.Location;
 71        // Get full InformationalVersion
 58772        var fullVersion = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
 73
 74        // Strip build metadata if present (everything after and including '+')
 58775        var semver = fullVersion?.Split('+')[0];
 76
 58777        var devPath = FindFileUpwards(Path.GetDirectoryName(dllPath)!, Path.Combine("src", "PowerShell", "Kestrun", "Kes
 78
 58779        if (devPath != null)
 80        {
 58781            Log.Information("🌿 Development module found.");
 58782            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    {
 594114        var current = startDir;
 115
 4125116        while (current != null)
 117        {
 4123118            var candidate = Path.Combine(current, relativeTarget);
 4123119            if (File.Exists(candidate))
 120            {
 592121                return candidate;
 122            }
 123
 3531124            current = Path.GetDirectoryName(current);
 125        }
 126
 2127        return null;
 128    }
 129}