Support/Package/Schema/Eigenverft.Manifested.Sandbox.Package.DefinitionReference.ps1

<#
    Eigenverft.Manifested.Sandbox.Package.DefinitionReference
#>


function Get-PackageDefinitionPublication {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$DefinitionDocument,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionPath
    )

    if (-not $DefinitionDocument.PSObject.Properties['definitionPublication']) {
        throw "Package definition '$DefinitionPath' is missing required definitionPublication metadata."
    }

    $publication = $DefinitionDocument.definitionPublication
    foreach ($requiredProperty in @('publisherId', 'publisherName', 'definitionRevision', 'publishedAtUtc')) {
        if (-not $publication.PSObject.Properties[$requiredProperty] -or [string]::IsNullOrWhiteSpace([string]$publication.$requiredProperty)) {
            throw "Package definition '$DefinitionPath' is missing definitionPublication.$requiredProperty."
        }
    }

    return [pscustomobject]@{
        PublisherId        = [string]$publication.publisherId
        PublisherName      = [string]$publication.publisherName
        DefinitionRevision = [int]$publication.definitionRevision
        PublishedAtUtc     = [string]$publication.publishedAtUtc
    }
}

function Get-PackageLocalDefinitionPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Candidate', 'Assigned')]
        [string]$Role,

        [Parameter(Mandatory = $true)]
        [string]$LocalRepositoryRoot,

        [Parameter(Mandatory = $true)]
        [string]$PublisherId,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionId
    )

    $safePublisherId = ConvertTo-PackageSafePathSegment -Value $PublisherId
    $safeDefinitionId = ConvertTo-PackageSafePathSegment -Value $DefinitionId
    return [System.IO.Path]::GetFullPath((Join-Path (Join-Path (Join-Path $LocalRepositoryRoot $Role) $safePublisherId) ($safeDefinitionId + '.json')))
}

function Copy-PackageDefinitionToLocalDefinitionStore {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('Candidate', 'Assigned')]
        [string]$Role,

        [Parameter(Mandatory = $true)]
        [string]$SourcePath,

        [Parameter(Mandatory = $true)]
        [string]$LocalRepositoryRoot,

        [Parameter(Mandatory = $true)]
        [string]$PublisherId,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionId,

        [Parameter(Mandatory = $true)]
        [int]$DefinitionRevision
    )

    $targetPath = Get-PackageLocalDefinitionPath -Role $Role -LocalRepositoryRoot $LocalRepositoryRoot -PublisherId $PublisherId -DefinitionId $DefinitionId
    $targetDirectory = Split-Path -Parent $targetPath
    $null = New-Item -ItemType Directory -Path $targetDirectory -Force

    $sourceHash = Get-PackageFileSha256 -Path $SourcePath
    $targetExists = Test-Path -LiteralPath $targetPath -PathType Leaf
    if ($targetExists) {
        $targetHash = Get-PackageFileSha256 -Path $targetPath
        if ([string]::Equals($sourceHash, $targetHash, [System.StringComparison]::OrdinalIgnoreCase)) {
            return [pscustomobject]@{
                Path          = $targetPath
                Hash          = $targetHash
                Status        = 'Reused'
                RevisionReuse = $false
            }
        }

        try {
            $existingInfo = Read-PackageJsonDocument -Path $targetPath
            $existingPublication = Get-PackageDefinitionPublication -DefinitionDocument $existingInfo.Document -DefinitionPath $targetPath
            if ($existingPublication.DefinitionRevision -eq $DefinitionRevision) {
                Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Package definition publisher '{0}' reused definitionRevision '{1}' for definition '{2}' with different content; updating local {3} materialized copy." -f $PublisherId, $DefinitionRevision, $DefinitionId, $Role)
            }
        }
        catch {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Existing local {0} definition copy '{1}' could not be inspected before replacement: {2}" -f $Role, $targetPath, $_.Exception.Message)
        }
    }

    Copy-FileToPath -SourcePath $SourcePath -TargetPath $targetPath -Overwrite | Out-Null
    return [pscustomobject]@{
        Path          = $targetPath
        Hash          = Get-PackageFileSha256 -Path $targetPath
        Status        = if ($targetExists) { 'Updated' } else { 'Copied' }
        RevisionReuse = $targetExists
    }
}

function Get-PackageRepositoryDefinitionFilePaths {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$RepositoryRoot
    )

    if (-not (Test-Path -LiteralPath $RepositoryRoot -PathType Container)) {
        return @()
    }

    return @(Get-ChildItem -LiteralPath $RepositoryRoot -Filter '*.json' -File -Recurse | Select-Object -ExpandProperty FullName)
}

