Public/Get-DevVmSystem.ps1

<#
.SYNOPSIS
Show complete DevVm system information and configuration.
 
.DESCRIPTION
Displays complete system information including:
- Configured runtimes and their paths (with source level)
- Configuration by level (global, project, session)
- Current activated versions
- Storage information (installations, cache)
 
.EXAMPLE
Get-DevVmSystem
devvm system
 
#>

function Get-DevVmSystem {
    [CmdletBinding()]
    param ()

    Write-Output ""
    Write-Output "DevVm Configuration"
    Write-Output "===================="
    Write-Output ""

    if ($script:DevVmConfig.RuntimeDefinitions) {
        Write-Output "Configured Runtimes:"
        $runtimes = @('node', 'java', 'maven', 'lein')

        foreach ($rt in $runtimes) {
            $rtInfo = Get-DevVmRuntimeInfo -Runtime $rt
            if (-not $rtInfo) { continue }

            $resolvedPath = Get-DevVmInstallPath -Runtime $rt

            $pathSource = 'default'
            $envVar = "DEVVM_${rt.ToUpper()}_PATH"
            if (Test-Path env:$envVar) {
                $pathSource = 'environment'
            }
            else {
                $projectDevVm = Resolve-DevVmProjectConfigPath -StartPath (Get-Location).Path
                if ($projectDevVm -and (Test-Path $projectDevVm)) {
                    try {
                        $projectRaw = Get-Content -Path $projectDevVm -Raw
                        $config = ConvertFrom-DevVmJsonSafe -Raw $projectRaw
                        if ($config -and $config.runtimes -and $config.runtimes.$rt -and $config.runtimes.$rt.basePath) {
                            $pathSource = 'project'
                        }
                    }
                    catch {
                        Write-Verbose "Failed to read project .devvm: $_"
                    }
                }

                if ($pathSource -eq 'default') {
                    $globalDevVm = $script:DevVmConfig.ConfigPaths.global
                    if ($globalDevVm -and (Test-Path $globalDevVm)) {
                        try {
                            $globalRaw = Get-Content -Path $globalDevVm -Raw
                            $config = ConvertFrom-DevVmJsonSafe -Raw $globalRaw
                            if ($config -and $config.runtimes -and $config.runtimes.$rt -and $config.runtimes.$rt.basePath) {
                                $pathSource = 'global'
                            }
                        }
                        catch {
                            Write-Verbose "Failed to read global .devvm: $_"
                        }
                    }
                }
            }

            Write-Output " - $rt"
            Write-Output " Base Path: $resolvedPath (from $pathSource)"
            Write-Output " Command: $($rtInfo.command)"
        }
        Write-Output ""
    }

    Write-Output "Configuration Paths:"
    Write-Output " session: Memory"
    Write-Output " project: $($script:DevVmConfig.ConfigPaths.project)"
    Write-Output " global: $($script:DevVmConfig.ConfigPaths.global)"
    Write-Output ""

    Write-Output "Configurations by Level:"

    $levelsToShow = @('global', 'project', 'session')
    $pathByLevel = [ordered]@{
        global = $script:DevVmConfig.ConfigPaths.global
        project = Resolve-DevVmProjectConfigPath -StartPath (Get-Location).Path
        session = $script:DevVmConfig.ConfigPaths.session
    }

    foreach ($level in $levelsToShow) {
        $configData = @{}
        $path = $pathByLevel[$level]
        $pathExists = $false

        if ($level -eq 'session') {
            if ($script:DevVmConfig.SessionSelections -and $script:DevVmConfig.SessionSelections.Count -gt 0) {
                $configData = $script:DevVmConfig.SessionSelections
            }
            $pathExists = $true
        }
        else {
            if ($path -and (Test-Path -LiteralPath $path)) {
                $pathExists = $true
                try {
                    $rawContent = Get-Content -Path $path -Raw
                    if ($rawContent -and $rawContent.Trim()) {
                        $parsed = ConvertFrom-DevVmJsonSafe -Raw $rawContent

                        if ($parsed) {
                            function ConvertJsonToHashtable {
                                param([object]$Parsed)
                                $data = @{}
                                if ($Parsed -is [pscustomobject]) {
                                    foreach ($property in $Parsed.PSObject.Properties) {
                                        if ($property.Value -is [pscustomobject]) {
                                            $data[$property.Name] = ConvertJsonToHashtable -Parsed $property.Value
                                        }
                                        else {
                                            $data[$property.Name] = $property.Value
                                        }
                                    }
                                }
                                return $data
                            }

                            $configData = ConvertJsonToHashtable -Parsed $parsed
                        }
                    }
                }
                catch {
                    Write-Verbose "Failed to load config at ${path}: $_"
                }
            }
        }

        $existsText = if ($pathExists) { 'yes' } else { 'no' }
        $pathText = $pathByLevel[$level]
        Write-Output " $level (exists: $existsText): $pathText"

        if ($configData.Count -gt 0) {
            if ($configData.Contains('versions') -and $configData['versions']) {
                Write-Output " versions:"
                foreach ($rt in ($configData['versions'].Keys | Sort-Object)) {
                    Write-Output " $rt = $($configData['versions'][$rt])"
                }
            }

            if ($configData.Contains('runtimes') -and $configData['runtimes']) {
                Write-Output " runtimes:"
                foreach ($rt in ($configData['runtimes'].Keys | Sort-Object)) {
                    $rtConfig = $configData['runtimes'][$rt]
                    if ($rtConfig -is [hashtable]) {
                        if ($rtConfig.Contains('basePath')) {
                            Write-Output " ${rt}:"
                            Write-Output " basePath = $($rtConfig['basePath'])"
                        }
                    }
                }
            }
        }
        else {
            Write-Output " (no entries)"
        }
    }
    Write-Output ""

    Write-Output "Activation (merged selection):"
    $merged = Get-DevVmMergedConfig
    if ($merged -and $merged.Count -gt 0) {
        foreach ($rt in ($merged.Keys | Sort-Object)) {
            Write-Output " $rt = $($merged[$rt])"
        }
    }
    else {
        Write-Output " (no entries)"
    }
    Write-Output ""

    Write-Output "Storage Information"
    Write-Output "==================="
    Write-Output ""

    $runtimes = @('node', 'java', 'maven', 'lein')
    $totalInstallSize = 0
    $totalCacheSize = 0

    function Get-DevVmDirectorySizeMB {
        param([string]$Path)
        $sum = (Get-ChildItem -Path $Path -File -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
        if ($null -eq $sum) { return 0 }
        return ($sum / 1MB)
    }

    Write-Output "Installations:"
    Write-Output ""

    foreach ($rt in $runtimes) {
        $installPath = Get-DevVmInstallPath -Runtime $rt

        if (Test-Path $installPath) {
            $versions = @(Get-ChildItem -Path $installPath -Directory -ErrorAction SilentlyContinue)
            $rtSize = 0

            if ($versions.Count -gt 0) {
                foreach ($version in $versions) {
                    $versionSize = Get-DevVmDirectorySizeMB -Path $version.FullName
                    $rtSize += $versionSize
                }

                $totalInstallSize += $rtSize
                Write-Output " $rt : $([math]::Round($rtSize, 2)) MB ($($versions.Count) versions)"
            }
        }
    }

    if ($totalInstallSize -eq 0) {
        Write-Output " (no installations)"
    }

    Write-Output ""
    Write-Output "Version Index Cache:"
    Write-Output ""

    $cacheDir = Get-DevVmCacheDirectory
    $cacheFiles = @(Get-ChildItem -Path $cacheDir -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -eq '.cache' })

    if ($cacheFiles.Count -gt 0) {
        foreach ($file in $cacheFiles) {
            $age = (Get-Date) - $file.LastWriteTime
            $status = if ($age.TotalDays -lt 30) { "VALID" } else { "EXPIRED" }
            $expiryDate = $file.LastWriteTime.AddDays(30)
            $daysRemaining = [math]::Max(0, ($expiryDate - (Get-Date)).TotalDays)

            $metadataFile = $file.FullName -replace '\.cache$', '.metadata'
            $runtimeName = 'unknown'
            if (Test-Path $metadataFile) {
                try {
                    $metadata = Get-Content -Path $metadataFile -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
                    if ($metadata -and $metadata.Runtime) {
                        $runtimeName = $metadata.Runtime
                    }
                }
                catch {
                    Write-Verbose "Failed to read cache metadata from ${metadataFile}: $_"
                }
            }

            $fileSize = $file.Length / 1KB
            $totalCacheSize += $file.Length / 1MB

            Write-Output " $runtimeName : $([math]::Round($fileSize, 2)) KB [$status - expires in $([math]::Round($daysRemaining, 1)) days]"
        }
    }
    else {
        Write-Output " (cache empty)"
    }

    Write-Output ""
    Write-Output "Summary:"
    Write-Output "--------"
    Write-Output "Total Installations: $([math]::Round($totalInstallSize, 2)) MB"
    Write-Output "Cache Indexes: $([math]::Round($totalCacheSize, 2)) MB"
    Write-Output "Total: $([math]::Round($totalInstallSize + $totalCacheSize, 2)) MB"
    Write-Output ""
}