ps/Modules/Cole.PowerShell.Developer/Public/Invoke-CategorizeCSProj.ps1
2023-05-30 22:51:22 -07:00

323 lines
15 KiB
PowerShell

function Invoke-CategorizeCSProj {
[CmdletBinding()]
[OutputType([PSCustomObject])]
param (
[Parameter(Mandatory = $true, ParameterSetName = 'Path')]
[ValidateNotNullOrEmpty()]
[string]$Path,
[Parameter(Mandatory = $true, ParameterSetName = 'Project')]
[ValidateNotNullOrEmpty()]
[object]$Project
)
$logLead = Get-LogLeadName
$projectName = $Project.Name
$deprecatedProjectRegex = "^(zz|deprecated|archive|obsolete|xxx)"
# This is a magic value invented by Cole for max-length of file for discrepancy on what exists
$magicChocoFilesLengthValue = 160
if ($PSCmdlet.ParameterSetName -eq 'Project') {
if ([string]::IsNullOrWhiteSpace($Project.Path)) {
Write-Warning "$logLead : Could not find a path property for [$($Project.Name) ($($Project.Type))]$(if ($ErrorActionPreference -ne 'Stop') { ", returning `$null"})"
return $null
}
$Path = $Project.Path
}
if (Test-Path -Path $Path) {
$Path = Resolve-Path -Path $Path
} else {
Write-Error "$logLead : Could not resolve the path [$Path].$(if ($ErrorActionPreference -ne 'Stop') { " Returning `$null" })"
return $null
}
Write-Verbose "$logLead : Processing child objects for csproj files"
$item = Get-Item -Path $Path
if ($item.PSIsContainer) {
$csprojList = Get-ChildItem -Path $Path -ChildPath "*.csproj"
if (-not (Any $csprojList)) {
Write-Error "$logLead : Provided path was a Directory, and no child projects were found in the folder directly.$(if ($ErrorActionPreference -ne 'Stop') { " Returning `$null" })"
}
$Path = $csprojList[0].FullName
Write-Warning "$logLead : Provided path was a Directory, using the first found csproj in this folder, with path [$Path]"
}
if ($PSCmdlet.ParameterSetName -eq 'Path') {
$projectName = [System.IO.Path]::GetFileNameWithoutExtension($Path)
}
$baseFolder = Split-Path -Path $Path -Parent
Write-Host "$logLead : Begin processing project $($projectName)"
$csproj = @{
Name = $projectName
Path = $Path
ChocoFiles = @{
HasChocoFiles = $false
InstallFileSize = 0
InstallFileNew = $false
UninstallFileSize = 0
UninstallFileNew = $false
ChocolateyInstall = $null
ChocolateyUninstall = $null
}
Nuspec = @{
HasNuspecFile = $false
IsChocolatey = $false
PackageId = $null
Version = $null
Raw = $null
}
AlkamiManifest = @{
HasAlkamiManifest = $false
ComponentType = $null
AlkamiManifest = $null
IsLegacyInstaller = $false
IsUnkownInstaller = $false
}
HasTestReferences = $false
HasProgramMain = $false
Packages = $null
IsLikelyDeprecated = $Path -match $deprecatedProjectRegex
TargetFramework = 'Framework'
TargetFrameworkValue = $null
HasPostBuildEvent = $false
HasPreBuildEvent = $false
}
$rawCsproj = [xml](Get-Content -Path $Path -Raw)
$files = Get-ChildItem -Path $baseFolder
if (![string]::IsNullOrWhiteSpace($rawCsproj.Project.PropertyGroup.TargetFramework)) {
$csproj.TargetFrameworkValue = $rawCsproj.Project.PropertyGroup.TargetFramework
} elseif (![string]::IsNullOrWhiteSpace($rawCsproj.Project.PropertyGroup.TargetFrameworkVersion)) {
$csproj.TargetFrameworkValue = $rawCsproj.Project.PropertyGroup.TargetFrameworkVersion
} else {
$csproj.TargetFrameworkValue = 'Unknown'
}
if ($rawCsproj.Project.PropertyGroup.TargetFramework -match 'net6' -or $rawCsproj.Project.PropertyGroup.TargetFramework -match 'netcore') {
$csproj.TargetFramework = 'core'
} elseif ($rawCsproj.Project.PropertyGroup.TargetFramework -match 'netstandard') {
$csproj.TargetFramework = 'netstandard'
}
#region check for build events
if ($null -ne $rawCsproj.Project.PropertyGroup.PreBuildEvent) {
$csproj.HasPreBuildEvent = $true
}
if ($null -ne $rawCsproj.Project.PropertyGroup.PostBuildEvent) {
$csproj.HasPostBuildEvent = $true
}
#endregion check for build events
$includePackagesConfig = ($rawCsproj.Project.ItemGroup.None.Include -eq 'packages.config') -or ($rawCsproj.Project.ItemGroup.Content.Include -eq 'packages.config')
$csproj.IncludesPackagesConfig = $includePackagesConfig
Write-Verbose "$logLead : Read packages.config"
$packages = @()
$packagesConfigPath = Join-Path -Path $baseFolder -ChildPath "packages.config"
$baseFolderContainsPackagesConfig = Test-Path -Path $packagesConfigPath
if ($baseFolderContainsPackagesConfig) {
$csproj.PackagesConfigPath = $packagesConfigPath
$packagesConfig = [xml](Get-Content -Path $packagesConfigPath -Raw)
foreach ($package in $packagesConfig.packages.package) {
$packages += @{ PackageId = $package.id; Version = $package.version}
}
}
foreach ($package in $rawCsproj.Project.ItemGroup.PackageReference) {
if ([string]::IsNullOrWhiteSpace($package.Include)) {
continue
}
$packages += @{ PackageId = $package.include; Version = "$($package.version)".Trim() }
}
$csproj.Packages = $packages
Write-Verbose "$logLead : Categorize semver"
# TODO: Recurse if not found on the base to look for tools\sem.ver a-la SDK widgets
# Record the path if not found on the project root
$semVerPath = $files.Where({$_.Name -eq "sem.ver"}).FullName
if (![string]::IsNullOrWhiteSpace($semVerPath)) {
if (Test-Path -Path $semVerPath) {
$semverValueRaw = $null
$semverValueVersion = $null
try {
$semverValueRaw = (ConvertFrom-Json (Get-Content -Path $semVerPath -Raw))
if (($null -ne $semverValueRaw) -and ($null -ne $semverValueRaw.Version)) {
$semverValueVersion = "$($semverValueRaw.Version.Major).$($semverValueRaw.Version.Minor).$($semverValueRaw.Version.Patch)"
}
$csproj.SemVer = @{
Version = $semverValueVersion
Raw = $semverValueRaw
}
} catch {
Write-Warning "$logLead : Could not capture the semver from [$semverPath]"
}
}
}
#region look for specific nuget packages
Write-Verbose "$logLead : Categorize nuget packages"
$newRelicAgentApiPackage = $packages.Where({ $_.PackageId.StartsWith('NewRelic.Agent.Api', [System.StringComparison]::InvariantCultureIgnoreCase) })
if (Any $newRelicAgentApiPackage) {
$csproj.NewRelicAgentApi = @{
Version = @($newRelicAgentApiPackage)[0].Version
}
}
$msCore = $packages.Where({ $_.PackageId.Equals('Alkami.MicroServices.Core', [System.StringComparison]::InvariantCultureIgnoreCase) })
if (Any $msCore) {
$csproj.MicroservicesCore = @{
Version = @($msCore)[0].Version
}
}
$fluentMigratorPackage = $packages.Where({ $_.PackageId.StartsWith('FluentMigrator', [System.StringComparison]::InvariantCultureIgnoreCase) })
if (Any $fluentMigratorPackage) {
$csproj.FluentMigrator = @{
# only take the first one found, because there could be .Runner, .Tools, etc
Version = @($fluentMigratorPackage)[0].Version
}
}
$topshelfPackage = $packages.Where({ $_.PackageId.StartsWith('TopShelf', [System.StringComparison]::InvariantCultureIgnoreCase) })
if (Any $topshelfPackage) {
$csproj.TopShelf = @{
Version = @($topshelfPackage.Version)[0].Version
}
}
# Ends in .Tests?
foreach ($testFrameworkPrefix in @('NUnit', 'XUnit', 'MSTest')) {
$projectHasTestReferences = $packages.Where({ $_.PackageId.StartsWith($testFrameworkPrefix, [System.StringComparison]::InvariantCultureIgnoreCase) })
$csproj.HasTestReferences = Any $projectHasTestReferences
if ($csproj.HasTestReferences) {
break
}
}
#endregion look for specific nuget packages
#region look for manifest details
Write-Verbose "$logLead : Categorize Manifest"
$projectUsesLegacyServiceInstaller = $packages.Where({ $_.PackageId.StartsWith('Alkami.MicroServices.Installer', [System.StringComparison]::InvariantCultureIgnoreCase) })
if (Any $projectUsesLegacyServiceInstaller) {
$csproj.AlkamiManifest.ComponentType = "Service"
$csproj.AlkamiManifest.HasAlkamiManifest = $false
$csproj.ServiceInstaller = @{
Name = $projectUsesLegacyServiceInstaller.PackageId
Version = $projectUsesLegacyServiceInstaller.Version
IsMaster = $projectUsesLegacyServiceInstaller.PackageId -match 'Master'
Legacy = $true
}
$csproj.AlkamiManifest.IsLegacyInstaller = $true
}
# cover the case of .json or .yaml
# Take the first one we find
$projectHasAlkamiManifestPath = @($files.Where({ $_.Name.StartsWith("AlkamiManifest") -and ($_.Name.ToLower().IndexOf("explain") -eq -1) }).FullName)[0]
$projectHasAlkamiManifest = ![string]::IsNullOrWhiteSpace($projectHasAlkamiManifestPath)
if ($projectHasAlkamiManifest) {
try {
$packageManifest = (Get-PackageManifest -Path $projectHasAlkamiManifestPath)
$csproj.AlkamiManifest.ComponentType = $packageManifest.general.componentType
$csproj.AlkamiManifest.AlkamiManifest = $packageManifest
$csproj.AlkamiManifest.HasAlkamiManifest = $true
} catch {
Write-Verbose "Something failed to process on [$projectHasAlkamiManifestPath]"
}
} else {
if (!$projectUsesLegacyServiceInstaller) {
$componentType = ''
# try to determine what type of project it should be
# TODO: Shorten the lookup by consolidating left of pipe
$providers = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(ConnectorBase|ProviderBase)\b" | Select-Object -Property Path
$webExtensions = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(IAlkamiWebExtension|IAlkamiModule)\b" | Select-Object -Property Path
$widgets = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "public class .+:\s+(.+WidgetDescription|.+AppRegistration)\b" | Select-Object -Property Path
if ($null -ne $providers) {
$componentType = 'Provider'
}
if ($null -ne $widgets) {
$componentType = 'Widget'
}
if ($null -ne $webExtensions) {
$componentType = 'WebExtension'
}
if ([string]::IsNullOrWhiteSpace($componentType)) {
$csProj.HasProgramMain = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "(public|private)\s?.*\s+Main\s*\(" | Select-Object -Property Path
}
$csproj.AlkamiManifest.ComponentType = $componentType
$csproj.AlkamiManifest.IsUnkownInstaller = $true
}
}
$csProj.ServicePointManagerLines = Get-ChildItem -Path (Join-Path -Path $baseFolder -ChildPath "*.cs") -Recurse | Select-String -Pattern "ServicePointManager" | Where-Object { -not $_.Line.Trim().StartsWith("//") } | ForEach-Object { "$($_.Path):$($_.LineNumber) :: $($_.Line)" }
Write-Verbose "$logLead : Categorize tools"
$toolsPath = Join-Path -Path $baseFolder -ChildPath "Tools"
if (Test-Path -Path $toolsPath) {
$csproj.Tools = @{
Files = (Get-ChildItem -Path $toolsPath -File).Name
}
if ($csproj.Tools.Files -match 'choco.*\.ps1') {
$csproj.ChocoFiles.HasChocoFiles = $true
}
$chocolateyInstallPath = Join-Path -Path $toolsPath -ChildPath "chocolateyInstall.ps1"
$chocolateyUninstallPath = Join-Path -Path $toolsPath -ChildPath "chocolateyUninstall.ps1"
if (Test-Path -Path $chocolateyInstallPath) {
$csproj.ChocoFiles.InstallFileSize = (Get-Item -Path $chocolateyInstallPath).Length
if ((Get-Item -Path $chocolateyInstallPath).Length -lt $magicChocoFilesLengthValue) {
$csproj.ChocoFiles.InstallFileNew = $true
}
}
if (Test-Path -Path $chocolateyUninstallPath) {
$csproj.ChocoFiles.UninstallFileSize = (Get-Item -Path $chocolateyUninstallPath).Length
if ($csproj.ChocoFiles.UninstallFileSize -lt $magicChocoFilesLengthValue) {
$csproj.ChocoFiles.UninstallFileNew = $true
}
}
}
#endregion look for manifest details
# above checks tools folder, this checks root folder, solution level folder check is elsewhere
$chocolateyInstallPath = Join-Path -Path $baseFolder -ChildPath "chocolateyInstall.ps1"
$chocolateyUninstallPath = Join-Path -Path $baseFolder -ChildPath "chocolateyUninstall.ps1"
if (Test-Path -Path $chocolateyInstallPath) {
$csproj.ChocoFiles.InstallFileSize = (Get-Item -Path $chocolateyInstallPath).Length
if ((Get-Item -Path $chocolateyInstallPath).Length -lt $magicChocoFilesLengthValue) {
$csproj.ChocoFiles.InstallFileNew = $true
}
}
if (Test-Path -Path $chocolateyUninstallPath) {
$csproj.ChocoFiles.UninstallFileSize = (Get-Item -Path $chocolateyUninstallPath).Length
if ($csproj.ChocoFiles.UninstallFileSize -lt $magicChocoFilesLengthValue) {
$csproj.ChocoFiles.UninstallFileNew = $true
}
}
$csproj.ShouldHaveAManifestedInstaller = (
($csproj.Nuspec.IsChocolatey -and -not $csproj.AlkamiManifest.HasAlkamiManifest) -or
$true -eq $csproj.ServiceInstaller.Legacy -or
$csproj.AlkamiManifest.IsLegacyInstaller
)
# TODO: Tools folder nuspec?
$nuspecPath = @($files.Where({$_.Name.EndsWith(".nuspec")}).FullName)[0]
if (![string]::IsNullOrWhiteSpace($nuspecPath)) {
$nuspecContent = [xml](Get-Content $nuspecPath)
$projectNuspecId = $nuspecContent.package.metadata.id
$projectNuspecVersion = $nuspecContent.package.metadata.version
# If it doesn't have a chocolatey include, it's a problem
$nuspecIsChocolatey = ($nuspecContent.package.files.file.src.EndsWith('chocolateyInstall.ps1').where({$_}).Count -gt 0)
$csproj.Nuspec.HasNuspecFile = $true
$csproj.Nuspec.IsChocolatey = $nuspecIsChocolatey
$csproj.Nuspec.PackageId = $projectNuspecId
$csproj.Nuspec.Version = $projectNuspecVersion
$csproj.Nuspec.Raw = $nuspecContent
}
return (New-Object -TypeName PSCustomObject -Property $csproj)
}