Support/Package/Execution/Eigenverft.Manifested.Sandbox.Package.Npm.ps1

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


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

    $packageRoot = Get-PackageRootFromInventoryPath -PackageAssignmentInventoryFilePath ([string]$PackageResult.PackageConfig.PackageAssignmentInventoryFilePath)
    return ([System.IO.Path]::GetFullPath((Join-Path $packageRoot 'Configuration\External\npm\npmrc')))
}

function New-PackageNpmCacheDirectory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $packageRoot = Get-PackageRootFromInventoryPath -PackageAssignmentInventoryFilePath ([string]$PackageResult.PackageConfig.PackageAssignmentInventoryFilePath)
    $segments = @(
        'Caches'
        'npm'
        [string]$PackageResult.DefinitionId
        [string]$PackageResult.Package.releaseTrack
        [string]$PackageResult.Package.version
        [string]$PackageResult.Package.artifactDistributionVariant
    ) | ForEach-Object {
        ([string]$_).Trim() -replace '[\\/:\*\?"<>\|]', '-'
    }

    $cacheDirectory = [System.IO.Path]::GetFullPath((Join-Path $packageRoot ($segments -join '\')))
    $null = New-Item -ItemType Directory -Path $cacheDirectory -Force
    return $cacheDirectory
}

function Initialize-PackageNpmGlobalConfig {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$GlobalConfigPath
    )

    $resolvedPath = [System.IO.Path]::GetFullPath($GlobalConfigPath)
    $directoryPath = Split-Path -Parent $resolvedPath
    if (-not [string]::IsNullOrWhiteSpace($directoryPath)) {
        $null = New-Item -ItemType Directory -Path $directoryPath -Force
    }

    if (-not (Test-Path -LiteralPath $resolvedPath -PathType Leaf)) {
        Set-Content -LiteralPath $resolvedPath -Value '' -Encoding UTF8
    }

    return $resolvedPath
}

function Resolve-PackageNpmInstallerCommand {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $install = Get-PackageAssignedInstallOperation -Release $PackageResult.Package
    if (-not $install) {
        throw "Package npm global package install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install on the selected release."
    }
    if (-not $install.PSObject.Properties['installerCommand'] -or [string]::IsNullOrWhiteSpace([string]$install.installerCommand)) {
        throw "Package npm global package install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install.installerCommand."
    }

    $installerCommand = [string]$install.installerCommand
    $dependencyInfo = Resolve-PackageDependencyCommandPath -PackageResult $PackageResult -CommandName $installerCommand
    Write-PackageExecutionMessage -Message ("[STATE] Installer command ready: definition='{0}', command='{1}', path='{2}'." -f $dependencyInfo.DefinitionId, $dependencyInfo.Command, $dependencyInfo.CommandPath)

    return $dependencyInfo
}

function Resolve-PackageNpmNodeCommandPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

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

    $npmDirectory = Split-Path -Parent ([System.IO.Path]::GetFullPath($NpmCommandPath))
    foreach ($candidateName in @('node.exe', 'node.cmd', 'node.bat')) {
        $candidatePath = Join-Path $npmDirectory $candidateName
        if (Test-Path -LiteralPath $candidatePath -PathType Leaf) {
            return ([System.IO.Path]::GetFullPath($candidatePath))
        }
    }

    try {
        $dependencyInfo = Resolve-PackageDependencyCommandPath -PackageResult $PackageResult -CommandName 'node'
        if ($dependencyInfo -and -not [string]::IsNullOrWhiteSpace([string]$dependencyInfo.CommandPath)) {
            return ([System.IO.Path]::GetFullPath([string]$dependencyInfo.CommandPath))
        }
    }
    catch {
        # Fall through to PATH lookup; npm materialization still reports a clear error below.
    }

    $pathCommand = Get-Command -Name 'node.exe' -ErrorAction SilentlyContinue
    if ($pathCommand -and -not [string]::IsNullOrWhiteSpace([string]$pathCommand.Source)) {
        return ([System.IO.Path]::GetFullPath([string]$pathCommand.Source))
    }

    throw "Package npm materialization for '$($PackageResult.PackageId)' requires node.exe to parse npm lock metadata."
}

function Test-PackageNpmMaterializedInstallKind {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [psobject]$Package
    )

    $install = Get-PackageAssignedInstallOperation -Release $Package
    return ($install -and
        $install.PSObject.Properties['kind'] -and
        [string]::Equals([string]$install.kind, 'npmMaterializedInstallGlobalPackage', [System.StringComparison]::OrdinalIgnoreCase))
}

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

    $install = Get-PackageAssignedInstallOperation -Release $PackageResult.Package
    if (-not $install -or -not $install.PSObject.Properties['packageSpec'] -or [string]::IsNullOrWhiteSpace([string]$install.packageSpec)) {
        throw "Package npm materialized install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install.packageSpec."
    }

    return (Resolve-PackageTemplateText -Text ([string]$install.packageSpec) -PackageConfig $PackageResult.PackageConfig -Package $PackageResult.Package)
}

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

    if ([string]::IsNullOrWhiteSpace([string]$PackageResult.PackageFileStagingDirectory)) {
        throw "Package npm materialization for '$($PackageResult.PackageId)' requires a package file staging directory."
    }

    return ([System.IO.Path]::GetFullPath((Join-Path ([string]$PackageResult.PackageFileStagingDirectory) 'npm-materialized')))
}

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

    return ([System.IO.Path]::GetFullPath((Join-Path $Directory 'npm-materialization.json')))
}

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

    switch -Regex ([string]$PackageConfig.Platform) {
        '^(windows|win32)$' { return 'win32' }
        '^(macos|darwin)$' { return 'darwin' }
        '^linux$' { return 'linux' }
        default { return ([string]$PackageConfig.Platform).ToLowerInvariant() }
    }
}

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

    switch -Regex ([string]$PackageConfig.Architecture) {
        '^(x64|amd64)$' { return 'x64' }
        '^(arm64|aarch64)$' { return 'arm64' }
        '^(x86|ia32)$' { return 'ia32' }
        default { return ([string]$PackageConfig.Architecture).ToLowerInvariant() }
    }
}