function Select-PackageDefinitionCandidatesFromRepository {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$RepositoryId,

        [Parameter(Mandatory = $true)]
        [psobject]$RepositorySource,

        [Parameter(Mandatory = $true)]
        [string]$RepositoryRoot,

        [Parameter(Mandatory = $true)]
        [string]$DefinitionId,

        [AllowNull()]
        [string]$PublisherId = $null
    )

    $candidates = New-Object System.Collections.Generic.List[object]
    foreach ($definitionPath in @(Get-PackageRepositoryDefinitionFilePaths -RepositoryRoot $RepositoryRoot)) {
        try {
            $definitionInfo = Read-PackageJsonDocument -Path $definitionPath
            $definition = $definitionInfo.Document
            if (-not $definition.PSObject.Properties['id'] -or
                -not [string]::Equals([string]$definition.id, $DefinitionId, [System.StringComparison]::OrdinalIgnoreCase)) {
                continue
            }

            $publication = Get-PackageDefinitionPublication -DefinitionDocument $definition -DefinitionPath $definitionPath
            if (-not [string]::IsNullOrWhiteSpace($PublisherId) -and
                -not [string]::Equals([string]$publication.PublisherId, $PublisherId, [System.StringComparison]::OrdinalIgnoreCase)) {
                continue
            }

            $candidates.Add([pscustomobject]@{
                RepositoryId       = $RepositoryId
                RepositoryKind     = [string]$RepositorySource.kind
                RepositoryRoot     = $RepositoryRoot
                DefinitionId       = [string]$definition.id
                DefinitionPath     = [System.IO.Path]::GetFullPath($definitionPath)
                PublisherId        = [string]$publication.PublisherId
                PublisherName      = [string]$publication.PublisherName
                DefinitionRevision = [int]$publication.DefinitionRevision
                PublishedAtUtc     = [string]$publication.PublishedAtUtc
                SourceHash         = Get-PackageFileSha256 -Path $definitionPath
            }) | Out-Null
        }
        catch {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Skipped package definition candidate '{0}' from repository '{1}': {2}" -f $definitionPath, $RepositoryId, $_.Exception.Message)
        }
    }

    return @($candidates.ToArray())
}

function Get-PackageEnabledTrustedRepositorySources {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$RepositoryInventoryDocument
    )

    $sources = @(
        foreach ($property in @($RepositoryInventoryDocument.repositorySources.PSObject.Properties)) {
            $source = $property.Value
            if (-not [bool]$source.enabled) {
                continue
            }
            if (-not [bool]$source.trusted) {
                continue
            }
            [pscustomobject]@{
                RepositoryId = [string]$property.Name
                Source       = $source
                SearchOrder  = if ($source.PSObject.Properties['searchOrder']) { [int]$source.searchOrder } else { 1000 }
            }
        }
    )

    return @($sources | Sort-Object -Property SearchOrder, RepositoryId)
}

