build.ps1

#Requires -Modules BuildHelpers,pester
<#
.SYNOPSIS
Build tasks
.PARAMETER Lint
Run static analysis on source code with PSScriptAnalyzer. -Lint can be used with -Test
.PARAMETER Test
Run PowerShell unit tests. -Test can be used with -Lint
.PARAMETER Benchmark
Run C# benchmarks using BenchmarkDotNet
.PARAMETER Filter
Only run tests (Describe or It) that match filter string (-like wildcards allowed)
.PARAMETER Tags
Only run tests (Describe or It) with certain tags
.PARAMETER Exclude
Exclude running tests (Describe or It) with certain tags
.EXAMPLE
.\build.ps1 -Test -Filter '*Readability*' -Exclude 'LinuxOnly'
.EXAMPLE
.\build.ps1 -Test -Filter '*Readability*' -Tags 'WindowsOnly'
.EXAMPLE
.\build.ps1 -CI -Test -Tags 'Remote' -Exclude 'LinuxOnly' -WithCoverage
.EXAMPLE
.\build.ps1 -Test -WithCoverage -Skip dotnet
#>

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('AdvancedFunctionHelpContent', '')]
[CmdletBinding()]
Param(
  [Switch] $Lint,
  [Switch] $Test,
  [Switch] $Benchmark,
  [Switch] $WithCoverage,
  [Switch] $ShowCoverageReport,
  [Switch] $CI,
  [ValidateSet('dotnet', 'powershell')]
  [String[]] $Skip,
  [Switch] $BuildOnly,
  [String] $Filter,
  [String[]] $Tags,
  [String] $Exclude = '',
  [Switch] $DryRun,
  [Switch] $Major,
  [Switch] $Minor
)
$Prefix = if ($DryRun) { '[DRYRUN] ' } else { '' }
$SourceDirectory = 'src'
function Invoke-Lint {
  [CmdletBinding()]
  Param()
  '==> Linting code' | Write-Output
  $Parameters = @{
    Path = $PSScriptRoot
    Settings = 'PSScriptAnalyzerSettings.psd1'
    Fix = $True
    EnableExit = $CI
    ReportSummary = $True
    Recurse = $True
  }
  Invoke-ScriptAnalyzer @Parameters
  "`n" | Write-Output
}
function Invoke-Test {
  [CmdletBinding()]
  Param()
  if (-not ($Skip -contains 'dotnet')) {
    $Message = if ($CI) { "==> Executing C# tests on $Env:BuildSystem" } else { '==> Executing C# tests' }
    $Message | Write-Output
    $ProjectPath = "$PSScriptRoot/src/cs/Tests/Tests.csproj"
    if ($WithCoverage) {
      dotnet test $ProjectPath /p:CollectCoverage=true /p:CoverletOutput=coverage.xml /p:CoverletOutputFormat=opencover
    } else {
      dotnet test $ProjectPath --logger:'console;verbosity=detailed'
    }
    "`n`n" | Write-Output
  }
  if (-not ($Skip -contains 'powershell')) {
    $Files = (Get-ChildItem (Join-Path $PSScriptRoot $SourceDirectory) -Recurse -Include '*.ps1').FullName
    $Configuration = [PesterConfiguration]@{
      Run = @{ PassThru = $True }
      Filter = @{ ExcludeTag = $Exclude }
      Debug = @{ ShowNavigationMarkers = $True; WriteVSCodeMarker = $True }
    }
    if ($Filter) {
      $Configuration.Filter.FullName = $Filter
    } elseif ($Tags) {
      $Configuration.Filter.Tag = $Tags
    }
    if ($WithCoverage) {
      $Configuration.CodeCoverage = @{ Enabled = $True; Path = $Files }
      $Configuration.TestResult = @{ Enabled = $False }
    }
    if ($CI) {
      Set-BuildEnvironment -VariableNamePrefix 'Prelude' -Force
      "==> Executing PowerShell tests on $Env:PreludeBuildSystem" | Write-Output
    } else {
      '==> Executing PowerShell tests' | Write-Output
    }
    $Result = Invoke-Pester -Configuration $Configuration
    if ($Result.FailedCount -gt 0) {
      $FailedMessage = "==> FAILED - $($Result.FailedCount) PowerShell test(s) failed"
      throw $FailedMessage
    } else {
      "`nPowerShell SUCCESS`n" | Write-Output
    }
  }
}
function Invoke-Publish {
  [CmdletBinding()]
  Param()
  Set-BuildEnvironment -VariableNamePrefix 'Prelude' -Force
  $ProjectManifestPath = "$Env:PreludeProjectPath/Prelude.psd1"
  $ValidateManifest = if (Test-ModuleManifest -Path $ProjectManifestPath) { 'Manifest is Valid' } else { 'Manifest is NOT Valid' }
  $ValidateApiKey = if ((Write-Output $Env:NUGET_API_KEY).Length -eq 46) { 'API Key is Valid' } else { 'API Key is NOT Valid' }
  "${Prefix}==> $ValidateManifest" | Write-Output
  "${Prefix}==> $ValidateApiKey" | Write-Output
  $Increment = if ($Major) {
    'Major'
  } elseif ($Minor) {
    'Minor'
  } else {
    'Build'
  }
  if (-not $DryRun) {
    "Updating Module $(${Increment}.ToUpper()) Version..." | Write-Output
    Update-Metadata $ProjectManifestPath -Increment $Increment
    'Publishing module...' | Write-Output
    Publish-Module -Path (Get-Location) -NuGetApiKey $Env:NUGET_API_KEY -SkipAutomaticTags -Verbose
    "`n==> DONE`n" | Write-Output
  } else {
    "${Prefix}Updating Module $(${Increment}.ToUpper()) Version..." | Write-Output
    "${Prefix}Publishing module..." | Write-Output
    "${Prefix}==> DONE" | Write-Output
  }
}
if ($Benchmark) {
  '==> Running C# Benchmarks' | Write-Output
  $ProjectPath = "$PSScriptRoot/src/cs/Performance/Performance.csproj"
  dotnet run --project $ProjectPath --configuration 'Release'
} else {
  if ($Lint -and -not $BuildOnly) {
    Invoke-Lint
  }
  if ($Test -and -not $BuildOnly) {
    Invoke-Test
  }
  if ($ShowCoverageReport) {
    $ReportTypes = 'Html;HtmlSummary;HtmlChart'
    if ($Test -and $WithCoverage) {
      $SourceDirs = "$SourceDirectory"
      reportgenerator.exe -reports:'**/coverage.xml' -targetdir:coverage -sourcedirs:$SourceDirs -historydir:.history -reporttypes:$ReportTypes
    } else {
      reportgenerator.exe -reports:'**/coverage.xml' -targetdir:coverage -sourcedirs:$SourceDirs -reporttypes:$ReportTypes
    }
    Invoke-Item './coverage/index.htm'
  }
  if (-not $Lint -and -not $Test -and -not $ShowCoverageReport) {
    if (-not $BuildOnly -and -not $DryRun) {
      Invoke-Lint
      Invoke-Test
    }
    Invoke-Publish
  }
}