function Test-PackageNpmIntegrity {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

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

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
        return $false
    }

    foreach ($token in @(([string]$Integrity) -split '\s+')) {
        if ([string]::IsNullOrWhiteSpace($token) -or $token -notmatch '^(?<algorithm>sha1|sha256|sha384|sha512)-(?<value>.+)$') {
            continue
        }

        $algorithm = $Matches.algorithm.ToUpperInvariant()
        $expected = $Matches.value
        $hashAlgorithm = [System.Security.Cryptography.HashAlgorithm]::Create($algorithm)
        if (-not $hashAlgorithm) {
            continue
        }

        $stream = [System.IO.File]::OpenRead([System.IO.Path]::GetFullPath($Path))
        try {
            $actual = [Convert]::ToBase64String($hashAlgorithm.ComputeHash($stream))
        }
        finally {
            $stream.Dispose()
            $hashAlgorithm.Dispose()
        }

        if ([string]::Equals($actual, $expected, [System.StringComparison]::Ordinal)) {
            return $true
        }
    }

    return $false
}

function ConvertTo-PackageNpmMaterializedPackageList {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$LockFilePath,

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

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

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

    $scriptPath = [System.IO.Path]::GetFullPath((Join-Path (Split-Path -Parent $LockFilePath) 'select-materialized-npm-packages.js'))
    $script = @'
const fs = require('fs');
const lockPath = process.argv[2];
const npmPlatform = process.argv[3];
const npmArchitecture = process.argv[4];
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
 
function allowed(list, value) {
  if (!Array.isArray(list) || list.length === 0) return true;
  const positives = list.filter((entry) => !String(entry).startsWith('!')).map(String);
  const negatives = list.filter((entry) => String(entry).startsWith('!')).map((entry) => String(entry).substring(1));
  if (negatives.includes(value)) return false;
  if (positives.length > 0 && !positives.includes(value)) return false;
  return true;
}
 
function packageName(lockKey) {
  const pieces = String(lockKey).split('node_modules/');
  return pieces[pieces.length - 1].replace(/\/$/, '');
}
 
function fileNameFromResolved(resolved) {
  const url = new URL(resolved);
  const parts = url.pathname.split('/');
  return decodeURIComponent(parts[parts.length - 1]);
}
 
const packages = [];
for (const [lockKey, entry] of Object.entries(lock.packages || {})) {
  if (!String(lockKey).includes('node_modules/')) continue;
  if (!entry || !entry.version || !entry.resolved || !entry.integrity) continue;
  if (!allowed(entry.os, npmPlatform) || !allowed(entry.cpu, npmArchitecture)) continue;
  packages.push({
    name: packageName(lockKey),
    version: String(entry.version),
    resolved: String(entry.resolved),
    integrity: String(entry.integrity),
    fileName: fileNameFromResolved(String(entry.resolved)),
    optional: !!entry.optional,
    os: Array.isArray(entry.os) ? entry.os.map(String) : [],
    cpu: Array.isArray(entry.cpu) ? entry.cpu.map(String) : []
  });
}
 
console.log(JSON.stringify(packages));
'@

    Set-Content -LiteralPath $scriptPath -Value $script -Encoding UTF8
    $json = & $NodeCommandPath $scriptPath $LockFilePath $NpmPlatform $NpmArchitecture
    $exitCode = $LASTEXITCODE
    if ($null -eq $exitCode) {
        $exitCode = 0
    }
    if ($exitCode -ne 0) {
        throw "Node lock parser failed with exit code $exitCode."
    }
    if ([string]::IsNullOrWhiteSpace(($json -join ''))) {
        throw "Node lock parser did not return npm package metadata."
    }

    return @($json | ConvertFrom-Json)
}

function ConvertFrom-PackageNpmJsonOutput {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [object[]]$Output,

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

    $text = @($Output | Where-Object { $null -ne $_ }) -join [Environment]::NewLine
    if ([string]::IsNullOrWhiteSpace($text)) {
        throw "$OperationName did not return JSON output."
    }

    try {
        return @($text | ConvertFrom-Json)
    }
    catch {
        $start = $text.IndexOf('[')
        $end = $text.LastIndexOf(']')
        if ($start -ge 0 -and $end -gt $start) {
            try {
                return @($text.Substring($start, ($end - $start + 1)) | ConvertFrom-Json)
            }
            catch {
                # Report the original parse failure below; the sliced retry is only a noise guard.
            }
        }

        throw "$OperationName did not return parseable JSON output. $($_.Exception.Message)"
    }
}

