diff --git a/helpers/software-report-base/Calculate-ImagesDiff.ps1 b/helpers/software-report-base/Calculate-ImagesDiff.ps1 index 6d874a04..91a05c86 100644 --- a/helpers/software-report-base/Calculate-ImagesDiff.ps1 +++ b/helpers/software-report-base/Calculate-ImagesDiff.ps1 @@ -18,7 +18,9 @@ Param ( [Parameter(Mandatory=$true)] [string] $CurrentJsonReportPath, [Parameter(Mandatory=$true)] - [string] $OutputFile + [string] $OutputFile, + [Parameter(Mandatory=$false)] + [string] $ImageDocsUrl ) $ErrorActionPreference = "Stop" @@ -45,4 +47,9 @@ $currentReport = Read-SoftwareReport -JsonReportPath $CurrentJsonReportPath $comparer = [SoftwareReportComparer]::new($previousReport, $currentReport) $comparer.CompareReports() $diff = $comparer.GetMarkdownReport() + +if ($ImageDocsUrl) { + $diff += "`n`n`n For comprehensive list of software installed on this image please click [here]($ImageDocsUrl)." +} + $diff | Out-File -Path $OutputFile -Encoding utf8NoBOM diff --git a/helpers/software-report-base/SoftwareReport.BaseNodes.psm1 b/helpers/software-report-base/SoftwareReport.BaseNodes.psm1 index 545b7a38..db7916ae 100644 --- a/helpers/software-report-base/SoftwareReport.BaseNodes.psm1 +++ b/helpers/software-report-base/SoftwareReport.BaseNodes.psm1 @@ -5,7 +5,7 @@ # Abstract base class for all nodes class BaseNode { [Boolean] ShouldBeIncludedToDiff() { - return $False + return $false } [Boolean] IsSimilarTo([BaseNode] $OtherNode) { @@ -26,7 +26,7 @@ class BaseToolNode: BaseNode { } [Boolean] ShouldBeIncludedToDiff() { - return $True + return $true } [String] GetValue() { @@ -35,7 +35,7 @@ class BaseToolNode: BaseNode { [Boolean] IsSimilarTo([BaseNode] $OtherNode) { if ($this.GetType() -ne $OtherNode.GetType()) { - return $False + return $false } return $this.ToolName -eq $OtherNode.ToolName diff --git a/helpers/software-report-base/SoftwareReport.Comparer.psm1 b/helpers/software-report-base/SoftwareReport.Comparer.psm1 index 8adb7332..45af1971 100644 --- a/helpers/software-report-base/SoftwareReport.Comparer.psm1 +++ b/helpers/software-report-base/SoftwareReport.Comparer.psm1 @@ -37,7 +37,13 @@ class SoftwareReportComparer { # Nodes are identical, nothing changed, just ignore it } elseif ($sameNodeInPreviousReport) { # Nodes are equal but not identical, so something was changed - $this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers)) + if ($currentReportNode -is [TableNode]) { + $this.CompareSimilarTableNodes($sameNodeInPreviousReport, $currentReportNode, $Headers) + } elseif ($currentReportNode -is [ToolVersionsNode]) { + $this.CompareSimilarToolVersionsListNodes($sameNodeInPreviousReport, $currentReportNode, $Headers) + } else { + $this.ChangedItems.Add([ReportDifferenceItem]::new($sameNodeInPreviousReport, $currentReportNode, $Headers)) + } } else { # Node was not found in previous report, new node was added $this.AddedItems.Add([ReportDifferenceItem]::new($null, $currentReportNode, $Headers)) @@ -60,6 +66,43 @@ class SoftwareReportComparer { } } + hidden [void] CompareSimilarTableNodes([TableNode] $PreviousReportNode, [TableNode] $CurrentReportNode, [Array] $Headers) { + $addedRows = $CurrentReportNode.Rows | Where-Object { $_ -notin $PreviousReportNode.Rows } + $deletedRows = $PreviousReportNode.Rows | Where-Object { $_ -notin $CurrentReportNode.Rows } + + if (($addedRows.Count -gt 0) -and ($deletedRows.Count -eq 0)) { + $this.AddedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) + } elseif (($deletedRows.Count -gt 0) -and ($addedRows.Count -eq 0)) { + $this.DeletedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) + } else { + $this.ChangedItems.Add([ReportDifferenceItem]::new($PreviousReportNode, $CurrentReportNode, $Headers)) + } + } + + hidden [void] CompareSimilarToolVersionsListNodes([ToolVersionsNode] $PreviousReportNode, [ToolVersionsNode] $CurrentReportNode, [Array] $Headers) { + $previousReportMajorVersions = $PreviousReportNode.Versions | ForEach-Object { $PreviousReportNode.ExtractMajorVersion($_) } + $currentReportMajorVersion = $CurrentReportNode.Versions | ForEach-Object { $CurrentReportNode.ExtractMajorVersion($_) } + + $addedVersions = $CurrentReportNode.Versions | Where-Object { $CurrentReportNode.ExtractMajorVersion($_) -notin $previousReportMajorVersions } + $deletedVersions = $PreviousReportNode.Versions | Where-Object { $PreviousReportNode.ExtractMajorVersion($_) -notin $currentReportMajorVersion } + $changedPreviousVersions = $PreviousReportNode.Versions | Where-Object { ($PreviousReportNode.ExtractMajorVersion($_) -in $currentReportMajorVersion) -and ($_ -notin $CurrentReportNode.Versions) } + $changedCurrentVersions = $CurrentReportNode.Versions | Where-Object { ($CurrentReportNode.ExtractMajorVersion($_) -in $previousReportMajorVersions) -and ($_ -notin $PreviousReportNode.Versions) } + + if ($addedVersions.Count -gt 0) { + $this.AddedItems.Add([ReportDifferenceItem]::new($null, [ToolVersionsNode]::new($CurrentReportNode.ToolName, $addedVersions, $CurrentReportNode.MajorVersionRegex, $true), $Headers)) + } + + if ($deletedVersions.Count -gt 0) { + $this.DeletedItems.Add([ReportDifferenceItem]::new([ToolVersionsNode]::new($PreviousReportNode.ToolName, $deletedVersions, $PreviousReportNode.MajorVersionRegex, $true), $null, $Headers)) + } + + $previousChangedNode = ($changedPreviousVersions.Count -gt 0) ? [ToolVersionsNode]::new($PreviousReportNode.ToolName, $changedPreviousVersions, $PreviousReportNode.MajorVersionRegex, $true) : $null + $currentChangedNode = ($changedCurrentVersions.Count -gt 0) ? [ToolVersionsNode]::new($CurrentReportNode.ToolName, $changedCurrentVersions, $CurrentReportNode.MajorVersionRegex, $true) : $null + if ($previousChangedNode -and $currentChangedNode) { + $this.ChangedItems.Add([ReportDifferenceItem]::new($previousChangedNode, $currentChangedNode, $Headers)) + } + } + [String] GetMarkdownReport() { $reporter = [SoftwareReportComparerReport]::new() $report = $reporter.GenerateMarkdownReport($this.CurrentReport, $this.PreviousReport, $this.AddedItems, $this.ChangedItems, $this.DeletedItems) @@ -69,10 +112,10 @@ class SoftwareReportComparer { hidden [Boolean] FilterExcludedNodes([BaseNode] $Node) { # We shouldn't show "Image Version" diff because it is already shown in report header if (($Node -is [ToolNode]) -and ($Node.ToolName -eq "Image Version:")) { - return $False + return $false } - return $True + return $true } } @@ -89,7 +132,7 @@ class SoftwareReportComparerReport { ### Render report header #### ############################# - $sb.AppendLine("# :desktop_computer: $($rootNode.Title)") + $sb.AppendLine("# :desktop_computer: Actions Runner Image: $($rootNode.Title)") # ToolNodes on root level contains main image description so just copy-paste them to final report $rootNode.Children | Where-Object { $_ -is [BaseToolNode] } | ForEach-Object { @@ -107,7 +150,7 @@ class SoftwareReportComparerReport { if ($addedItemsExcludeTables.Count -gt 0) { $tableItems = $addedItemsExcludeTables | ForEach-Object { [PSCustomObject]@{ - "Category" = $this.RenderCategory($_.Headers, $True); + "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName); "Current ($imageVersion)" = $_.CurrentReportNode.GetValue(); } @@ -115,7 +158,6 @@ class SoftwareReportComparerReport { $sb.AppendLine("### Added :heavy_plus_sign:") $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) - $sb.AppendLine() } # Render added tables separately @@ -131,7 +173,7 @@ class SoftwareReportComparerReport { if ($deletedItemsExcludeTables.Count -gt 0) { $tableItems = $deletedItemsExcludeTables | ForEach-Object { [PSCustomObject]@{ - "Category" = $this.RenderCategory($_.Headers, $True); + "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.PreviousReportNode.ToolName); "Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue(); } @@ -139,7 +181,11 @@ class SoftwareReportComparerReport { $sb.AppendLine("### Deleted :heavy_minus_sign:") $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) - $sb.AppendLine() + } + + # Render deleted tables separately + $DeletedItems | Where-Object { $_.IsTableNode() } | ForEach-Object { + $sb.AppendLine($this.RenderTableNodesDiff($_)) } ############################# @@ -150,7 +196,7 @@ class SoftwareReportComparerReport { if ($changedItemsExcludeTables.Count -gt 0) { $tableItems = $changedItemsExcludeTables | ForEach-Object { [PSCustomObject]@{ - "Category" = $this.RenderCategory($_.Headers, $True); + "Category" = $this.RenderCategory($_.Headers, $true); "Tool name" = $this.RenderToolName($_.CurrentReportNode.ToolName); "Previous ($previousImageVersion)" = $_.PreviousReportNode.GetValue(); "Current ($imageVersion)" = $_.CurrentReportNode.GetValue(); @@ -159,7 +205,6 @@ class SoftwareReportComparerReport { $sb.AppendLine("### Updated") $sb.AppendLine($this.RenderHtmlTable($tableItems, "Category")) - $sb.AppendLine() } # Render updated tables separately @@ -167,11 +212,6 @@ class SoftwareReportComparerReport { $sb.AppendLine($this.RenderTableNodesDiff($_)) } - # Render deleted tables separately - $DeletedItems | Where-Object { $_.IsTableNode() } | ForEach-Object { - $sb.AppendLine($this.RenderTableNodesDiff($_)) - } - return $sb.ToString() } @@ -187,7 +227,6 @@ class SoftwareReportComparerReport { $sb.AppendLine(" ") $sb.AppendLine("
") - $tableRowSpans = $this.CalculateHtmlTableRowSpan($Table, $RowSpanColumnName) for ($rowIndex = 0; $rowIndex -lt $Table.Count; $rowIndex++) { $row = $Table[$rowIndex] @@ -246,7 +285,7 @@ class SoftwareReportComparerReport { } $sb = [System.Text.StringBuilder]::new() - $sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $False))") + $sb.AppendLine("#### $($this.RenderCategory($DiffItem.Headers, $false))") $sb.AppendLine([TableNode]::new($tableHeaders, $tableRows).ToMarkdown(0)) return $sb.ToString() } diff --git a/helpers/software-report-base/SoftwareReport.Nodes.psm1 b/helpers/software-report-base/SoftwareReport.Nodes.psm1 index 1385782a..48ad1cf7 100644 --- a/helpers/software-report-base/SoftwareReport.Nodes.psm1 +++ b/helpers/software-report-base/SoftwareReport.Nodes.psm1 @@ -35,7 +35,7 @@ class HeaderNode: BaseNode { } [Boolean] ShouldBeIncludedToDiff() { - return $True + return $true } [void] AddNode([BaseNode] $node) { @@ -63,8 +63,8 @@ class HeaderNode: BaseNode { $this.AddNode([ToolNode]::new($ToolName, $Version)) } - [void] AddToolVersionsNode([String] $ToolName, [Array] $Version) { - $this.AddNode([ToolVersionsNode]::new($ToolName, $Version)) + [void] AddToolVersionsNode([String] $ToolName, [Array] $Version, [String] $MajorVersionRegex, [Boolean] $InlineList) { + $this.AddNode([ToolVersionsNode]::new($ToolName, $Version, $MajorVersionRegex, $InlineList)) } [void] AddTableNode([Array] $Table) { @@ -155,12 +155,21 @@ class ToolNode: BaseToolNode { # Node type to describe the tool with multiple versions "Toolcache Node.js 14.17.6 16.2.0 18.2.3" class ToolVersionsNode: BaseToolNode { [Array] $Versions + [Regex] $MajorVersionRegex + [String] $ListType - ToolVersionsNode([String] $ToolName, [Array] $Versions): base($ToolName) { + ToolVersionsNode([String] $ToolName, [Array] $Versions, [String] $MajorVersionRegex, [Boolean] $InlineList): base($ToolName) { $this.Versions = $Versions + $this.MajorVersionRegex = [Regex]::new($MajorVersionRegex) + $this.ListType = $InlineList ? "Inline" : "List" + $this.ValidateMajorVersionRegex() } [String] ToMarkdown($level) { + if ($this.ListType -eq "Inline") { + return "- $($this.ToolName): $($this.Versions -join ', ')" + } + $sb = [System.Text.StringBuilder]::new() $sb.AppendLine() $sb.AppendLine("$("#" * $level) $($this.ToolName)") @@ -175,16 +184,35 @@ class ToolVersionsNode: BaseToolNode { return $this.Versions -join ', ' } + [String] ExtractMajorVersion([String] $Version) { + $match = $this.MajorVersionRegex.Match($Version) + if ($match.Success -ne $true) { + throw "Version '$Version' doesn't match regex '$($this.PrimaryVersionRegex)'" + } + + return $match.Groups[0].Value + } + [PSCustomObject] ToJsonObject() { return [PSCustomObject]@{ NodeType = $this.GetType().Name ToolName = $this.ToolName Versions = $this.Versions + MajorVersionRegex = $this.MajorVersionRegex.ToString() + ListType = $this.ListType } } static [ToolVersionsNode] FromJsonObject($jsonObj) { - return [ToolVersionsNode]::new($jsonObj.ToolName, $jsonObj.Versions) + return [ToolVersionsNode]::new($jsonObj.ToolName, $jsonObj.Versions, $jsonObj.MajorVersionRegex, $jsonObj.ListType -eq "Inline") + } + + hidden [void] ValidateMajorVersionRegex() { + $this.Versions | Group-Object { $this.ExtractMajorVersion($_) } | ForEach-Object { + if ($_.Count -gt 1) { + throw "Multiple versions from list $($this.GetValue()) return the same result from regex '$($this.MajorVersionRegex)': $($_.Name)" + } + } } } @@ -200,7 +228,7 @@ class TableNode: BaseNode { } [Boolean] ShouldBeIncludedToDiff() { - return $True + return $true } static [TableNode] FromObjectsArray([Array] $Table) { diff --git a/images/macos/helpers/SoftwareReport.Helpers.psm1 b/images/macos/helpers/SoftwareReport.Helpers.psm1 index 0f8b9abc..3dfc8496 100644 --- a/images/macos/helpers/SoftwareReport.Helpers.psm1 +++ b/images/macos/helpers/SoftwareReport.Helpers.psm1 @@ -23,14 +23,6 @@ function Take-Part { return [string]::Join($Delimiter, $selectedParts) } -function New-MDNewLine { - param ( - [int] $Count = 1 - ) - $newLineSymbol = [System.Environment]::NewLine - return $newLineSymbol * $Count -} - function Get-LinkTarget { param ( [string] $inputPath @@ -60,61 +52,3 @@ function Get-BrewPackageVersion { return $packageVersion } - -function Get-CachedToolInstances { - <# - .SYNOPSIS - Returns hashtable of installed cached tools. - - .DESCRIPTION - Return hashtable that contains versions and architectures for the selected cached tool. - - .PARAMETER Name - Name of cached tool. - - .PARAMETER VersionCommand - Optional parameter. Command to return version of system default tool. - - .EXAMPLE - Get-CachedToolInstances -Name "Python" -VersionCommand "--version" - - #> - - param - ( - [String] $Name, - [String] $VersionCommand - ) - - $toolInstances = @() - $toolPath = Join-Path -Path $env:AGENT_TOOLSDIRECTORY -ChildPath $Name - - # Get all installed versions from TOOLSDIRECTORY folder - $versions = Get-ChildItem $toolPath | Sort-Object { [System.Version]$_.Name } - foreach ($version in $versions) { - $instanceInfo = @{} - - # Create instance hashtable - [string]$instanceInfo.Path = Join-Path -Path $toolPath -ChildPath $version.Name - [string]$instanceInfo.Version = $version.Name - - # Get all architectures for current version - [array]$instanceInfo.Architecture_Array = Get-ChildItem $version.FullName -Name -Directory | Where-Object { $_ -match "^x[0-9]{2}$" } - [string]$instanceInfo.Architecture = $instanceInfo.Architecture_Array -Join ", " - - # Add (default) postfix to version name, in case if current version is in environment path - if (-not ([string]::IsNullOrEmpty($VersionCommand))) { - $defaultVersion = $(& ($Name.ToLower()) $VersionCommand 2>&1) - $defaultToolVersion = $defaultVersion | Select-String -Pattern "\d+\.\d+\.\d+" -AllMatches ` - | ForEach-Object { $_.Matches.Value } - - if ([version]$version.Name -eq [version]$defaultToolVersion) { - $instanceInfo.Version += " (Default)" - } - } - - $toolInstances += $instanceInfo - } - - return $toolInstances -} \ No newline at end of file diff --git a/images/macos/software-report/SoftwareReport.Common.psm1 b/images/macos/software-report/SoftwareReport.Common.psm1 index 3d03d0a0..a5226b33 100644 --- a/images/macos/software-report/SoftwareReport.Common.psm1 +++ b/images/macos/software-report/SoftwareReport.Common.psm1 @@ -9,8 +9,7 @@ function Get-BashVersion { function Get-DotnetVersionList { $sdkRawList = Run-Command "dotnet --list-sdks" - $sdkVersionList = $sdkRawList | ForEach-Object { Take-Part $_ -Part 0 } - return [string]::Join(" ", $sdkVersionList) + return $sdkRawList | ForEach-Object { Take-Part $_ -Part 0 } } function Get-GoVersion { @@ -148,8 +147,7 @@ function Get-NVMNodeVersionList { $nvmInitCommand = ". ${nvmPath} > /dev/null 2>&1 || true" $nodejsVersionsRaw = Run-Command "${nvmInitCommand} && nvm ls" $nodeVersions = $nodejsVersionsRaw | ForEach-Object { $_.TrimStart(" ").TrimEnd(" *") } | Where-Object { $_.StartsWith("v") } - $formattedNodeVersions = $nodeVersions | ForEach-Object { $_.TrimStart("v") } - return [string]::Join(" ", $formattedNodeVersions) + return $nodeVersions | ForEach-Object { $_.TrimStart("v") } } function Build-OSInfoSection { @@ -331,7 +329,7 @@ function Get-PackerVersion { } function Get-OpenSSLVersion { - $opensslVersion = Get-Item /usr/local/opt/openssl@1.1 | ForEach-Object {"{0} ``({1} -> {2})``" -f (Run-Command "openssl version"), $_.FullName, $_.Target} + $opensslVersion = Run-Command "openssl version" return ($opensslVersion -replace "^OpenSSL").Trim() } diff --git a/images/macos/software-report/SoftwareReport.Generator.ps1 b/images/macos/software-report/SoftwareReport.Generator.ps1 index f6e796d5..c23e26fa 100644 --- a/images/macos/software-report/SoftwareReport.Generator.ps1 +++ b/images/macos/software-report/SoftwareReport.Generator.ps1 @@ -33,7 +33,7 @@ $installedSoftware = $softwareReport.Root.AddHeaderNode("Installed Software") # Language and Runtime $languageAndRuntime = $installedSoftware.AddHeaderNode("Language and Runtime") -$languageAndRuntime.AddToolNode(".NET SDK", $(Get-DotnetVersionList)) +$languageAndRuntime.AddToolVersionsNode(".NET Core SDK", $(Get-DotnetVersionList), '^\d+\.\d+\.\d', $true) $languageAndRuntime.AddToolNode("Bash", $(Get-BashVersion)) $languageAndRuntime.AddNodes($(Get-ClangLLVMVersions)) $languageAndRuntime.AddNodes($(Get-GccVersions)) @@ -45,7 +45,7 @@ $languageAndRuntime.AddToolNode("Mono", $(Get-MonoVersion)) $languageAndRuntime.AddToolNode("MSBuild", $(Get-MSBuildVersion)) $languageAndRuntime.AddToolNode("Node.js", $(Get-NodeVersion)) $languageAndRuntime.AddToolNode("NVM", $(Get-NVMVersion)) -$languageAndRuntime.AddToolNode("NVM - Cached node versions:", $(Get-NVMNodeVersionList)) +$languageAndRuntime.AddToolVersionsNode("NVM - Cached node versions", $(Get-NVMNodeVersionList), '^\d+', $true) $languageAndRuntime.AddToolNode("Perl", $(Get-PerlVersion)) $languageAndRuntime.AddToolNode("PHP", $(Get-PHPVersion)) $languageAndRuntime.AddToolNode("Python", $(Get-PythonVersion)) @@ -153,7 +153,7 @@ $tools.AddToolNode("Xcode Command Line Tools", $(Get-XcodeCommandLineToolsVersio # Linters $linters = $installedSoftware.AddHeaderNode("Linters") -$linters.AddToolNode("Swift", $(Get-SwiftLintVersion)) +$linters.AddToolNode("SwiftLint", $(Get-SwiftLintVersion)) $linters.AddToolNode("Yamllint", $(Get-YamllintVersion)) # Browsers @@ -193,7 +193,7 @@ $powerShell = $installedSoftware.AddHeaderNode("PowerShell Tools") $powerShell.AddToolNode("PowerShell", $(Get-PowershellVersion)) $powerShellModules = $powerShell.AddHeaderNode("PowerShell Modules") -$powerShellModules.AddTableNode($(Get-PowerShellModules)) +$powerShellModules.AddNodes($(Get-PowerShellModules)) # Web Servers $webServers = $installedSoftware.AddHeaderNode("Web Servers") diff --git a/images/macos/software-report/SoftwareReport.Toolcache.psm1 b/images/macos/software-report/SoftwareReport.Toolcache.psm1 index 4ab0b6fc..d59f6b57 100644 --- a/images/macos/software-report/SoftwareReport.Toolcache.psm1 +++ b/images/macos/software-report/SoftwareReport.Toolcache.psm1 @@ -28,47 +28,26 @@ function Get-ToolcacheNodeVersions { return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } -function Get-ToolcacheGoTable { - $ToolInstances = Get-CachedToolInstances -Name "Go" -VersionCommand "version" - foreach ($Instance in $ToolInstances) { - $Version = [System.Version]($Instance.Version -Split(" "))[0] - $Instance."Environment Variable" = "GOROOT_$($Version.major)_$($Version.minor)_X64" - } - - $Content = $ToolInstances | ForEach-Object { - return [PSCustomObject]@{ - Version = $_.Version - Architecture = $_.Architecture - "Environment Variable" = $_."Environment Variable" - } - } - - return $Content +function Get-ToolcacheGoVersions { + $toolcachePath = Join-Path $env:HOME "hostedtoolcache" "Go" + return Get-ChildItem $toolcachePath -Name | Sort-Object { [Version]$_ } } function Build-ToolcacheSection { - $goToolNode = [HeaderNode]::new("Go") - $goToolNode.AddTableNode($(Get-ToolcacheGoTable)) return @( - [ToolVersionsNode]::new("Ruby", $(Get-ToolcacheRubyVersions)) - [ToolVersionsNode]::new("Python", $(Get-ToolcachePythonVersions)) - [ToolVersionsNode]::new("PyPy", $(Get-ToolcachePyPyVersions)) - [ToolVersionsNode]::new("Node.js", $(Get-ToolcacheNodeVersions)) - $goToolNode + [ToolVersionsNode]::new("Ruby", $(Get-ToolcacheRubyVersions), '^\d+\.\d+', $false), + [ToolVersionsNode]::new("Python", $(Get-ToolcachePythonVersions), '^\d+\.\d+', $false), + [ToolVersionsNode]::new("PyPy", $(Get-ToolcachePyPyVersions), '^\d+\.\d+', $false), + [ToolVersionsNode]::new("Node.js", $(Get-ToolcacheNodeVersions), '^\d+', $false), + [ToolVersionsNode]::new("Go", $(Get-ToolcacheGoVersions), '^\d+\.\d+', $false) ) } function Get-PowerShellModules { $modules = (Get-ToolsetValue powershellModules).name - - $psModules = Get-Module -Name $modules -ListAvailable | Sort-Object Name | Group-Object Name - return $psModules | ForEach-Object { - $moduleName = $_.Name - $moduleVersions = ($_.group.Version | Sort-Object -Unique) -join '