Private/Logic/RuntimeKernel/Definitions/Manifested.CommandDefinitions.ps1
|
function Get-ManifestedCommandDefinitionsRoot { [CmdletBinding()] param() $moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) return (Join-Path $moduleRoot 'Definitions\Commands') } function Convert-ManifestedDefinitionKindToRuntimeKind { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Kind ) switch ($Kind) { 'portable-package' { return 'PortablePackage' } 'npm-cli' { return 'NpmCli' } 'machine-prerequisite' { return 'MachinePrerequisite' } default { throw "Unsupported command definition kind '$Kind'." } } } function Get-ManifestedDefinitionSectionValue { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [Parameter(Mandatory = $true)] [string]$SectionName ) if (-not $Definition.PSObject.Properties.Match($SectionName).Count) { return $null } return $Definition.$SectionName } function Get-ManifestedDefinitionBlock { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [Parameter(Mandatory = $true)] [string]$SectionName, [Parameter(Mandatory = $true)] [string]$BlockName ) $sectionValue = Get-ManifestedDefinitionSectionValue -Definition $Definition -SectionName $SectionName if (-not $sectionValue) { return $null } if (-not $sectionValue.PSObject.Properties.Match($BlockName).Count) { return $null } $value = $sectionValue.$BlockName if ($null -eq $value) { return $null } if ($value -is [string] -and [string]::IsNullOrWhiteSpace($value)) { return $null } return $value } function Get-ManifestedActiveDefinitionBlockNames { [CmdletBinding()] param( [psobject]$SectionValue ) if ($null -eq $SectionValue) { return @() } $activeBlocks = New-Object System.Collections.Generic.List[string] foreach ($property in @($SectionValue.PSObject.Properties)) { if ($null -eq $property.Value) { continue } if ($property.Value -is [string] -and [string]::IsNullOrWhiteSpace($property.Value)) { continue } $activeBlocks.Add($property.Name) | Out-Null } return @($activeBlocks) } function ConvertTo-ManifestedRuntimeDisplayName { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$RuntimeName ) $stem = ($RuntimeName -replace 'Runtime$', '').Trim() if ([string]::IsNullOrWhiteSpace($stem)) { return $RuntimeName } return (($stem -creplace '([a-z0-9])([A-Z])', '$1 $2').Trim() + ' runtime') } function Assert-ManifestedCommandDefinition { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [Parameter(Mandatory = $true)] [string]$DefinitionFileName ) foreach ($propertyName in @('schemaVersion', 'commandName', 'runtimeName', 'kind', 'wrapperCommand', 'refreshSwitchName', 'facts', 'supply', 'artifact', 'install', 'environment', 'dependencies', 'policies', 'hooks')) { if (-not $Definition.PSObject.Properties[$propertyName]) { throw "Command definition '$DefinitionFileName' is missing '$propertyName'." } } if ($Definition.kind -notin @('portable-package', 'npm-cli', 'machine-prerequisite')) { throw "Command definition '$($Definition.commandName)' has unsupported kind '$($Definition.kind)'." } if ($Definition.PSObject.Properties.Match('handlerIds').Count -gt 0) { throw "Command definition '$($Definition.commandName)' may not define handlerIds in the block-driven registry." } $factsBlocks = @(Get-ManifestedActiveDefinitionBlockNames -SectionValue (Get-ManifestedDefinitionSectionValue -Definition $Definition -SectionName 'facts')) if ($factsBlocks.Count -ne 1) { throw "Command definition '$($Definition.commandName)' must define exactly one active facts block." } foreach ($factsBlock in @($factsBlocks)) { if ($factsBlock -notin @('portableRuntime', 'pythonEmbeddableRuntime', 'machinePrerequisite', 'npmCli')) { throw "Command definition '$($Definition.commandName)' uses unsupported facts block '$factsBlock'." } } $supplyBlocks = @(Get-ManifestedActiveDefinitionBlockNames -SectionValue (Get-ManifestedDefinitionSectionValue -Definition $Definition -SectionName 'supply')) if ($supplyBlocks.Count -gt 1) { throw "Command definition '$($Definition.commandName)' may define at most one active supply block." } foreach ($supplyBlock in @($supplyBlocks)) { if ($supplyBlock -notin @('githubRelease', 'nodeDist', 'directDownload', 'pythonEmbed', 'vsCodeUpdate')) { throw "Command definition '$($Definition.commandName)' uses unsupported supply block '$supplyBlock'." } } if ($Definition.kind -in @('portable-package', 'machine-prerequisite')) { if ($supplyBlocks.Count -ne 1) { throw "Command definition '$($Definition.commandName)' must define exactly one active supply block." } } $portableArchiveInstall = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'install' -BlockName 'portableArchive' $pythonZipInstall = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'install' -BlockName 'pythonEmbeddableZip' $machineInstallerInstall = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'install' -BlockName 'machineInstaller' $npmInstall = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'install' -BlockName 'npmGlobalPackage' $zipPackageArtifact = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'artifact' -BlockName 'zipPackage' $executableInstallerArtifact = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'artifact' -BlockName 'executableInstaller' if ($portableArchiveInstall -and -not $zipPackageArtifact) { throw "Command definition '$($Definition.commandName)' must pair install.portableArchive with artifact.zipPackage." } if ($pythonZipInstall -and -not $zipPackageArtifact) { throw "Command definition '$($Definition.commandName)' must pair install.pythonEmbeddableZip with artifact.zipPackage." } if ($machineInstallerInstall -and -not $executableInstallerArtifact) { throw "Command definition '$($Definition.commandName)' must pair install.machineInstaller with artifact.executableInstaller." } if ($npmInstall -and $Definition.kind -ne 'npm-cli') { throw "Command definition '$($Definition.commandName)' may only use install.npmGlobalPackage for npm-cli definitions." } if ([bool]$Definition.policies.supportsEnvironmentSync) { $commandProjection = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'environment' -BlockName 'commandProjection' if (-not $commandProjection) { throw "Command definition '$($Definition.commandName)' must define environment.commandProjection when supportsEnvironmentSync is enabled." } } foreach ($dependency in @($Definition.dependencies)) { foreach ($dependencyProperty in @('runtimeName', 'minimumVersion', 'satisfactionMode', 'autoInstall', 'reason')) { if (-not $dependency.PSObject.Properties[$dependencyProperty]) { throw "Dependency entry in '$($Definition.commandName)' is missing '$dependencyProperty'." } } } } function New-ManifestedCommandExecutionContext { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition ) $runtimeDisplayName = ConvertTo-ManifestedRuntimeDisplayName -RuntimeName $Definition.runtimeName $zipArtifactBlock = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'artifact' -BlockName 'zipPackage' $installerArtifactBlock = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'artifact' -BlockName 'executableInstaller' $artifactBlock = if ($zipArtifactBlock) { $zipArtifactBlock } else { $installerArtifactBlock } return [pscustomobject]@{ ExecutionModel = 'DefinitionBlocks' RuntimeName = $Definition.runtimeName CommandName = $Definition.commandName RuntimeKind = Convert-ManifestedDefinitionKindToRuntimeKind -Kind $Definition.kind Definition = $Definition SupportsEnvironmentSync = [bool]$Definition.policies.supportsEnvironmentSync InstallRequiresElevation = [bool]$Definition.policies.installRequiresElevation RequireTrustedArtifact = [bool]$Definition.policies.requireTrustedArtifact RepairTarget = ('managed ' + $runtimeDisplayName + ' artifacts') ArtifactTarget = if ($installerArtifactBlock) { ('managed ' + $runtimeDisplayName + ' installer') } elseif ($artifactBlock) { ('managed ' + $runtimeDisplayName + ' package') } else { $null } InstallTarget = ('managed ' + $runtimeDisplayName) EnvironmentTarget = if ([bool]$Definition.policies.supportsEnvironmentSync) { ($runtimeDisplayName + ' command-line environment') } else { $null } } } function Get-ManifestedCommandProjectionFromDefinition { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [Parameter(Mandatory = $true)] [pscustomobject]$Facts ) $environment = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'environment' -BlockName 'commandProjection' if (-not $environment) { return [pscustomobject]@{ Applicable = $false DesiredExecutablePath = $null DesiredCommandDirectory = $null ExpectedCommandPaths = [ordered]@{} } } $desiredDirectoryFact = if ($environment.PSObject.Properties.Match('desiredDirectoryFact').Count -gt 0) { $environment.desiredDirectoryFact } else { 'RuntimeHome' } $executableFact = if ($environment.PSObject.Properties.Match('executableFact').Count -gt 0) { $environment.executableFact } else { 'ExecutablePath' } $desiredCommandDirectory = if ($Facts.PSObject.Properties.Match($desiredDirectoryFact).Count -gt 0) { $Facts.($desiredDirectoryFact) } else { $null } $desiredExecutablePath = if ($Facts.PSObject.Properties.Match($executableFact).Count -gt 0) { $Facts.($executableFact) } else { $null } if ([string]::IsNullOrWhiteSpace($desiredCommandDirectory) -and -not [string]::IsNullOrWhiteSpace($desiredExecutablePath)) { $desiredCommandDirectory = Split-Path -Parent $desiredExecutablePath } $expectedCommandPaths = [ordered]@{} foreach ($commandName in @($environment.expectedCommands)) { if ([string]::IsNullOrWhiteSpace($commandName)) { continue } if (-not [string]::IsNullOrWhiteSpace($desiredExecutablePath) -and ((Split-Path -Leaf $desiredExecutablePath) -ieq $commandName)) { $expectedCommandPaths[$commandName] = (Get-ManifestedFullPath -Path $desiredExecutablePath) } elseif (-not [string]::IsNullOrWhiteSpace($desiredCommandDirectory)) { $expectedCommandPaths[$commandName] = (Get-ManifestedFullPath -Path (Join-Path $desiredCommandDirectory $commandName)) } } return [pscustomobject]@{ Applicable = (-not [string]::IsNullOrWhiteSpace($desiredCommandDirectory)) -and ($expectedCommandPaths.Count -gt 0) DesiredExecutablePath = $desiredExecutablePath DesiredCommandDirectory = $desiredCommandDirectory ExpectedCommandPaths = $expectedCommandPaths } } function Import-ManifestedCommandDefinitions { [CmdletBinding()] param( [string]$DefinitionsRoot = (Get-ManifestedCommandDefinitionsRoot) ) $script:ManifestedCommandDefinitions = @() $script:ManifestedCommandDefinitionsByCommandName = @{} $script:ManifestedCommandDefinitionsByRuntimeName = @{} if ([string]::IsNullOrWhiteSpace($DefinitionsRoot) -or -not (Test-Path -LiteralPath $DefinitionsRoot)) { return @() } $definitionFiles = @(Get-ChildItem -LiteralPath $DefinitionsRoot -Filter '*.json' -File | Sort-Object Name) foreach ($definitionFile in $definitionFiles) { $definition = Get-Content -LiteralPath $definitionFile.FullName -Raw -ErrorAction Stop | ConvertFrom-Json Assert-ManifestedCommandDefinition -Definition $definition -DefinitionFileName $definitionFile.Name if ($script:ManifestedCommandDefinitionsByCommandName.ContainsKey($definition.commandName)) { throw "Duplicate command definition for '$($definition.commandName)'." } if ($script:ManifestedCommandDefinitionsByRuntimeName.ContainsKey($definition.runtimeName)) { throw "Duplicate runtime definition for '$($definition.runtimeName)'." } $script:ManifestedCommandDefinitions += $definition $script:ManifestedCommandDefinitionsByCommandName[$definition.commandName] = $definition $script:ManifestedCommandDefinitionsByRuntimeName[$definition.runtimeName] = $definition } return @($script:ManifestedCommandDefinitions | Sort-Object runtimeName) } function Get-ManifestedCommandDefinitions { [CmdletBinding()] param() return @($script:ManifestedCommandDefinitions | Sort-Object runtimeName) } function Get-ManifestedCommandDefinition { [CmdletBinding(DefaultParameterSetName = 'ByName')] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByName')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'ByCommandName')] [string]$CommandName, [Parameter(Mandatory = $true, ParameterSetName = 'ByRuntimeName')] [string]$RuntimeName ) switch ($PSCmdlet.ParameterSetName) { 'ByCommandName' { if ($script:ManifestedCommandDefinitionsByCommandName.ContainsKey($CommandName)) { return $script:ManifestedCommandDefinitionsByCommandName[$CommandName] } } 'ByRuntimeName' { if ($script:ManifestedCommandDefinitionsByRuntimeName.ContainsKey($RuntimeName)) { return $script:ManifestedCommandDefinitionsByRuntimeName[$RuntimeName] } } 'ByName' { foreach ($definition in @(Get-ManifestedCommandDefinitions)) { if (($definition.commandName -eq $Name) -or ($definition.runtimeName -eq $Name) -or ($definition.wrapperCommand -eq $Name)) { return $definition } } } } return $null } function Get-ManifestedCommandContext { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$CommandName ) $definition = Get-ManifestedCommandDefinition -CommandName $CommandName if (-not $definition) { return $null } return (New-ManifestedCommandExecutionContext -Definition $definition) } function Get-ManifestedRuntimeContext { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$RuntimeName ) $definition = Get-ManifestedCommandDefinition -RuntimeName $RuntimeName if (-not $definition) { return $null } return (New-ManifestedCommandExecutionContext -Definition $definition) } |