function Save-PackageNpmMaterializedPackageWithNpmPack {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Package,

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

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

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

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

    $packageName = [string]$Package.name
    $packageVersion = [string]$Package.version
    if ([string]::IsNullOrWhiteSpace($packageName) -or [string]::IsNullOrWhiteSpace($packageVersion)) {
        throw "npm materialization package metadata must include name and version."
    }

    $knownFileName = [string]$Package.fileName
    $knownIntegrity = [string]$Package.integrity
    if (-not [string]::IsNullOrWhiteSpace($knownFileName) -and -not [string]::IsNullOrWhiteSpace($knownIntegrity)) {
        $knownTargetPath = [System.IO.Path]::GetFullPath((Join-Path $TargetDirectory $knownFileName))
        if (Test-PackageNpmIntegrity -Path $knownTargetPath -Integrity $knownIntegrity) {
            return $Package
        }
    }

    $packageSpec = '{0}@{1}' -f $packageName, $packageVersion
    $packArguments = @('pack', $packageSpec, '--pack-destination', $TargetDirectory, '--json', '--cache', $CacheDirectory)
    $packArguments += @(Get-NpmGlobalConfigArguments -GlobalConfigPath $GlobalConfigPath)

    Write-PackageExecutionMessage -Message ("[STATE] Packing npm materialized package '{0}'." -f $packageSpec)
    $null = New-Item -ItemType Directory -Path $TargetDirectory -Force
    Push-Location $TargetDirectory
    try {
        $packOutput = & $NpmCommandPath @packArguments
        $exitCode = $LASTEXITCODE
        if ($null -eq $exitCode) {
            $exitCode = 0
        }
    }
    finally {
        Pop-Location
    }

    if ($exitCode -ne 0) {
        throw "npm pack for '$packageSpec' failed with exit code $exitCode."
    }

    $packItems = @(ConvertFrom-PackageNpmJsonOutput -Output $packOutput -OperationName "npm pack for '$packageSpec'")
    if ($packItems.Count -eq 0) {
        throw "npm pack for '$packageSpec' returned no package metadata."
    }

    $packItem = @($packItems | Where-Object {
            [string]::Equals([string]$_.name, $packageName, [System.StringComparison]::OrdinalIgnoreCase) -and
            [string]::Equals([string]$_.version, $packageVersion, [System.StringComparison]::OrdinalIgnoreCase)
        } | Select-Object -First 1)
    if ($packItem.Count -eq 0) {
        $packItem = @($packItems | Select-Object -First 1)
    }

    $fileName = [string]$packItem[0].filename
    if ([string]::IsNullOrWhiteSpace($fileName)) {
        $fileName = $knownFileName
    }
    if ([string]::IsNullOrWhiteSpace($fileName)) {
        throw "npm pack for '$packageSpec' did not report a tarball filename."
    }

    $integrity = [string]$packItem[0].integrity
    if ([string]::IsNullOrWhiteSpace($integrity)) {
        $integrity = $knownIntegrity
    }
    if ([string]::IsNullOrWhiteSpace($integrity)) {
        throw "npm pack for '$packageSpec' did not report integrity metadata."
    }

    $targetPath = [System.IO.Path]::GetFullPath((Join-Path $TargetDirectory $fileName))
    if (-not (Test-PackageNpmIntegrity -Path $targetPath -Integrity $integrity)) {
        throw "npm pack output '$fileName' did not satisfy integrity metadata."
    }

    return [pscustomobject]@{
        name      = $packageName
        version   = $packageVersion
        resolved  = [string]$Package.resolved
        integrity = $integrity
        fileName  = $fileName
        optional  = [bool]$Package.optional
        os        = @($Package.os)
        cpu       = @($Package.cpu)
    }
}

function Test-PackageNpmMaterializationDirectory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Directory,

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

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

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

    $manifestPath = Get-PackageNpmMaterializationManifestPath -Directory $Directory
    if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
        return $null
    }

    try {
        $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
    }
    catch {
        return $null
    }

    if (-not $manifest -or
        -not [string]::Equals([string]$manifest.packageSpec, $PackageSpec, [System.StringComparison]::OrdinalIgnoreCase) -or
        -not [string]::Equals([string]$manifest.npmPlatform, $NpmPlatform, [System.StringComparison]::OrdinalIgnoreCase) -or
        -not [string]::Equals([string]$manifest.npmArchitecture, $NpmArchitecture, [System.StringComparison]::OrdinalIgnoreCase)) {
        return $null
    }

    $tarballPaths = New-Object System.Collections.Generic.List[string]
    foreach ($package in @($manifest.packages)) {
        if (-not $package.PSObject.Properties['fileName'] -or
            [string]::IsNullOrWhiteSpace([string]$package.fileName) -or
            -not $package.PSObject.Properties['integrity'] -or
            [string]::IsNullOrWhiteSpace([string]$package.integrity)) {
            return $null
        }

        $tarballPath = [System.IO.Path]::GetFullPath((Join-Path $Directory ([string]$package.fileName)))
        if (-not (Test-PackageNpmIntegrity -Path $tarballPath -Integrity ([string]$package.integrity))) {
            return $null
        }
        $tarballPaths.Add($tarballPath) | Out-Null
    }

    if ($tarballPaths.Count -eq 0) {
        return $null
    }

    return [pscustomobject]@{
        ManifestPath = [System.IO.Path]::GetFullPath($manifestPath)
        Manifest     = $manifest
        TarballPaths = @($tarballPaths.ToArray())
    }
}

