Private/Import-PSDependLock.ps1

function Import-PSDependLock {
    <#
    .SYNOPSIS
    Read a lock file back into the same shape Resolve-PSDependLock produces.
 
    .DESCRIPTION
    Parses the JSON lock and returns an ordered hashtable with lockfileVersion,
    dependencies and packages. Throws when the file is not a PSDepend lock or
    was written by a newer, unsupported lockfileVersion.
 
    .PARAMETER Path
    Path to the lock file.
 
    .EXAMPLE
    Import-PSDependLock -Path .\requirements.lock.json
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    function ConvertTo-OrderedHashtable {
        param($InputObject)
        if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
            $table = [ordered]@{}
            foreach ($property in $InputObject.PSObject.Properties) {
                $table[$property.Name] = ConvertTo-OrderedHashtable $property.Value
            }
            return $table
        }
        $InputObject
    }


    function Assert-Table {
        param($Value, [string]$Description)

        if ($Value -isnot [System.Collections.IDictionary]) {
            throw "Lock file [$Path] is corrupt: $Description must be a JSON object"
        }
    }

    $raw = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
    try {
        $parsed = ConvertFrom-Json -InputObject $raw -ErrorAction Stop
    } catch {
        throw "Lock file [$Path] is not valid JSON: $_"
    }
    $lock = ConvertTo-OrderedHashtable $parsed

    if ($lock -isnot [System.Collections.IDictionary]) {
        throw "Lock file [$Path] is not a PSDepend lock file (the top level must be a JSON object)"
    }
    if (-not $lock.Contains('lockfileVersion') -or -not $lock.Contains('dependencies') -or -not $lock.Contains('packages')) {
        throw "Lock file [$Path] is not a PSDepend lock file (missing lockfileVersion, dependencies or packages)"
    }
    if ($lock.lockfileVersion -ne 1) {
        throw "Lock file [$Path] uses lockfileVersion [$($lock.lockfileVersion)]; this version of PSDepend supports lockfileVersion 1. Regenerate it with Update-PSDependLock"
    }

    Assert-Table -Value $lock.dependencies -Description 'dependencies'
    Assert-Table -Value $lock.packages -Description 'packages'

    foreach ($dependencyName in $lock.dependencies.Keys) {
        $entry = $lock.dependencies[$dependencyName]
        Assert-Table -Value $entry -Description "dependency [$dependencyName]"
        foreach ($requiredProperty in 'dependencyType', 'name', 'requested') {
            if (-not $entry.Contains($requiredProperty) -or [string]::IsNullOrWhiteSpace([string]$entry[$requiredProperty])) {
                throw "Lock file [$Path] is corrupt: dependency [$dependencyName] has no [$requiredProperty]"
            }
        }
        if ($entry.Contains('contextHash') -and [string]$entry.contextHash -notmatch '^[0-9a-f]{64}$') {
            throw "Lock file [$Path] is corrupt: dependency [$dependencyName] has an invalid resolution context hash"
        }
        if ($entry.Contains('resolved')) {
            $expectedKey = "$($entry.dependencyType)::$($entry.name)"
            if ($entry.resolved -cne $expectedKey) {
                throw "Lock file [$Path] is corrupt: dependency [$dependencyName] has resolved key [$($entry.resolved)]; expected [$expectedKey]"
            }
            if (-not $lock.packages.Contains($entry.resolved)) {
                throw "Lock file [$Path] is corrupt: dependency [$dependencyName] references package [$($entry.resolved)], which is not locked"
            }
        }
    }

    foreach ($packageKey in $lock.packages.Keys) {
        if ([string]$packageKey -notmatch '^([^:]+)::([A-Za-z0-9@][A-Za-z0-9._/@-]*)$') {
            throw "Lock file [$Path] is corrupt: package key [$packageKey] is not a valid DependencyType::Name"
        }
        $dependencyType = $Matches[1]
        $package = $lock.packages[$packageKey]
        Assert-Table -Value $package -Description "package [$packageKey]"
        if (-not $package.Contains('version') -or -not (Test-PSDependExactVersion -Version ([string]$package.version))) {
            throw "Lock file [$Path] is corrupt: package [$packageKey] must have an exact version"
        }
        if (-not $package.Contains('dependencies')) {
            throw "Lock file [$Path] is corrupt: package [$packageKey] has no dependencies object"
        }
        Assert-Table -Value $package.dependencies -Description "dependencies of package [$packageKey]"
        foreach ($childName in $package.dependencies.Keys) {
            if ([string]$childName -notmatch '^[A-Za-z0-9@][A-Za-z0-9._/@-]*$') {
                throw "Lock file [$Path] is corrupt: package [$packageKey] has invalid dependency name [$childName]"
            }
            $childKey = "${dependencyType}::$childName"
            if (-not $lock.packages.Contains($childKey)) {
                throw "Lock file [$Path] is corrupt: package [$packageKey] references [$childKey], which is not locked"
            }
        }
    }

    $lock
}