diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..429fc4e --- /dev/null +++ b/modules.json @@ -0,0 +1,14 @@ +{ + "dependencies": {}, + "devDependencies": { + "Pester": { + "version": "4.10.1" + }, + "platyPS": { + "version": "0.14.2" + }, + "PSScriptAnalyzer": { + "version": "1.21.0" + } + } +} diff --git a/pipeline.build.ps1 b/pipeline.build.ps1 index 633e61e..c22a321 100644 --- a/pipeline.build.ps1 +++ b/pipeline.build.ps1 @@ -126,7 +126,7 @@ task CopyModule { task BuildModule BuildDotNet, CopyModule, VersionModule # Synopsis: Build help -task BuildHelp BuildModule, PlatyPS, { +task BuildHelp BuildModule, Dependencies, { # Avoid YamlDotNet issue in same app domain exec { $pwshPath = (Get-Process -Id $PID).Path; @@ -177,18 +177,6 @@ task VersionModule { } } -task ReleaseModule VersionModule, { - $modulePath = (Join-Path -Path $ArtifactPath -ChildPath 'PSDocs'); - Write-Verbose -Message "[ReleaseModule] -- Checking module path: $modulePath"; - - if (!(Test-Path -Path $modulePath)) { - Write-Error -Message "[ReleaseModule] -- Module path does not exist"; - } - elseif (![String]::IsNullOrEmpty($ApiKey)) { - Publish-Module -Path $modulePath -NuGetApiKey $ApiKey; - } -} - # Synopsis: Install NuGet provider task NuGet { if ($Null -eq (Get-PackageProvider -Name NuGet -ErrorAction Ignore)) { @@ -196,31 +184,15 @@ task NuGet { } } -# Synopsis: Install Pester module -task Pester NuGet, { - if ($Null -eq (Get-InstalledModule -Name Pester -RequiredVersion 4.10.1 -ErrorAction Ignore)) { - Install-Module -Name Pester -RequiredVersion 4.10.1 -Scope CurrentUser -Force -SkipPublisherCheck; - } - Import-Module -Name Pester -RequiredVersion 4.10.1 -Verbose:$False; -} - -# Synopsis: Install PSScriptAnalyzer module -task PSScriptAnalyzer NuGet, { - if ($Null -eq (Get-InstalledModule -Name PSScriptAnalyzer -MinimumVersion 1.18.1 -ErrorAction Ignore)) { - Install-Module -Name PSScriptAnalyzer -MinimumVersion 1.18.1 -Scope CurrentUser -Force; - } - Import-Module -Name PSScriptAnalyzer -Verbose:$False; -} - -# Synopsis: Install PlatyPS module -task platyPS { - if ($Null -eq (Get-InstalledModule -Name PlatyPS -MinimumVersion 0.14.0 -ErrorAction Ignore)) { - Install-Module -Name PlatyPS -Scope CurrentUser -MinimumVersion 0.14.0 -Force; - } +task Dependencies NuGet, { + Import-Module $PWD/scripts/dependencies.psm1; + Install-Dependencies -Path $PWD/modules.json -Dev; } # Synopsis: Test the module -task TestModule Pester, PSScriptAnalyzer, { +task TestModule Dependencies, { + Import-Module Pester -RequiredVersion 4.10.1 -Force; + # Run Pester tests $pesterParams = @{ Path = $PWD; OutputFile = 'reports/pester-unit.xml'; OutputFormat = 'NUnitXml'; PesterOption = @{ IncludeVSCodeMarker = $True }; PassThru = $True; }; @@ -255,17 +227,10 @@ task Benchmark { } # Synopsis: Run script analyzer -task Analyze Build, PSScriptAnalyzer, { +task Analyze Build, Dependencies, { Invoke-ScriptAnalyzer -Path out/modules/PSDocs; } -# Synopsis: Add shipit build tag -task TagBuild { - if ($Null -ne $Env:BUILD_DEFINITIONNAME) { - Write-Host "`#`#vso[build.addbuildtag]shipit"; - } -} - # Synopsis: Remove temp files. task Clean { Remove-Item -Path out,reports -Recurse -Force -ErrorAction SilentlyContinue; @@ -275,7 +240,5 @@ task Build Clean, BuildModule, VersionModule, BuildHelp task Test Build, TestDotNet, TestModule -task Release ReleaseModule, TagBuild - # Synopsis: Build and test. Entry point for CI Build stage task . Build, TestDotNet diff --git a/scripts/dependencies.psm1 b/scripts/dependencies.psm1 new file mode 100644 index 0000000..5e230e8 --- /dev/null +++ b/scripts/dependencies.psm1 @@ -0,0 +1,152 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Note: +# Handles dependencies updates. + +function Update-Dependencies { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $False)] + [String]$Path = (Join-Path -Path $PWD -ChildPath 'modules.json'), + + [Parameter(Mandatory = $False)] + [String]$Repository = 'PSGallery' + ) + process { + $modules = Get-Content -Path $Path -Raw | ConvertFrom-Json -AsHashtable; + $dependencies = CheckVersion $modules.dependencies -Repository $Repository; + $devDependencies = CheckVersion $modules.devDependencies -Repository $Repository -Dev; + + $modules = [Ordered]@{ + dependencies = $dependencies + devDependencies = $devDependencies + } + $modules | ConvertTo-Json -Depth 10 | Set-Content -Path $Path; + + $updates = @(git status --porcelain); + if ($Null -ne $Env:WORKING_BRANCH -and $Null -ne $updates -and $updates.Length -gt 0) { + git add modules.json; + git commit -m "Update $path"; + git push --force -u origin $Env:WORKING_BRANCH; + + $existingBranch = @(gh pr list --head $Env:WORKING_BRANCH --state open --json number | ConvertFrom-Json); + if ($Null -eq $existingBranch -or $existingBranch.Length -eq 0) { + gh pr create -B 'main' -H $Env:WORKING_BRANCH -l 'dependencies' -t 'Bump PowerShell dependencies' -F 'out/updates.txt'; + } + else { + $pr = $existingBranch[0].number + gh pr edit $pr -F 'out/updates.txt'; + } + } + } +} + +function Install-Dependencies { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $False)] + [String]$Path = (Join-Path -Path $PWD -ChildPath 'modules.json'), + + [Parameter(Mandatory = $False)] + [String]$Repository = 'PSGallery', + + [Parameter(Mandatory = $False)] + [Switch]$Dev + ) + process { + $modules = Get-Content -Path $Path -Raw | ConvertFrom-Json; + InstallVersion $modules.dependencies -Repository $Repository; + if ($Dev) { + InstallVersion $modules.devDependencies -Repository $Repository -Dev; + } + } +} + +function CheckVersion { + [CmdletBinding()] + [OutputType([System.Collections.Specialized.OrderedDictionary])] + param ( + [Parameter(Mandatory = $True)] + [Hashtable]$InputObject, + + [Parameter(Mandatory = $True)] + [String]$Repository, + + [Parameter(Mandatory = $False)] + [Switch]$Dev, + + [Parameter(Mandatory = $False)] + [String]$OutputPath = 'out/' + ) + begin { + $group = 'Dependencies'; + if ($Dev) { + $group = 'DevDependencies'; + } + if (!(Test-Path -Path $OutputPath)) { + $Null = New-Item -Path $OutputPath -ItemType Directory -Force; + } + $changeNotes = Join-Path -Path $OutputPath -ChildPath 'updates.txt'; + } + process { + $dependencies = [Ordered]@{ }; + $InputObject.GetEnumerator() | Sort-Object -Property Name | ForEach-Object { + $dependencies[$_.Name] = $_.Value + } + foreach ($module in $dependencies.GetEnumerator()) { + Write-Host -Object "[$group] -- Checking $($module.Name)"; + $installParams = @{} + $installParams += $module.Value; + $installParams.MinimumVersion = $installParams.version; + $installParams.Remove('version'); + $available = @(Find-Module -Repository $Repository -Name $module.Name @installParams -ErrorAction Ignore); + foreach ($found in $available) { + if (([Version]$found.Version) -gt ([Version]$module.Value.version)) { + Write-Host -Object "[$group] -- Newer version found $($found.Version)"; + $dependencies[$module.Name].version = $found.Version; + $Null = Add-Content -Path $changeNotes -Value "Bump $($module.Name) to v$($found.Version)."; + } + else { + Write-Host -Object "[$group] -- Already up to date."; + } + } + } + return $dependencies; + } +} + +function InstallVersion { + [CmdletBinding()] + [OutputType([void])] + param ( + [Parameter(Mandatory = $True)] + [PSObject]$InputObject, + + [Parameter(Mandatory = $True)] + [String]$Repository, + + [Parameter(Mandatory = $False)] + [Switch]$Dev + ) + begin { + $group = 'Dependencies'; + if ($Dev) { + $group = 'DevDependencies'; + } + } + process { + foreach ($module in $InputObject.PSObject.Properties.GetEnumerator()) { + Write-Host -Object "[$group] -- Installing $($module.Name) v$($module.Value.version)"; + $installParams = @{ RequiredVersion = $module.Value.version }; + if ($Null -eq (Get-InstalledModule -Name $module.Name @installParams -ErrorAction Ignore)) { + Install-Module -Name $module.Name @installParams -Force -Repository $Repository; + } + } + } +} + +Export-ModuleMember -Function @( + 'Update-Dependencies' + 'Install-Dependencies' +)