function Copy-PackageNpmMaterializationDirectory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$SourceDirectory,

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

        [Parameter(Mandatory = $true)]
        [psobject]$Materialization
    )

    $null = New-Item -ItemType Directory -Path $TargetDirectory -Force
    $sourceManifestPath = Get-PackageNpmMaterializationManifestPath -Directory $SourceDirectory
    $targetManifestPath = Get-PackageNpmMaterializationManifestPath -Directory $TargetDirectory
    $null = Copy-FileToPath -SourcePath $sourceManifestPath -TargetPath $targetManifestPath -Overwrite

    $copiedTarballs = New-Object System.Collections.Generic.List[string]
    foreach ($package in @($Materialization.Manifest.packages)) {
        $sourcePath = [System.IO.Path]::GetFullPath((Join-Path $SourceDirectory ([string]$package.fileName)))
        $targetPath = [System.IO.Path]::GetFullPath((Join-Path $TargetDirectory ([string]$package.fileName)))
        $null = Copy-FileToPath -SourcePath $sourcePath -TargetPath $targetPath -Overwrite
        $copiedTarballs.Add($targetPath) | Out-Null
    }

    return [pscustomobject]@{
        ManifestPath = $targetManifestPath
        Manifest     = $Materialization.Manifest
        TarballPaths = @($copiedTarballs.ToArray())
    }
}

function Find-PackageNpmMaterializationInDepots {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

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

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

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

    foreach ($depotSource in @(Get-PackagePackageDepotSources -PackageConfig $PackageResult.PackageConfig)) {
        if ([string]::IsNullOrWhiteSpace([string]$depotSource.basePath)) {
            continue
        }

        $candidateDirectory = [System.IO.Path]::GetFullPath((Join-Path ([string]$depotSource.basePath) ([string]$PackageResult.PackageDepotRelativeDirectory)))
        $materialization = Test-PackageNpmMaterializationDirectory -Directory $candidateDirectory -PackageSpec $PackageSpec -NpmPlatform $NpmPlatform -NpmArchitecture $NpmArchitecture
        if ($materialization) {
            $materialization | Add-Member -MemberType NoteProperty -Name SourceId -Value ([string]$depotSource.id) -Force
            $materialization | Add-Member -MemberType NoteProperty -Name SourceDirectory -Value $candidateDirectory -Force
            return $materialization
        }
    }

    return $null
}

function New-PackageNpmMaterializationManifest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

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

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

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

        [Parameter(Mandatory = $true)]
        [object[]]$Packages
    )

    return [pscustomobject]@{
        schemaVersion    = '1.0'
        packageSpec      = $PackageSpec
        definitionId     = [string]$PackageResult.DefinitionId
        packageId        = [string]$PackageResult.PackageId
        packageVersion   = [string]$PackageResult.Package.version
        releaseTrack     = [string]$PackageResult.Package.releaseTrack
        artifactDistributionVariant = [string]$PackageResult.Package.artifactDistributionVariant
        npmPlatform      = $NpmPlatform
        npmArchitecture  = $NpmArchitecture
        generatedAtUtc   = [DateTime]::UtcNow.ToString('o')
        packages         = @($Packages)
    }
}