function Resolve-PackageDefinitionReference {
<#
.SYNOPSIS
Resolves a Package definition identity to a local materialized definition path.
 
.DESCRIPTION
PackageRepositoryInventory.json is the source of truth for live definition
sources. Without RepositoryId, enabled and trusted repositories are searched by
searchOrder. Matching uses JSON id and optional definitionPublication.publisherId;
filenames are only storage detail. The selected live definition is copied to the
local Candidate definition store and that copy is used for Assigned execution.
#>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$RepositoryId = $null,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$DefinitionId,

        [AllowNull()]
        [string]$PublisherId = $null,

        [AllowNull()]
        [string]$ApplicationRootDirectory = $null,

        [AllowNull()]
        [string]$LocalRepositoryRoot = $null
    )

    $repositoryInventoryInfo = Get-PackageRepositoryInventoryInfo
    $candidateRows = New-Object System.Collections.Generic.List[object]
    $sourceRows = @()

    if ([string]::IsNullOrWhiteSpace($RepositoryId)) {
        $sourceRows = @(Get-PackageEnabledTrustedRepositorySources -RepositoryInventoryDocument $repositoryInventoryInfo.Document)
    }
    else {
        $repositoryProperty = Get-PackageRepositorySourceProperty -Document $repositoryInventoryInfo.Document -RepositoryId $RepositoryId
        if (-not $repositoryProperty) {
            throw "Package repository '$RepositoryId' was not found in '$($repositoryInventoryInfo.Path)'."
        }
        $sourceRows = @([pscustomobject]@{
            RepositoryId = [string]$RepositoryId
            Source       = $repositoryProperty.Value
            SearchOrder  = if ($repositoryProperty.Value.PSObject.Properties['searchOrder']) { [int]$repositoryProperty.Value.searchOrder } else { 1000 }
        })
    }

    foreach ($sourceRow in @($sourceRows)) {
        $repositoryRoot = Resolve-PackageRepositoryRootPath -RepositoryId $sourceRow.RepositoryId -Source $sourceRow.Source -ApplicationRootDirectory $ApplicationRootDirectory
        foreach ($candidate in @(Select-PackageDefinitionCandidatesFromRepository -RepositoryId $sourceRow.RepositoryId -RepositorySource $sourceRow.Source -RepositoryRoot $repositoryRoot -DefinitionId $DefinitionId -PublisherId $PublisherId)) {
            $candidate | Add-Member -MemberType NoteProperty -Name SearchOrder -Value ([int]$sourceRow.SearchOrder) -Force
            $candidateRows.Add($candidate) | Out-Null
        }
    }

    $candidates = @($candidateRows.ToArray() | Sort-Object -Property SearchOrder, RepositoryId, PublisherId, DefinitionRevision)
    if ($candidates.Count -eq 0) {
        $sourceText = if ([string]::IsNullOrWhiteSpace($RepositoryId)) { 'enabled trusted repositories' } else { "repository '$RepositoryId'" }
        $publisherText = if ([string]::IsNullOrWhiteSpace($PublisherId)) { '' } else { " and publisher '$PublisherId'" }
        throw "Package definition '$DefinitionId'$publisherText was not found in $sourceText."
    }

    $bestOrder = [int]$candidates[0].SearchOrder
    $sameOrder = @($candidates | Where-Object { [int]$_.SearchOrder -eq $bestOrder })
    if ($sameOrder.Count -gt 1) {
        $locations = (@($sameOrder) | ForEach-Object { "'$($_.RepositoryId):$($_.DefinitionPath)'" }) -join ', '
        throw "Package definition '$DefinitionId' is ambiguous at repository searchOrder '$bestOrder'. Matching candidates: $locations. Use -RepositoryId or -PublisherId."
    }

    $selected = $candidates[0]
    $selectedSourceRow = @($sourceRows | Where-Object { [string]::Equals([string]$_.RepositoryId, [string]$selected.RepositoryId, [System.StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1)[0]
    $resolvedLocalRepositoryRoot = if ([string]::IsNullOrWhiteSpace($LocalRepositoryRoot)) {
        Get-PackageDefaultLocalRepositoryRoot
    }
    else {
        [string]$LocalRepositoryRoot
    }
    $candidateCopy = Copy-PackageDefinitionToLocalDefinitionStore -Role 'Candidate' -SourcePath $selected.DefinitionPath -LocalRepositoryRoot $resolvedLocalRepositoryRoot -PublisherId $selected.PublisherId -DefinitionId $selected.DefinitionId -DefinitionRevision $selected.DefinitionRevision

    return [pscustomobject]@{
        RepositoryId              = [string]$selected.RepositoryId
        DefinitionId              = [string]$selected.DefinitionId
        DefinitionPath            = [System.IO.Path]::GetFullPath($candidateCopy.Path)
        SourceKind                = [string]$selected.RepositoryKind
        SourcePath                = [string]$selected.DefinitionPath
        SourceRepositoryRoot      = [string]$selected.RepositoryRoot
        SourceHash                = [string]$selected.SourceHash
        CandidatePath             = [System.IO.Path]::GetFullPath($candidateCopy.Path)
        CandidateHash             = [string]$candidateCopy.Hash
        SnapshotPath              = $null
        SnapshotHash              = $null
        ResolvedAtUtc             = [DateTime]::UtcNow.ToString('o')
        SnapshotFallback          = $false
        RepositoryInventoryPath   = $repositoryInventoryInfo.Path
        Trusted                   = $true
        TrustMode                 = if ($selectedSourceRow) { [string]$selectedSourceRow.Source.trustMode } else { $null }
        PublisherId               = [string]$selected.PublisherId
        PublisherName             = [string]$selected.PublisherName
        DefinitionRevision        = [int]$selected.DefinitionRevision
        PublishedAtUtc            = [string]$selected.PublishedAtUtc
        MaterializationStatus     = [string]$candidateCopy.Status
    }
}