function New-PackageNpmMaterializationFromRegistry {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

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

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

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

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

    $installerCommandInfo = Resolve-PackageNpmInstallerCommand -PackageResult $PackageResult
    $nodeCommandPath = Resolve-PackageNpmNodeCommandPath -PackageResult $PackageResult -NpmCommandPath ([string]$installerCommandInfo.CommandPath)
    $cacheDirectory = New-PackageNpmCacheDirectory -PackageResult $PackageResult
    $globalConfigPath = Initialize-PackageNpmGlobalConfig -GlobalConfigPath (Get-PackageNpmGlobalConfigPath -PackageResult $PackageResult)
    $resolutionDirectory = [System.IO.Path]::GetFullPath((Join-Path $TargetDirectory '.npm-resolution'))
    Remove-PathIfExists -Path $resolutionDirectory | Out-Null
    $null = New-Item -ItemType Directory -Path $resolutionDirectory -Force
    $null = New-Item -ItemType Directory -Path $TargetDirectory -Force

    $lockArguments = @('install', '--package-lock-only', '--ignore-scripts', '--no-audit', '--no-fund', '--cache', $cacheDirectory)
    $lockArguments += @(Get-NpmGlobalConfigArguments -GlobalConfigPath $globalConfigPath)
    $lockArguments += $PackageSpec

    Write-PackageExecutionMessage -Message ("[STATE] Materializing npm package spec '{0}'." -f $PackageSpec)
    Push-Location $resolutionDirectory
    try {
        & ([string]$installerCommandInfo.CommandPath) @lockArguments
        $exitCode = $LASTEXITCODE
        if ($null -eq $exitCode) {
            $exitCode = 0
        }
    }
    finally {
        Pop-Location
    }

    if ($exitCode -ne 0) {
        throw "npm metadata resolution for '$PackageSpec' failed with exit code $exitCode."
    }

    $lockFilePath = Join-Path $resolutionDirectory 'package-lock.json'
    if (-not (Test-Path -LiteralPath $lockFilePath -PathType Leaf)) {
        throw "npm metadata resolution for '$PackageSpec' did not produce package-lock.json."
    }

    $packages = @(ConvertTo-PackageNpmMaterializedPackageList -LockFilePath $lockFilePath -NodeCommandPath $nodeCommandPath -NpmPlatform $NpmPlatform -NpmArchitecture $NpmArchitecture)
    if ($packages.Count -eq 0) {
        throw "npm metadata resolution for '$PackageSpec' did not produce any materializable packages."
    }

    $materializedPackages = New-Object System.Collections.Generic.List[object]
    foreach ($package in $packages) {
        $materializedPackage = Save-PackageNpmMaterializedPackageWithNpmPack `
            -Package $package `
            -NpmCommandPath ([string]$installerCommandInfo.CommandPath) `
            -CacheDirectory $cacheDirectory `
            -GlobalConfigPath $globalConfigPath `
            -TargetDirectory $TargetDirectory
        $materializedPackages.Add($materializedPackage) | Out-Null
    }

    $manifest = New-PackageNpmMaterializationManifest -PackageResult $PackageResult -PackageSpec $PackageSpec -NpmPlatform $NpmPlatform -NpmArchitecture $NpmArchitecture -Packages @($materializedPackages.ToArray())
    $manifestPath = Get-PackageNpmMaterializationManifestPath -Directory $TargetDirectory
    $manifest | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $manifestPath -Encoding UTF8

    $materialization = Test-PackageNpmMaterializationDirectory -Directory $TargetDirectory -PackageSpec $PackageSpec -NpmPlatform $NpmPlatform -NpmArchitecture $NpmArchitecture
    if (-not $materialization) {
        throw "npm materialization for '$PackageSpec' could not be validated after download."
    }

    return $materialization
}

function Invoke-PackageNpmMaterializationDepotDistribution {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

        [Parameter(Mandatory = $true)]
        [psobject]$Materialization
    )

    $mode = if ($PackageResult.PackageConfig.PSObject.Properties['DepotDistributionMode'] -and
        -not [string]::IsNullOrWhiteSpace([string]$PackageResult.PackageConfig.DepotDistributionMode)) {
        [string]$PackageResult.PackageConfig.DepotDistributionMode
    }
    else {
        'packageFocused'
    }

    $files = New-Object System.Collections.Generic.List[object]
    $files.Add([pscustomobject]@{ FileName = 'npm-materialization.json'; SourcePath = [string]$Materialization.ManifestPath }) | Out-Null
    foreach ($package in @($Materialization.Manifest.packages)) {
        $sourcePath = [System.IO.Path]::GetFullPath((Join-Path (Split-Path -Parent ([string]$Materialization.ManifestPath)) ([string]$package.fileName)))
        $files.Add([pscustomobject]@{ FileName = [string]$package.fileName; SourcePath = $sourcePath }) | Out-Null
    }

    $actions = New-Object System.Collections.Generic.List[object]
    if ([string]::Equals($mode, 'disabled', [System.StringComparison]::OrdinalIgnoreCase)) {
        return [pscustomobject]@{ Mode = $mode; Status = 'Skipped'; Reason = 'DisabledByPolicy'; Actions = @(); CopiedCount = 0; FailedCount = 0; SkippedCount = 0 }
    }

    foreach ($mirrorSource in @(Get-PackageDepotDistributionTargets -PackageConfig $PackageResult.PackageConfig)) {
        foreach ($file in @($files.ToArray())) {
            if (-not [string]::Equals([string]$mirrorSource.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Skip'; Status = 'Skipped'; Reason = 'UnsupportedDepotKind'; SourcePath = [string]$file.SourcePath; TargetPath = $null; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $null }) | Out-Null
                continue
            }
            if ([string]::IsNullOrWhiteSpace([string]$mirrorSource.basePath)) {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Skip'; Status = 'Skipped'; Reason = 'MissingBasePath'; SourcePath = [string]$file.SourcePath; TargetPath = $null; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $null }) | Out-Null
                continue
            }

            $targetDirectory = [System.IO.Path]::GetFullPath((Join-Path ([string]$mirrorSource.basePath) ([string]$PackageResult.PackageDepotRelativeDirectory)))
            $targetPath = [System.IO.Path]::GetFullPath((Join-Path $targetDirectory ([string]$file.FileName)))
            $sourceFullPath = [System.IO.Path]::GetFullPath([string]$file.SourcePath)
            if ([string]::Equals($sourceFullPath, $targetPath, [System.StringComparison]::OrdinalIgnoreCase)) {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Skip'; Status = 'Skipped'; Reason = 'SourceIsTarget'; SourcePath = $sourceFullPath; TargetPath = $targetPath; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $null }) | Out-Null
                continue
            }

            $match = Test-PackageDepotDistributionFileMatches -SourcePath $sourceFullPath -TargetPath $targetPath
            if ($match.Matches) {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Skip'; Status = 'Skipped'; Reason = [string]$match.Reason; SourcePath = $sourceFullPath; TargetPath = $targetPath; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $null }) | Out-Null
                continue
            }
            if ([string]::Equals($mode, 'packageFocused', [System.StringComparison]::OrdinalIgnoreCase) -and
                -not [string]::Equals([string]$match.Reason, 'Missing', [System.StringComparison]::OrdinalIgnoreCase)) {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Skip'; Status = 'Skipped'; Reason = 'DifferentTargetPreservedByPackageFocusedPolicy'; SourcePath = $sourceFullPath; TargetPath = $targetPath; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = [string]$match.Reason }) | Out-Null
                continue
            }

            try {
                if ($mirrorSource.ensureExists) {
                    $null = New-Item -ItemType Directory -Path $targetDirectory -Force
                }
                $null = Copy-FileToPath -SourcePath $sourceFullPath -TargetPath $targetPath -Overwrite
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Copy'; Status = 'Copied'; Reason = [string]$match.Reason; SourcePath = $sourceFullPath; TargetPath = $targetPath; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $null }) | Out-Null
            }
            catch {
                $actions.Add([pscustomobject]@{ DepotId = [string]$mirrorSource.id; FileName = [string]$file.FileName; Action = 'Copy'; Status = 'Failed'; Reason = [string]$match.Reason; SourcePath = $sourceFullPath; TargetPath = $targetPath; EnsureExists = [bool]$mirrorSource.ensureExists; ErrorMessage = $_.Exception.Message }) | Out-Null
            }
        }
    }

    $copiedCount = @($actions.ToArray() | Where-Object { [string]::Equals([string]$_.Status, 'Copied', [System.StringComparison]::OrdinalIgnoreCase) }).Count
    $failedCount = @($actions.ToArray() | Where-Object { [string]::Equals([string]$_.Status, 'Failed', [System.StringComparison]::OrdinalIgnoreCase) }).Count
    $skippedCount = @($actions.ToArray() | Where-Object { [string]::Equals([string]$_.Status, 'Skipped', [System.StringComparison]::OrdinalIgnoreCase) }).Count

    return [pscustomobject]@{
        Mode         = $mode
        Status       = if ($actions.Count -eq 0) { 'Skipped' } else { 'Planned' }
        Reason       = if ($actions.Count -eq 0) { 'NoDepotTargets' } else { $null }
        Actions      = @($actions.ToArray())
        CopiedCount  = $copiedCount
        FailedCount  = $failedCount
        SkippedCount = $skippedCount
    }
}

function Invoke-PackageNpmMaterialization {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    if (-not (Test-PackageNpmMaterializedInstallKind -Package $PackageResult.Package)) {
        return $PackageResult
    }

    $packageSpec = Get-PackageNpmResolvedPackageSpec -PackageResult $PackageResult
    $npmPlatform = Get-PackageNpmPlatform -PackageConfig $PackageResult.PackageConfig
    $npmArchitecture = Get-PackageNpmArchitecture -PackageConfig $PackageResult.PackageConfig
    $stageDirectory = Get-PackageNpmMaterializationDirectory -PackageResult $PackageResult
    Remove-PathIfExists -Path $stageDirectory | Out-Null

    $depotMaterialization = Find-PackageNpmMaterializationInDepots -PackageResult $PackageResult -PackageSpec $packageSpec -NpmPlatform $npmPlatform -NpmArchitecture $npmArchitecture
    if ($depotMaterialization) {
        $copied = Copy-PackageNpmMaterializationDirectory -SourceDirectory ([string]$depotMaterialization.SourceDirectory) -TargetDirectory $stageDirectory -Materialization $depotMaterialization
        $PackageResult | Add-Member -MemberType NoteProperty -Name NpmMaterialization -Value ([pscustomobject]@{
            Success         = $true
            Status          = 'HydratedFromDepot'
            PackageSpec     = $packageSpec
            NpmPlatform     = $npmPlatform
            NpmArchitecture = $npmArchitecture
            SourceId        = [string]$depotMaterialization.SourceId
            ManifestPath    = [string]$copied.ManifestPath
            Manifest        = $copied.Manifest
            TarballPaths    = @($copied.TarballPaths)
            DepotDistribution = $null
        }) -Force
        Write-PackageExecutionMessage -Message ("[ACTION] Hydrated npm materialization from depot '{0}'." -f [string]$depotMaterialization.SourceId)
    }
    else {
        $materialization = New-PackageNpmMaterializationFromRegistry -PackageResult $PackageResult -PackageSpec $packageSpec -NpmPlatform $npmPlatform -NpmArchitecture $npmArchitecture -TargetDirectory $stageDirectory
        $PackageResult | Add-Member -MemberType NoteProperty -Name NpmMaterialization -Value ([pscustomobject]@{
            Success         = $true
            Status          = 'MaterializedFromRegistry'
            PackageSpec     = $packageSpec
            NpmPlatform     = $npmPlatform
            NpmArchitecture = $npmArchitecture
            SourceId        = 'npmRegistry'
            ManifestPath    = [string]$materialization.ManifestPath
            Manifest        = $materialization.Manifest
            TarballPaths    = @($materialization.TarballPaths)
            DepotDistribution = $null
        }) -Force
        Write-PackageExecutionMessage -Message ("[ACTION] Materialized npm package spec '{0}' with {1} tarball(s)." -f $packageSpec, @($materialization.TarballPaths).Count)
    }

    $distribution = Invoke-PackageNpmMaterializationDepotDistribution -PackageResult $PackageResult -Materialization $PackageResult.NpmMaterialization
    $PackageResult.NpmMaterialization.DepotDistribution = $distribution
    Write-PackageExecutionMessage -Message ("[STATE] npm materialization depot distribution completed: mode='{0}', copied={1}, skipped={2}, failed={3}." -f [string]$distribution.Mode, [int]$distribution.CopiedCount, [int]$distribution.SkippedCount, [int]$distribution.FailedCount)

    return $PackageResult
}

function Install-PackageNpmPackage {
<#
.SYNOPSIS
Installs an exact npm package spec into a staged Package-owned prefix.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $install = Get-PackageAssignedInstallOperation -Release $PackageResult.Package
    if (-not $install) {
        throw "Package npm global package install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install on the selected release."
    }
    if (-not $install.PSObject.Properties['packageSpec'] -or [string]::IsNullOrWhiteSpace([string]$install.packageSpec)) {
        throw "Package npm global package install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install.packageSpec."
    }

    $packageSpec = Resolve-PackageTemplateText -Text ([string]$install.packageSpec) -PackageConfig $PackageResult.PackageConfig -Package $PackageResult.Package
    $installerCommandInfo = Resolve-PackageNpmInstallerCommand -PackageResult $PackageResult
    $cacheDirectory = New-PackageNpmCacheDirectory -PackageResult $PackageResult
    $globalConfigPath = Initialize-PackageNpmGlobalConfig -GlobalConfigPath (Get-PackageNpmGlobalConfigPath -PackageResult $PackageResult)
    if ([string]::IsNullOrWhiteSpace([string]$PackageResult.PackageInstallStageDirectory)) {
        throw "Package npm global package install for '$($PackageResult.PackageId)' requires a package install stage directory."
    }
    $stagePath = [System.IO.Path]::GetFullPath([string]$PackageResult.PackageInstallStageDirectory)
    Remove-PathIfExists -Path $stagePath | Out-Null
    $null = New-Item -ItemType Directory -Path $stagePath -Force
    $stagePromoted = $false

    $commandArguments = @('install', '-g', '--prefix', $stagePath, '--cache', $cacheDirectory)
    $commandArguments += @(Get-NpmGlobalConfigArguments -GlobalConfigPath $globalConfigPath)
    $commandArguments += $packageSpec

    Write-PackageExecutionMessage -Message ("[STATE] npm global package install:")
    Write-PackageExecutionMessage -Message ("[PATH] npm command: {0}" -f $installerCommandInfo.CommandPath)
    Write-PackageExecutionMessage -Message ("[PATH] npm stage: {0}" -f $stagePath)
    Write-PackageExecutionMessage -Message ("[PATH] npm cache: {0}" -f $cacheDirectory)
    Write-PackageExecutionMessage -Message ("[PATH] npm global config: {0}" -f $globalConfigPath)
    Write-PackageExecutionMessage -Message ("[STATE] npm package spec: {0}" -f $packageSpec)

    try {
        Push-Location $stagePath
        try {
            & $installerCommandInfo.CommandPath @commandArguments
            $exitCode = $LASTEXITCODE
            if ($null -eq $exitCode) {
                $exitCode = 0
            }
        }
        finally {
            Pop-Location
        }

        if ($exitCode -ne 0) {
            throw "Package npm global package install for '$($PackageResult.PackageId)' failed with exit code $exitCode."
        }

        $installParent = Split-Path -Parent $PackageResult.InstallDirectory
        if (-not [string]::IsNullOrWhiteSpace($installParent)) {
            $null = New-Item -ItemType Directory -Path $installParent -Force
        }
        Remove-PathIfExists -Path $PackageResult.InstallDirectory | Out-Null
        Move-Item -LiteralPath $stagePath -Destination $PackageResult.InstallDirectory -Force
        $stagePromoted = $true
    }
    finally {
        if (-not $stagePromoted) {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Preserving failed npm package install stage '{0}' for inspection." -f $stagePath)
        }
    }

    return [pscustomobject]@{
        Status           = Get-PackageOwnedInstallStatus -PackageResult $PackageResult
        InstallKind      = 'npmGlobalPackage'
        InstallDirectory = $PackageResult.InstallDirectory
        ReusedExisting   = $false
        InstallerCommand = $installerCommandInfo.Command
        InstallerCommandPath = $installerCommandInfo.CommandPath
        PackageSpec      = $packageSpec
        CommandArguments = @($commandArguments)
        CacheDirectory   = $cacheDirectory
        GlobalConfigPath = $globalConfigPath
        StagePath        = $stagePath
        ExitCode         = $exitCode
    }
}

function Install-PackageNpmMaterializedInstallGlobalPackage {
<#
.SYNOPSIS
Installs a materialized npm package spec from local tarballs into a staged Package-owned prefix.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $install = Get-PackageAssignedInstallOperation -Release $PackageResult.Package
    if (-not $install) {
        throw "Package npm materialized install for '$($PackageResult.PackageId)' requires packageOperations.assigned.install on the selected release."
    }
    if (-not $PackageResult.PSObject.Properties['NpmMaterialization'] -or -not $PackageResult.NpmMaterialization -or -not $PackageResult.NpmMaterialization.Success) {
        throw "Package npm materialized install for '$($PackageResult.PackageId)' requires prepared npm materialization."
    }

    $packageSpec = Get-PackageNpmResolvedPackageSpec -PackageResult $PackageResult
    $tarballPaths = @($PackageResult.NpmMaterialization.TarballPaths)
    if ($tarballPaths.Count -eq 0) {
        throw "Package npm materialized install for '$($PackageResult.PackageId)' has no local materialized tarballs."
    }
    foreach ($tarballPath in $tarballPaths) {
        if ([string]::IsNullOrWhiteSpace([string]$tarballPath) -or -not (Test-Path -LiteralPath ([string]$tarballPath) -PathType Leaf)) {
            throw "Package npm materialized install for '$($PackageResult.PackageId)' is missing materialized tarball '$tarballPath'."
        }
    }

    $installerCommandInfo = Resolve-PackageNpmInstallerCommand -PackageResult $PackageResult
    $cacheDirectory = New-PackageNpmCacheDirectory -PackageResult $PackageResult
    $globalConfigPath = Initialize-PackageNpmGlobalConfig -GlobalConfigPath (Get-PackageNpmGlobalConfigPath -PackageResult $PackageResult)
    if ([string]::IsNullOrWhiteSpace([string]$PackageResult.PackageInstallStageDirectory)) {
        throw "Package npm materialized install for '$($PackageResult.PackageId)' requires a package install stage directory."
    }

    $stagePath = [System.IO.Path]::GetFullPath([string]$PackageResult.PackageInstallStageDirectory)
    Remove-PathIfExists -Path $stagePath | Out-Null
    $null = New-Item -ItemType Directory -Path $stagePath -Force
    $stagePromoted = $false

    $cacheAddArguments = @('cache', 'add')
    $cacheAddArguments += @($tarballPaths)
    $cacheAddArguments += @('--cache', $cacheDirectory)
    $cacheAddArguments += @(Get-NpmGlobalConfigArguments -GlobalConfigPath $globalConfigPath)

    $commandArguments = @('install', '-g', '--prefix', $stagePath, '--cache', $cacheDirectory, '--offline')
    $commandArguments += @(Get-NpmGlobalConfigArguments -GlobalConfigPath $globalConfigPath)
    $commandArguments += $packageSpec

    Write-PackageExecutionMessage -Message ("[STATE] npm materialized package install:")
    Write-PackageExecutionMessage -Message ("[PATH] npm command: {0}" -f $installerCommandInfo.CommandPath)
    Write-PackageExecutionMessage -Message ("[PATH] npm stage: {0}" -f $stagePath)
    Write-PackageExecutionMessage -Message ("[PATH] npm cache: {0}" -f $cacheDirectory)
    Write-PackageExecutionMessage -Message ("[PATH] npm materialization manifest: {0}" -f [string]$PackageResult.NpmMaterialization.ManifestPath)
    Write-PackageExecutionMessage -Message ("[STATE] npm package spec: {0}" -f $packageSpec)

    try {
        & ([string]$installerCommandInfo.CommandPath) @cacheAddArguments
        $cacheAddExitCode = $LASTEXITCODE
        if ($null -eq $cacheAddExitCode) {
            $cacheAddExitCode = 0
        }
        if ($cacheAddExitCode -ne 0) {
            throw "npm cache add for materialized package '$($PackageResult.PackageId)' failed with exit code $cacheAddExitCode."
        }

        Push-Location $stagePath
        try {
            & ([string]$installerCommandInfo.CommandPath) @commandArguments
            $exitCode = $LASTEXITCODE
            if ($null -eq $exitCode) {
                $exitCode = 0
            }
        }
        finally {
            Pop-Location
        }

        if ($exitCode -ne 0) {
            throw "Package npm materialized install for '$($PackageResult.PackageId)' failed with exit code $exitCode."
        }

        $installParent = Split-Path -Parent $PackageResult.InstallDirectory
        if (-not [string]::IsNullOrWhiteSpace($installParent)) {
            $null = New-Item -ItemType Directory -Path $installParent -Force
        }
        Remove-PathIfExists -Path $PackageResult.InstallDirectory | Out-Null
        Move-Item -LiteralPath $stagePath -Destination $PackageResult.InstallDirectory -Force
        $stagePromoted = $true
    }
    finally {
        if (-not $stagePromoted) {
            Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Preserving failed npm materialized install stage '{0}' for inspection." -f $stagePath)
        }
    }

    return [pscustomobject]@{
        Status           = Get-PackageOwnedInstallStatus -PackageResult $PackageResult
        InstallKind      = 'npmMaterializedInstallGlobalPackage'
        InstallDirectory = $PackageResult.InstallDirectory
        ReusedExisting   = $false
        InstallerCommand = $installerCommandInfo.Command
        InstallerCommandPath = $installerCommandInfo.CommandPath
        PackageSpec      = $packageSpec
        MaterializationManifestPath = [string]$PackageResult.NpmMaterialization.ManifestPath
        MaterializedTarballPaths = @($tarballPaths)
        CacheAddArguments = @($cacheAddArguments)
        CommandArguments = @($commandArguments)
        CacheDirectory   = $cacheDirectory
        GlobalConfigPath = $globalConfigPath
        StagePath        = $stagePath
        ExitCode         = $exitCode
    }
}