diff --git a/.build/tasks/Changelog.changelogmanagement.build.ps1 b/.build/tasks/Changelog.changelogmanagement.build.ps1 new file mode 100644 index 00000000..8ecad39c --- /dev/null +++ b/.build/tasks/Changelog.changelogmanagement.build.ps1 @@ -0,0 +1,197 @@ +<# + .SYNOPSIS + Tasks for releasing modules. + + .PARAMETER OutputDirectory + The base directory of all output. Defaults to folder 'output' relative to + the $BuildRoot. + + .PARAMETER BuiltModuleSubdirectory + The parent path of the module to be built. + + .PARAMETER VersionedOutputDirectory + If the module should be built using a version folder, e.g. ./MyModule/1.0.0. + Defaults to $true. + + .PARAMETER ChangelogPath + The path to and the name of the changelog file. Defaults to 'CHANGELOG.md'. + + .PARAMETER ReleaseNotesPath + The path to and the name of the release notes file. Defaults to 'ReleaseNotes.md'. + + .PARAMETER ProjectName + The project name. + + .PARAMETER ModuleVersion + The module version that was built. + + .PARAMETER GalleryApiToken + The API token that gives permission to publish to the gallery. + + .PARAMETER NuGetPublishSource + The source to publish nuget packages. Defaults to https://www.powershellgallery.com. + + .PARAMETER PSModuleFeed + The name of the feed (repository) that is passed to command Publish-Module. + Defaults to 'PSGallery'. + + .PARAMETER SkipPublish + If publishing should be skipped. Defaults to $false. + + .PARAMETER PublishModuleWhatIf + If the publish command will be run with '-WhatIf' to show what will happen + during publishing. Defaults to $false. +#> + +param +( + [Parameter()] + [string] + $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')), + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $VersionedOutputDirectory = (property VersionedOutputDirectory $true), + + [Parameter()] + $ChangelogPath = (property ChangelogPath 'CHANGELOG.md'), + + [Parameter()] + $ReleaseNotesPath = (property ReleaseNotesPath (Join-Path $OutputDirectory 'ReleaseNotes.md')), + + [Parameter()] + [string] + $ProjectName = (property ProjectName ''), + + [Parameter()] + [System.String] + $ModuleVersion = (property ModuleVersion ''), + + [Parameter()] + [string] + $GalleryApiToken = (property GalleryApiToken ''), + + [Parameter()] + [string] + $NuGetPublishSource = (property NuGetPublishSource 'https://www.powershellgallery.com/'), + + [Parameter()] + $PSModuleFeed = (property PSModuleFeed 'PSGallery'), + + [Parameter()] + $SkipPublish = (property SkipPublish ''), + + [Parameter()] + $PublishModuleWhatIf = (property PublishModuleWhatIf '') +) + +# Synopsis: Create ReleaseNotes from changelog and update the Changelog for release +task Create_changelog_release_output { + # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. + . Set-SamplerTaskVariable + + $ChangeLogOutputPath = Get-SamplerAbsolutePath -Path 'CHANGELOG.md' -RelativeTo $OutputDirectory + + "`tChangeLogOutputPath = '$ChangeLogOutputPath'" + + # Parse the Changelog and extract unreleased + try + { + Import-Module ChangelogManagement -ErrorAction Stop + + # Update the source changelog file + Write-Build DarkGray "`tCreating '$ChangeLogOutputPath'..." + Update-Changelog -Path $ChangeLogPath -OutputPath $ChangeLogOutputPath -ErrorAction Stop -ReleaseVersion $ModuleVersion -LinkMode none + + # Get the updated CHANGELOG.md + $changeLogData = Get-ChangelogData -Path $ChangeLogOutputPath + + # Filter out the latest module version change log entries + $changeLogDataForLatestRelease = $changeLogData.Released | Where-Object -FilterScript { + $_.Version -eq $ModuleVersion + } + + <# + Get the raw markdown release notes for the module manifest. The + module manifest release notes has a hard size limit when publishing + to PowerShell Gallery. + #> + if ($changeLogDataForLatestRelease.RawData.Length -gt 10000) + { + $moduleManifestReleaseNotes = $changeLogDataForLatestRelease.RawData.Substring(0, 10000) + } + else + { + $moduleManifestReleaseNotes = $changeLogDataForLatestRelease.RawData + } + + # Create a ReleaseNotes from the Updated changelog + ConvertFrom-Changelog -Path $ChangeLogOutputPath -Format Release -NoHeader -OutputPath $ReleaseNotesPath -ErrorAction Stop + } + catch + { + Write-Build Red "Error creating the Changelog Output and/or ReleaseNotes. $($_.Exception.Message)" + } + + if (-not ($ReleaseNotes = (Get-Content -raw $ReleaseNotesPath -ErrorAction SilentlyContinue))) + { + $ReleaseNotes = Get-Content -raw $ChangeLogOutputPath -ErrorAction SilentlyContinue + } + + if ($ReleaseNotes -and -not [string]::IsNullOrEmpty($builtModuleManifest) -and (Test-Path -Path $builtModuleManifest -ErrorAction SilentlyContinue)) + { + try + { + Import-Module Configuration -ErrorAction Stop + } + catch + { + Write-Build Red "Issue importing Configuration module. $($_.Exception.Message)" + return + } + + Write-Build DarkGray "Built Manifest $builtModuleManifest" + # No need to test the manifest again here, because the pipeline tested all manifests via the where-clause already + + # Uncomment release notes (the default in Plaster/New-ModuleManifest) + $ManifestString = Get-Content -raw $builtModuleManifest + if ( $ManifestString -match '#\sReleaseNotes\s?=') + { + $ManifestString = $ManifestString -replace '#\sReleaseNotes\s?=', ' ReleaseNotes =' + $Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($False) + [System.IO.File]::WriteAllLines($BuiltModuleManifest, $ManifestString, $Utf8NoBomEncoding) + } + + $UpdateReleaseNotesParams = @{ + Path = $builtModuleManifest + PropertyName = 'PrivateData.PSData.ReleaseNotes' + Value = $moduleManifestReleaseNotes + ErrorAction = 'SilentlyContinue' + } + + Update-Manifest @UpdateReleaseNotesParams + } + else + { + if ([string]::IsNullOrEmpty($ReleaseNotes)) + { + Write-Build -Color Red "No Release notes found to insert." + } + + if ([string]::IsNullOrEmpty($builtModuleManifest) -or -not (Test-Path -Path $builtModuleManifest)) + { + if ([string]::IsNullOrEmpty($ProjectName)) + { + Write-Build -Color DarkGray "No PowerShell module name found. We assume you are not building a PowerShell Module." + } + else + { + Write-Build -Color Red "No valid manifest found for project '$ProjectName'. Cannot update the Release Notes." + } + } + } +} diff --git a/.build/tasks/Create_Changelog_Branch.build.ps1 b/.build/tasks/Create_Changelog_Branch.build.ps1 index 58e699c5..7e376db9 100644 --- a/.build/tasks/Create_Changelog_Branch.build.ps1 +++ b/.build/tasks/Create_Changelog_Branch.build.ps1 @@ -166,7 +166,7 @@ task Create_Changelog_Branch { } # Track this branch on the remote 'origin - $pullArguments += @('-c', 'http.sslbackend="schannel"', 'pull', 'origin', $MainGitBranch, '--tag') + $pullArguments += @('-c', 'http.sslbackend=schannel', 'pull', 'origin', $MainGitBranch, '--tag') Sampler\Invoke-SamplerGit -Argument $pullArguments @@ -238,7 +238,7 @@ task Create_Changelog_Branch { } # Track this branch on the remote 'origin - $pushArguments += @('-c', 'http.sslbackend="schannel"', 'push', '-u', 'origin', $BranchName) + $pushArguments += @('-c', 'http.sslbackend=schannel', 'push', '-u', 'origin', $BranchName) Sampler\Invoke-SamplerGit -Argument $pushArguments diff --git a/.build/tasks/Create_Release_Git_Tag.build.ps1 b/.build/tasks/Create_Release_Git_Tag.build.ps1 index 7591bcf3..3a64b76d 100644 --- a/.build/tasks/Create_Release_Git_Tag.build.ps1 +++ b/.build/tasks/Create_Release_Git_Tag.build.ps1 @@ -175,7 +175,7 @@ task Create_Release_Git_Tag { $pushArguments += @('-c', ('http.extraheader="AUTHORIZATION: basic {0}"' -f $patBase64)) } - $pushArguments += @('-c', 'http.sslbackend="schannel"', 'push', 'origin', '--tags') + $pushArguments += @('-c', 'http.sslbackend=schannel', 'push', 'origin', '--tags') Sampler\Invoke-SamplerGit -Argument $pushArguments diff --git a/.build/tasks/DeployAll.PSDeploy.build.ps1 b/.build/tasks/DeployAll.PSDeploy.build.ps1 index 674339cb..b2f91523 100644 --- a/.build/tasks/DeployAll.PSDeploy.build.ps1 +++ b/.build/tasks/DeployAll.PSDeploy.build.ps1 @@ -16,6 +16,7 @@ param ( # Synopsis: Deploy everything configured in PSDeploy task Deploy_with_PSDeploy { + Write-Warning -Message 'DEPRECATED: Support for PSDeploy will be removed in a future Sampler release.' if ([System.String]::IsNullOrEmpty($ProjectName)) { $ProjectName = Get-SamplerProjectName -BuildRoot $BuildRoot diff --git a/.build/tasks/GitVersion.build.ps1 b/.build/tasks/GitVersion.build.ps1 new file mode 100644 index 00000000..ae19d7bf --- /dev/null +++ b/.build/tasks/GitVersion.build.ps1 @@ -0,0 +1,61 @@ +task GitVersion -if (Get-Command -Name dotnet-gitversion.exe, gitversion.exe -ErrorAction SilentlyContinue) { + + $command = if (Get-Command -Name gitversion.exe -ErrorAction SilentlyContinue) + { + Write-Host 'Using gitversion.exe...' + 'gitversion.exe' + } + elseif (Get-Command -Name dotnet-gitversion -ErrorAction SilentlyContinue) + { + Write-Host 'Using dotnet-gitversion...' + 'dotnet-gitversion' + } + else + { + Write-Error 'Neither gitversion.exe nor dotnet-gitversion is available.' + return + } + + $gitVersionObject = & $command + Write-Host -------------- GitVersion Outout -------------- + $gitVersionObject | Write-Host + Write-Host ----------------------------------------------- + + $gitVersionObject = $gitVersionObject | ConvertFrom-Json + $longestKeyLength = ($gitVersionObject | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Sort-Object { $_.Length } | Select-Object -Last 1).Length + $gitVersionObject.PSObject.Properties.ForEach{ + Write-Host -Object ("Setting Task Variable {0,-$longestKeyLength} with value '{1}'." -f $_.Name, $_.Value) + } + + $isPreRelease = [bool]$gitVersionObject.PreReleaseLabel + $versionElements = $gitVersionObject.MajorMinorPatch + + if ($isPreRelease) + { + if ($gitVersionObject.BranchName -eq 'main') + { + $nextPreReleaseNumber = $gitVersionObject.PreReleaseNumber + $paddedNextPreReleaseNumber = '{0:D4}' -f $nextPreReleaseNumber + + $versionElements += $gitVersionObject.PreReleaseLabelWithDash + $versionElements += $paddedNextPreReleaseNumber + } + else + { + $versionElements += $gitVersionObject.PreReleaseLabelWithDash + $versionElements += '.' + $gitVersionObject.CommitsSinceVersionSource + } + } + + $versionString = -join $versionElements + [System.Environment]::SetEnvironmentVariable('ModuleVersion', $versionString, 'Process') + + if ([string]::IsNullOrEmpty($env:TF_BUILD)) #when the code runs on an AzDo build agent, the TF_BUILD variable is set + { + Write-Host "Writing version string '$versionString' to build / environment variable 'VersionString'." + Write-Host "##vso[task.setvariable variable=VersionString;]$($versionString)" + + Write-Host "Updating build number to '$versionString'." + Write-Host "##vso[build.updatebuildnumber]$($versionString)" + } +} diff --git a/.build/tasks/publish.nuget.build.ps1 b/.build/tasks/publish.nuget.build.ps1 new file mode 100644 index 00000000..8b1347f3 --- /dev/null +++ b/.build/tasks/publish.nuget.build.ps1 @@ -0,0 +1,123 @@ +<# + .SYNOPSIS + Tasks for releasing modules. + + .PARAMETER OutputDirectory + The base directory of all output. Defaults to folder 'output' relative to + the $BuildRoot. + + .PARAMETER BuiltModuleSubdirectory + The parent path of the module to be built. + + .PARAMETER VersionedOutputDirectory + If the module should be built using a version folder, e.g. ./MyModule/1.0.0. + Defaults to $true. + + .PARAMETER ChangelogPath + The path to and the name of the changelog file. Defaults to 'CHANGELOG.md'. + + .PARAMETER ReleaseNotesPath + The path to and the name of the release notes file. Defaults to 'ReleaseNotes.md'. + + .PARAMETER ProjectName + The project name. + + .PARAMETER ModuleVersion + The module version that was built. + + .PARAMETER GalleryApiToken + The API token that gives permission to publish to the gallery. + + .PARAMETER NuGetPublishSource + The source to publish nuget packages. Defaults to https://www.powershellgallery.com. + + .PARAMETER PSModuleFeed + The name of the feed (repository) that is passed to command Publish-Module. + Defaults to 'PSGallery'. + + .PARAMETER SkipPublish + If publishing should be skipped. Defaults to $false. + + .PARAMETER PublishModuleWhatIf + If the publish command will be run with '-WhatIf' to show what will happen + during publishing. Defaults to $false. +#> + +param +( + [Parameter()] + [string] + $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')), + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $VersionedOutputDirectory = (property VersionedOutputDirectory $true), + + [Parameter()] + $ChangelogPath = (property ChangelogPath 'CHANGELOG.md'), + + [Parameter()] + $ReleaseNotesPath = (property ReleaseNotesPath (Join-Path $OutputDirectory 'ReleaseNotes.md')), + + [Parameter()] + [string] + $ProjectName = (property ProjectName ''), + + [Parameter()] + [System.String] + $ModuleVersion = (property ModuleVersion ''), + + [Parameter()] + [string] + $GalleryApiToken = (property GalleryApiToken ''), + + [Parameter()] + [string] + $NuGetPublishSource = (property NuGetPublishSource 'https://www.powershellgallery.com/'), + + [Parameter()] + $PSModuleFeed = (property PSModuleFeed 'PSGallery'), + + [Parameter()] + $SkipPublish = (property SkipPublish ''), + + [Parameter()] + $PublishModuleWhatIf = (property PublishModuleWhatIf '') +) + +# Synopsis: (dotnet) nuget push package to a gallery. +task publish_nupkg_to_gallery -if ($GalleryApiToken -and ((Get-Command -Name 'nuget' -ErrorAction 'SilentlyContinue') -or (Get-Command -Name 'dotnet' -ErrorAction 'SilentlyContinue'))) { + # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. + . Set-SamplerTaskVariable + + # find Module's nupkg + $PackageToRelease = Get-ChildItem -Path (Join-Path -Path $OutputDirectory -ChildPath "$ProjectName.$ModuleVersion.nupkg") + + Write-Build DarkGray "About to release $PackageToRelease" + if (-not $SkipPublish) + { + # Determine whether to use nuget or .NET SDK nuget + if (Get-Command -Name 'nuget' -ErrorAction 'SilentlyContinue') + { + Write-Build DarkGray 'Publishing package with nuget' + $command = 'nuget' + #TODO: Make the GalleryApiToken optional + $nugetArgs = @('push', $PackageToRelease, '-Source', $nugetPublishSource, '-ApiKey', $GalleryApiToken) + } + else + { + Write-Build DarkGray 'Publishing package with .NET SDK nuget' + $command = 'dotnet' + #TODO: Make the GalleryApiToken optional + $nugetArgs = @('nuget', 'push', $PackageToRelease, '--source', $nugetPublishSource, '--api-key', $GalleryApiToken) + } + + $response = & $command $nugetArgs + } + + Write-Build Green "Response = " + $response +} diff --git a/.build/tasks/release.module.build.ps1 b/.build/tasks/release.module.build.ps1 index b7596257..7e846010 100644 --- a/.build/tasks/release.module.build.ps1 +++ b/.build/tasks/release.module.build.ps1 @@ -99,149 +99,6 @@ param $ChocolateyBuildOutput = (property ChocolateyBuildOutput 'choco') ) -# Synopsis: Create ReleaseNotes from changelog and update the Changelog for release -task Create_changelog_release_output { - # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. - . Set-SamplerTaskVariable - - $ChangeLogOutputPath = Get-SamplerAbsolutePath -Path 'CHANGELOG.md' -RelativeTo $OutputDirectory - - "`tChangeLogOutputPath = '$ChangeLogOutputPath'" - - # Parse the Changelog and extract unreleased - try - { - Import-Module ChangelogManagement -ErrorAction Stop - - # Update the source changelog file - Write-Build DarkGray "`tCreating '$ChangeLogOutputPath'..." - Update-Changelog -Path $ChangeLogPath -OutputPath $ChangeLogOutputPath -ErrorAction Stop -ReleaseVersion $ModuleVersion -LinkMode none - - # Get the updated CHANGELOG.md - $changeLogData = Get-ChangelogData -Path $ChangeLogOutputPath - - # Filter out the latest module version change log entries - $changeLogDataForLatestRelease = $changeLogData.Released | Where-Object -FilterScript { - $_.Version -eq $ModuleVersion - } - - <# - Get the raw markdown release notes for the module manifest. The - module manifest release notes has a hard size limit when publishing - to PowerShell Gallery. - #> - if ($changeLogDataForLatestRelease.RawData.Length -gt 10000) - { - $moduleManifestReleaseNotes = $changeLogDataForLatestRelease.RawData.Substring(0, 10000) - } - else - { - $moduleManifestReleaseNotes = $changeLogDataForLatestRelease.RawData - } - - # Create a ReleaseNotes from the Updated changelog - ConvertFrom-Changelog -Path $ChangeLogOutputPath -Format Release -NoHeader -OutputPath $ReleaseNotesPath -ErrorAction Stop - } - catch - { - Write-Build Red "Error creating the Changelog Output and/or ReleaseNotes. $($_.Exception.Message)" - } - - if (-not ($ReleaseNotes = (Get-Content -raw $ReleaseNotesPath -ErrorAction SilentlyContinue))) - { - $ReleaseNotes = Get-Content -raw $ChangeLogOutputPath -ErrorAction SilentlyContinue - } - - if ($ReleaseNotes -and -not [string]::IsNullOrEmpty($builtModuleManifest) -and (Test-Path -Path $builtModuleManifest -ErrorAction SilentlyContinue)) - { - try - { - Import-Module Configuration -ErrorAction Stop - } - catch - { - Write-Build Red "Issue importing Configuration module. $($_.Exception.Message)" - return - } - - Write-Build DarkGray "Built Manifest $builtModuleManifest" - # No need to test the manifest again here, because the pipeline tested all manifests via the where-clause already - - # Uncomment release notes (the default in Plaster/New-ModuleManifest) - $ManifestString = Get-Content -raw $builtModuleManifest - if ( $ManifestString -match '#\sReleaseNotes\s?=') - { - $ManifestString = $ManifestString -replace '#\sReleaseNotes\s?=', ' ReleaseNotes =' - $Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($False) - [System.IO.File]::WriteAllLines($BuiltModuleManifest, $ManifestString, $Utf8NoBomEncoding) - } - - $UpdateReleaseNotesParams = @{ - Path = $builtModuleManifest - PropertyName = 'PrivateData.PSData.ReleaseNotes' - Value = $moduleManifestReleaseNotes - ErrorAction = 'SilentlyContinue' - } - - Update-Manifest @UpdateReleaseNotesParams - } - else - { - if ([string]::IsNullOrEmpty($ReleaseNotes)) - { - Write-Build -Color Red "No Release notes found to insert." - } - - if ([string]::IsNullOrEmpty($builtModuleManifest) -or -not (Test-Path -Path $builtModuleManifest)) - { - if ([string]::IsNullOrEmpty($ProjectName)) - { - Write-Build -Color DarkGray "No PowerShell module name found. We assume you are not building a PowerShell Module." - } - else - { - Write-Build -Color Red "No valid manifest found for project '$ProjectName'. Cannot update the Release Notes." - } - } - } -} - -# Synopsis: Publish Nuget package to a gallery. -task publish_nupkg_to_gallery -if ($GalleryApiToken -and ((Get-Command -Name 'nuget' -ErrorAction 'SilentlyContinue') -or (Get-Command -Name 'dotnet' -ErrorAction 'SilentlyContinue'))) { - # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. - . Set-SamplerTaskVariable - - Import-Module -Name 'ModuleBuilder' -ErrorAction 'Stop' - - $ChangeLogOutputPath = Join-Path -Path $OutputDirectory -ChildPath 'CHANGELOG.md' - - "`tChangeLogOutputPath = $ChangeLogOutputPath" - - # find Module's nupkg - $PackageToRelease = Get-ChildItem -Path (Join-Path -Path $OutputDirectory -ChildPath "$ProjectName.$ModuleVersion.nupkg") - - Write-Build DarkGray "About to release $PackageToRelease" - if (-not $SkipPublish) - { - # Determine whether to use nuget or .NET SDK nuget - if (Get-Command -Name 'nuget' -ErrorAction 'SilentlyContinue') - { - Write-Build DarkGray 'Publishing package with nuget' - $command = 'nuget' - $nugetArgs = @('push', $PackageToRelease, '-Source', $nugetPublishSource, '-ApiKey', $GalleryApiToken) - } - else - { - Write-Build DarkGray 'Publishing package with .NET SDK nuget' - $command = 'dotnet' - $nugetArgs = @('nuget', 'push', $PackageToRelease, '--source', $nugetPublishSource, '--api-key', $GalleryApiToken) - } - $response = & $command $nugetArgs - } - - Write-Build Green "Response = " + $response -} - # Synopsis: Packaging the module by Publishing to output folder (incl dependencies) task package_module_nupkg { # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. diff --git a/.build/tasks/release.psresource.build.ps1 b/.build/tasks/release.psresource.build.ps1 new file mode 100644 index 00000000..7384ce7a --- /dev/null +++ b/.build/tasks/release.psresource.build.ps1 @@ -0,0 +1,300 @@ +<# + .SYNOPSIS + Tasks for releasing modules. + + .PARAMETER OutputDirectory + The base directory of all output. Defaults to folder 'output' relative to + the $BuildRoot. + + .PARAMETER BuiltModuleSubdirectory + The parent path of the module to be built. + + .PARAMETER VersionedOutputDirectory + If the module should be built using a version folder, e.g. ./MyModule/1.0.0. + Defaults to $true. + + .PARAMETER ChangelogPath + The path to and the name of the changelog file. Defaults to 'CHANGELOG.md'. + + .PARAMETER ReleaseNotesPath + The path to and the name of the release notes file. Defaults to 'ReleaseNotes.md'. + + .PARAMETER ProjectName + The project name. + + .PARAMETER ModuleVersion + The module version that was built. + + .PARAMETER GalleryApiToken + The API token that gives permission to publish to the gallery. + + .PARAMETER NuGetPublishSource + The source to publish nuget packages. Defaults to https://www.powershellgallery.com. + + .PARAMETER PSModuleFeed + The name of the feed (repository) that is passed to command Publish-Module. + Defaults to 'PSGallery'. + + .PARAMETER SkipPublish + If publishing should be skipped. Defaults to $false. + + .PARAMETER PublishModuleWhatIf + If the publish command will be run with '-WhatIf' to show what will happen + during publishing. Defaults to $false. +#> + +param +( + [Parameter()] + [string] + $OutputDirectory = (property OutputDirectory (Join-Path $BuildRoot 'output')), + + [Parameter()] + [System.String] + $BuiltModuleSubdirectory = (property BuiltModuleSubdirectory ''), + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $VersionedOutputDirectory = (property VersionedOutputDirectory $true), + + [Parameter()] + $ChangelogPath = (property ChangelogPath 'CHANGELOG.md'), + + [Parameter()] + $ReleaseNotesPath = (property ReleaseNotesPath (Join-Path $OutputDirectory 'ReleaseNotes.md')), + + [Parameter()] + [string] + $ProjectName = (property ProjectName ''), + + [Parameter()] + [System.String] + $ModuleVersion = (property ModuleVersion ''), + + [Parameter()] + [string] + $GalleryApiToken = (property GalleryApiToken ''), + + [Parameter()] + [string] + $NuGetPublishSource = (property NuGetPublishSource 'https://www.powershellgallery.com/'), + + [Parameter()] + $PSModuleFeed = (property PSModuleFeed 'PSGallery'), + + [Parameter()] + $SkipPublish = (property SkipPublish ''), + + [Parameter()] + $PublishModuleWhatIf = (property PublishModuleWhatIf '') +) + +# Synopsis: Packaging the module by Publishing to output folder (incl dependencies) +task package_psresource_nupkg { + # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. + . Set-SamplerTaskVariable + + # If we fail to import PSResourceGet, we won't be able to continue + # This happens when PowerShellGet is already loaded + Import-Module -Name 'Microsoft.PowerShell.PSResourceGet' -ErrorAction 'Stop' + + #region Set output/ as PSResourceRepository + # Force registering the output repository mapping to the Project's output path + try + { + Write-Build DarkGray " Unregistering output repository." + $null = Unregister-PSResourceRepository -Name output -ErrorAction Ignore + } + catch + { + Write-Build Yellow " Output repository was not registered, skipping unregistering." + } + + + # Parse PublishModuleWhatIf to be boolean + $null = [bool]::TryParse($PublishModuleWhatIf, [ref]$script:PublishModuleWhatIf) + + $RepositoryParams = @{ + Name = 'output' + uri = $OutputDirectory + Trusted = $true + ErrorAction = 'Stop' + } + + $null = Register-PSResourceRepository @RepositoryParams + # Cleaning up existing packaged module + if ($ModuleToRemove = Get-ChildItem -Path (Join-Path -Path $OutputDirectory -ChildPath "$ProjectName.*.nupkg")) + { + Write-Build DarkGray " Removing existing $ProjectName package" + Remove-Item -force -Path $ModuleToRemove -ErrorAction Stop + } + #endregion + + if (-not $BuiltModuleManifest) + { + throw "No valid manifest found for project $ProjectName." + } + + Write-Build DarkGray " Built module's Manifest found at $BuiltModuleManifest" + + # Uncomment release notes (the default in Plaster/New-ModuleManifest) + $manifest = Get-SamplerModuleInfo -ModuleManifestPath $BuiltModuleManifest + + $manifestString = Get-Content -Raw $BuiltModuleManifest + if ( $manifestString -match '#\sReleaseNotes\s?=' -and $manifest.PrivateData.psdata.keys -notcontains 'ReleaseNotes') + { + $manifestString = $manifestString -replace '#\sReleaseNotes\s?=', ' ReleaseNotes =' + $Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($False) + [System.IO.File]::WriteAllLines($BuiltModuleManifest, $manifestString, $Utf8NoBomEncoding) + # reload module manifest + $manifest = Get-SamplerModuleInfo -ModuleManifestPath $BuiltModuleManifest + } + + $alreadyPublishedModules = @() + $resourceToPublishQueue = [System.Collections.Queue]::new() + $resourceToPublishQueue.Enqueue([Microsoft.PowerShell.Commands.ModuleSpecification]$BuiltModuleManifest) + + while ($resourceToPublishQueue.count -gt 0) + { + # Take first module in queue, if it has dependencies, add them first, and re-queue that module after, otherwise publish it if not already published + $nextModuleSpecs = $resourceToPublishQueue.Dequeue() + try + { + #TODO: If Module doesn't work on current env (OS, PSVersion, etc), we should not try to load it. + $nextModule = $nextModuleSpecs | Import-Module -PassThru -ErrorAction Stop + if ($nextModule.Count -gt 1) + { + # Maybe there were ScriptsToProcess, so we imported more than 1 elements + # We need to find just the module, either by name or by its manifest path + $nextModule = $nextModule.Where({ + $nextModuleAbsolutePath = Get-SamplerAbsolutePath -Path $_.Path + $nextModuleSpecsPath = Get-SamplerAbsolutePath -Path $nextModuleSpecs.Name + $_.Name -eq $nextModuleSpecs.Name -or $nextModuleAbsolutePath -eq $nextModuleSpecsPath + }, 1) + + Write-Build DarkGray (' Found {0} elements importing {1}.' -f $nextModule.Count, $nextModuleSpecs.Name) + } + } + catch + { + Write-Build Red "Error importing module $($nextModuleSpecs.Name) with version $($nextModuleSpecs.Version). $($_.Exception.Message)" + throw "Cannot continue packaging the module. Error with $($nextModuleSpecs.Name) with version $($nextModuleSpecs.Version). $($_.Exception.Message)" + } + + # Best way to deduce the module manifest I found + $nextModuleManifestPath = '{0}{1}{2}.psd1' -f $nextModule.ModuleBase,[io.path]::DirectorySeparatorChar,$nextModule.Name + # Using $nextModule.RequiredModule doesn't get the right value as it resolves them. + $nextModuleManifest = Get-SamplerModuleInfo -ModuleManifestPath $nextModuleManifestPath + $requiredModules = [Microsoft.PowerShell.Commands.ModuleSpecification[]]$nextModuleManifest.RequiredModules + $externallyManagedModules = [Microsoft.PowerShell.Commands.ModuleSpecification[]]$nextModuleManifest.PrivateData.PSData.ExternalModuleDependencies + # Only consider dependencies that are not already published and not externally managed + $nextModuleSpecsDependencies = $requiredModules.Where{$_.Name -notin $externallyManagedModules.Name -and $_.Name -notin $alreadyPublishedModules.Name} + # If there are dependencies, add them to the queue first, and then re-add the module itself at the end of the queue, otherwise publish it if not already published + if ($nextModuleSpecsDependencies.count -gt 0) + { + foreach ($module in $nextModuleSpecsDependencies) + { + Write-Build DarkGray (' Module {0} v{1} has dependency on module {2} {3}' -f $nextModule.Name, $nextModule.Version, $module.Name, $module.Version) + $resourceToPublishQueue.Enqueue([Microsoft.PowerShell.Commands.ModuleSpecification[]]$module) + } + + $resourceToPublishQueue.Enqueue($nextModuleSpecs) + } + else + { + if ($nextModuleSpecs.Name -notin $alreadyPublishedModules.Name) + { + # TODO: Maybe be more robust with the prerelease flag? + + if (Get-PSResourceRepository -Name output -ErrorAction Ignore) + { + $isModuleInOutputRepo = $nextModule | Find-PSResource -repository 'output' -ErrorAction Ignore -Prerelease + } + else { + $isModuleInOutputRepo = $false + } + + $nextReleaseTag = if ($nextModuleManifest.PrivateData.PSData.Prerelease) + { + '-{0}' -f $nextModuleManifest.PrivateData.PSData.Prerelease + } + + $moduleVersionWithTag = '{0}{1}' -f $nextModuleManifest.ModuleVersion, $nextReleaseTag + if (-not $isModuleInOutputRepo) + { + Write-Build Yellow (" Packaging Required Module {0} v{1} from path '{2}'" -f $nextModuleManifest.Name, $moduleVersionWithTag, $nextModule.ModuleBase) + try + { + Publish-PSResource -Repository output -ErrorAction Stop -Path $nextModuleManifestPath -WhatIf:$PublishModuleWhatIf + Write-Build Green (" Published Required Module {0} v{1} to output repository" -f $nextModuleManifest.Name, $moduleVersionWithTag) + } + catch + { + Write-Build Red (" Failed to publish Required Module {0} v{1} to output repository. Error: {2}" -f $nextModuleManifest.Name, $moduleVersionWithTag, $_) + throw + } + } + else + { + Write-Build DarkGray (" Required Module {0} v{1} already in output repository, skipping packaging" -f $nextModuleManifest.Name, $moduleVersionWithTag) + } + + $alreadyPublishedModules += $nextModuleSpecs + } + } + } + + Write-Build Green "`n Packaged $ProjectName NuGet package `n" + Write-Build DarkGray " Cleaning up" + + $null = Unregister-PSResourceRepository -Name output -ErrorAction SilentlyContinue +} + +# Synopsis: Publish a built PowerShell module to a gallery. +task publish_module_to_psresource_gallery -if ($GalleryApiToken -and (Get-Command -Name 'Publish-PSResource' -ErrorAction 'SilentlyContinue')) { + # Get the values for task variables, see https://github.com/gaelcolas/Sampler?tab=readme-ov-file#build-task-variables. + . Set-SamplerTaskVariable + + # Parse PublishModuleWhatIf to be boolean + $null = [bool]::TryParse($PublishModuleWhatIf, [ref]$script:PublishModuleWhatIf) + + if (-not $BuiltModuleManifest) + { + throw "No valid manifest found for project $ProjectName." + } + + # Uncomment release notes (the default in Plaster/New-ModuleManifest) + $ManifestString = Get-Content -Raw $BuiltModuleManifest + if ( $ManifestString -match '#\sReleaseNotes\s?=') + { + $ManifestString = $ManifestString -replace '#\sReleaseNotes\s?=', ' ReleaseNotes =' + $Utf8NoBomEncoding = [System.Text.UTF8Encoding]::new($False) + [System.IO.File]::WriteAllLines($BuiltModuleManifest, $ManifestString, $Utf8NoBomEncoding) + } + + Write-Build DarkGray "`nAbout to release '$BuiltModuleBase'." + + $PublishModuleParams = @{ + Path = $BuiltModuleBase + Repository = $PSModuleFeed + ErrorAction = 'Stop' + } + + if ($PublishModuleWhatIf) + { + $PublishModuleParams['WhatIf'] = $true + } + + if (-not $SkipPublish) + { + # When publishing, release notes will be used from module manifest. + Write-Build DarkGray " Outputting configured repositories using command Get-PSResourceRepository" + Get-PSResourceRepository + + Write-Build DarkGray " Publishing using command Publish-PSResource" + $PublishModuleParams['ApiKey'] = $GalleryApiToken + Publish-PSResource @PublishModuleParams + } + + Write-Build Green "Package Published to PS Resource Gallery." +} diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 index 78312d2c..bc4c1516 100644 --- a/.vscode/analyzersettings.psd1 +++ b/.vscode/analyzersettings.psd1 @@ -1,7 +1,7 @@ @{ CustomRulePath = '.\output\RequiredModules\DscResource.AnalyzerRules' includeDefaultRules = $true - IncludeRules = @( + IncludeRules = @( # DSC Resource Kit style guideline rules. 'PSAvoidDefaultValueForMandatoryParameter', 'PSAvoidDefaultValueSwitchParameter', @@ -41,4 +41,13 @@ 'Measure-*' ) + Rules = @{ + PSAvoidUsingCmdletAliases = @{ + 'allowlist' = @( + # Invoke-Build module aliases + 'Invoke-Build', + 'task' + ) + } + } } diff --git a/.vscode/launch.json b/.vscode/launch.json index bd155ebd..a9ad22bc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,9 +5,41 @@ "version": "0.2.0", "configurations": [ { - "name": "PowerShell: Interactive Session", "type": "PowerShell", - "request": "launch" + "request": "launch", + "name": "Launch Build", + "script": "${workspaceRoot}/Build.ps1", + "args": [], + "cwd": "${workspaceRoot}/" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "Launch Build -ResolveDependency", + "script": "${workspaceRoot}/Build.ps1", + "args": ["-ResolveDependency"], + "cwd": "${workspaceRoot}" + }, + { + "type": "PowerShell", + "request": "launch", + "name": "Launch Test", + "script": "${workspaceRoot}/Build.ps1", + "args": ["-Tasks test"], + "cwd": "${workspaceRoot}" + }, + { + "name": "PowerShell Interactive Session", + "type": "PowerShell", + "request": "launch", + "cwd": "" + }, + { + "name": "PowerShell: Launch Current File", + "type": "PowerShell", + "request": "launch", + "script": "${file}", + "cwd": "${file}" } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 86afb34e..146aa242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `package_psresource_nupkg` tasks that recursively packs dependencies, ignoring `ExternalDependencies` using `PSResourceGet` module. + +### Fixed + +- Update URL for PowerShell gallery pre-release badge ([#553](https://github.com/gaelcolas/Sampler/issues/553)). +- Update azure-pipelines task PublishCodeCoverageResults to v2 ([#551](https://github.com/gaelcolas/Sampler/issues/551)). +- Updated comment-based help for Resolve-Dependency.ps1 +- Moved large parts of the ReadMe to the Wiki to handle Include simple tutorials in the Wiki ([issue #487](https://github.com/gaelcolas/Sampler/issues/487)). + +### Changed + +- Added support for GitVersion 6 (Breaking Change). + - Updated `GitVersion.yml` config file. + - Removed the GitVersion task in Azure DevOps pipeline. + - Added a task to the Azure DevOps pipeline to Install GitVersion. + - Added a new build task to Sampler: `GitVersion`. + - Updated `Get-SamplerBuildVersion` to be compatible with GitVersion 6. + - Updated `Get-SamplerBuildVersion` Pester tests. + - Changed `README.md`. + +## [0.119.0] - 2026-01-08 + +### Added + +- Note for caching DestinationPath to the location where the module should be + created depending on you platform and workflow. +- HomeBrew installation instructions for GitVersion + ### Fixed - Add retry logic for ModuleFast dependency installation ([#510](https://github.com/gaelcolas/Sampler/issues/510)). @@ -20,10 +50,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `dsccommunity` Template - Added DocGenerator tasks and configuration to `build.yaml` [#468](https://github.com/gaelcolas/Sampler/issues/468). - Updated HQRM task to use Pester 5 version. +- Fix QA tests failing with latest version of DscResource.Test. +- Fixed issue with changelog check under certain conditions +- how to configure http.sslBackend schannel in command in the `Create_Release_Git_Tag` tasks. ### Changed -- The use of Write-* cmdlets has been standardized to a consistent style with named parameters (-Message and -Object). +- The use of Write-* cmdlets has been standardized to a consistent style + with named parameters (-Message and -Object). ## [0.118.3] - 2025-04-29 @@ -166,7 +200,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the `modify` statement that adds a `Prerelease` key that is needed for a module manifest that is created under Windows PowerShell. This resulted in two `Prerelease` keys when creating a module under PowerShell 7.x. - Now it will add a commented `Perelease` key and then next `modify` statement + Now it will add a commented `Prerelease` key and then next `modify` statement will remove the comment, making it work on all version of PowerShell. Fixes [#436](https://github.com/gaelcolas/Sampler/issues/436). - The QA test template was updated so that it is possible to run the tests @@ -176,7 +210,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fix Azure Pipeline bug to resolve errors and delays during the build process. Shallow fetch has been disabled to ensure complete repository cloning. Fixes [#424](https://github.com/gaelcolas/Sampler/issues/424) +- Fix Azure Pipeline bug to resolve errors and delays during the build process. + Shallow fetch has been disabled to ensure complete repository cloning. + Fixes [#424](https://github.com/gaelcolas/Sampler/issues/424) ## [0.116.4] - 2023-04-06 @@ -443,14 +479,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added Pipeline to build chocolatey packages. - Added Sample to add Chocolatey Package source files. - Added New-SamplerPipeline to create build, Sampler Module or Chocolatey pipeline. -- Extra configuration files for passing to Azure Policy Guest Configuration Package on creation. +- Extra configuration files for passing to Azure Policy Guest Configuration + Package on creation. ### Fixed -- Fixed `Resolve-Dependency.ps1` to not fail when `PowerShell-yaml` module was specified but already loaded (handle on dll). Fixes [#335](https://github.com/gaelcolas/Sampler/issues/335) +- Fixed `Resolve-Dependency.ps1` to not fail when `PowerShell-yaml` module + was specified but already loaded (handle on dll). Fixes [#335](https://github.com/gaelcolas/Sampler/issues/335) - Fixed default source folder to source and not src. -- Fixed failed loading when there's no project name (when calling `Set-SamplerTaskVariable`). Fixes [#331](https://github.com/gaelcolas/Sampler/issues/331). -- Fixed `Get-SamplerAbsolutePath` returning the wrong path in PowerShell and ISE. Fixes [#341](https://github.com/gaelcolas/Sampler/issues/341). +- Fixed failed loading when there's no project name (when calling + `Set-SamplerTaskVariable`). Fixes [#331](https://github.com/gaelcolas/Sampler/issues/331). +- Fixed `Get-SamplerAbsolutePath` returning the wrong path in PowerShell + and ISE. Fixes [#341](https://github.com/gaelcolas/Sampler/issues/341). - The templates was using the task `Create_ChangeLog_GitHub_PR` in the meta task publish that is also specifically run in a separate Azure Pipelines task. This made the task to run twice. @@ -474,8 +514,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 in Azure Pipelines. - GitVersion.yml now uses the correct chosen default branch. - Codecov.yml now uses the correct chosen default branch. -- Fixed GuestConfiguration compilation to work with GuestConfiguration module version 4.0.0-preview0002. -- Set the default type to AuditAndSet, but supporting override by creating a '$GCPackageName.psd1' file along with the config. +- Fixed GuestConfiguration compilation to work with GuestConfiguration module + version 4.0.0-preview0002. +- Set the default type to AuditAndSet, but supporting override by creating + a '$GCPackageName.psd1' file along with the config. ## [0.112.0] - 2021-09-23 @@ -532,7 +574,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed `$BuiltModuleSubdirectory` definition in the `begin` bloc of `build.ps1` template ([issue #299](https://github.com/gaelcolas/Sampler/issues/299)). -- Replaced `$ModudulePath` with `$BuiltModuleBase` for the `release.module.build.ps1` task file. +- Replaced `$ModulePath` with `$BuiltModuleBase` for the `release.module.build.ps1` + task file. ## [0.111.5] - 2021-06-25 @@ -543,7 +586,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- The task `Invoke_Pester_Tests_v5` no longer fails when using Pester +- The task `Invoke_Pester_Tests_v5` no longer fails when using Pester v5.3.0-alpha5 ([issue #307](https://github.com/gaelcolas/Sampler/issues/307)). - The task `Convert_Pester_Coverage` no longer fails when using a preview version of Pester ([issue #301](https://github.com/gaelcolas/Sampler/issues/301)). @@ -643,8 +686,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Pester 5 code coverage. - The function `Get-CodeCoverageThreshold` was changed to support Pester 5 advanced build configuration. -- The function `Get-SamplerCodeCoverageOutputFile` was changed to support Pester 5 - advanced build configuration. +- The function `Get-SamplerCodeCoverageOutputFile` was changed to support + Pester 5 advanced build configuration. ### Fixed @@ -676,8 +719,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Support for Generating MAML help files (all Locale/Culture) from PlatyPS Markdown Source. -- Support for Updating the PlatyPS Markdown source in your repo (this is a dev task to do before a commit). +- Support for Generating MAML help files (all Locale/Culture) from PlatyPS + Markdown Source. +- Support for Updating the PlatyPS Markdown source in your repo (this is + a dev task to do before a commit). - Support for Generating MAML file from Comment-based help (not recommended). - Support for code coverage when using ModuleBuilder pattern for building module. - `Update-JaCoCoStatistic` @@ -696,11 +741,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed codecov.yml to parse version number in paths correctly. - Fix uploading to Azure Code Coverage. - _Merge_CodeCoverage_Files_ - - Fixed so the file that is outputted is in UTF-8 (without BOM) to support + - Fixed so the file that is outputted is in UTF-8 (without BOM) to support Codecov.io. - The task now only searches for the file pattern inside the `./output/testResults` folder. - - The merge process is not attempted if `CodeCoverageThreshold` is set to + - The merge process is not attempted if `CodeCoverageThreshold` is set to `0`. - Updated so that build.yaml now have a key `CodeCoverage` which have to settings `CodeCoverageMergedOutputFile` and `CodeCoverageFilePattern`. @@ -745,7 +790,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Made Convert-SamplerHashtableToString public function. -- Refactored a lot of Path resolution into Sampler public function for consitency and re-usability. +- Refactored a lot of Path resolution into Sampler public function for + consitency and re-usability. - Updated the Tasks to use those Sampler functions. - Updated Get-BuiltModuleVersion to support $BuiltModuleSubdirectory as per #239. @@ -771,21 +817,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Extracted the Common functions to be within the main Sampler module to enable re-usability. -- Updated this project's `build.ps1` to load the Private/Public *.ps1 so it can build itselves without impacting Sampler templates. -- Added empty functions' Unit test files (for subsequent PR when writing moving to Pester 5). +- Updated this project's `build.ps1` to load the Private/Public *.ps1 so it can + build itselves without impacting Sampler templates. +- Added empty functions' Unit test files (for subsequent PR when writing + moving to Pester 5). - Added Comment-based help for the extracted functions. -- Dropped the CodeCoverage Threshold of the project to reflect the newly discovered code (`Common.Functions.psm1` wasn't counted for code coverage). +- Dropped the CodeCoverage Threshold of the project to reflect the newly + discovered code (`Common.Functions.psm1` wasn't counted for code coverage). ### Removed - Removed the GitHub functions to publish them in the `Sampler.GitHubTasks` module. ## [0.109.4] - 2021-03-06 + ### Added -- Added the build_guestconfiguration_packages task to create GuestConfig packages using the GuestConfiguration module. -- Added GCPackage template so that you can use `Add-Sample -Sample GCPackage` to add a GC Package to your Sampler project. -- Added the gcpack meta task to call clean, build, and build_guestconfiguration_packages for you. +- Added the build_guestconfiguration_packages task to create GuestConfig + packages using the GuestConfiguration module. +- Added GCPackage template so that you can use `Add-Sample -Sample GCPackage` + to add a GC Package to your Sampler project. +- Added the gcpack meta task to call clean, build, and build_guestconfiguration_packages + for you. ## [0.109.3] - 2021-02-16 @@ -800,7 +853,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 so that repositories that move over to Pester 5 can future-proof the file `azure-pipelines.yml` (for example when splitting tests over several jobs). The parameter `PesterScript` is deprecated and will be removed when - Pester 4 support is removed some time in the future. Change scripts to + Pester 4 support is removed some time in the future. Change scripts to `PesterPath` when migrating to Pester 5 tests. ## [0.109.2] - 2021-01-13 @@ -809,14 +862,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The Deploy tasks `publish_nupkg_to_gallery` and `publish_module_to_gallery` are now made mutually exclusive. For each deploy pipeline you must choose - to use either one. + to use either one. - `publish_nupkg_to_gallery` is using `nuget` to publish to the gallery. - `publish_module_to_gallery` is using the cmdlet `Publish-Module` to publish to the gallery. ### Fixed -- Fix issue in DscResourcesToExport task to properly process DscResource schema ([issue #230](https://github.com/gaelcolas/Sampler/issues/230)). +- Fix issue in DscResourcesToExport task to properly process DscResource + schema ([issue #230](https://github.com/gaelcolas/Sampler/issues/230)). - Fix uploading of code coverage when using the DSC Community template. ## [0.109.1] - 2021-01-06 @@ -831,10 +885,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Updating all azure-pipeline.yaml to change Build Artifacts to Pipeline Artifacts ([issue #159](https://github.com/gaelcolas/Sampler/issues/159)). +- Updating all azure-pipeline.yaml to change Build Artifacts to Pipeline + Artifacts ([issue #159](https://github.com/gaelcolas/Sampler/issues/159)). - Update plasterManifest.xml call by New-SampleModule : - - Add section modify to replace "FunctionsToExport = '*'" by "FunctionsToExport = ''" in new module manifest ([issue #67](https://github.com/gaelcolas/Sampler/issues/67)). - - Add section modify to add "Prerelease = ''" in "PSData" block in new module manifest ([issue #69](https://github.com/gaelcolas/Sampler/issues/69)). + - Add section modify to replace "FunctionsToExport = '*'" by + "FunctionsToExport = ''" in new module manifest ([issue #67](https://github.com/gaelcolas/Sampler/issues/67)). + - Add section modify to add "Prerelease = ''" in "PSData" block in new + module manifest ([issue #69](https://github.com/gaelcolas/Sampler/issues/69)). - Changing ClassResource. - Add generic content in the class. - Add pester tests. @@ -843,7 +900,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add private functions. - Add pester tests. - Update Sampler integration tests. -- Changing the Reasons property in the classes based resource template. It's now NotConfigurable. +- Changing the Reasons property in the classes based resource template. + It's now NotConfigurable. - Renamed Build_Module_ModuleBuilder task to Build_ModuleOutPut_ModuleBuilder. Build_Module_ModuleBuilder is now a metatask that calls Build_ModuleOutPut_ModuleBuilder and Build_DscResourcesToExport_ModuleBuilder tasks. @@ -854,11 +912,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added new function Get-ClassBasedResourceName on Common.Functions.psm1 module. It's used to find the class-based resource defined in psm1 file. - Added new task Build_DscResourcesToExport_ModuleBuilder. - On build, it adds DscResources (class or Mof) in DscResourcesToExport manifest key. + On build, it adds DscResources (class or Mof) in DscResourcesToExport + manifest key. ### Fixed -- Fixed Test-ModuleManifest ([issue #208](https://github.com/gaelcolas/Sampler/issues/208)) +- Fixed Test-ModuleManifest ([issue #208](https://github.com/gaelcolas/Sampler/issues/208)) in tasks. ## [0.108.0] - 2020-09-14 @@ -898,27 +957,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- Removed the CompletModule_noBuild template as it's unecessary and add complexity to the template. +- Removed the CompletModule_noBuild template as it's unecessary and add + complexity to the template. ## [0.107.3] - 2020-09-10 ### Fixed -- Fixed the Build of template with DSC Resource by adding required modules, config & helper modules tests. -- Fixed the issue with the Publish module task (always use Publish-Module unless you want to UseNugetPush). +- Fixed the Build of template with DSC Resource by adding required modules, + config & helper modules tests. +- Fixed the issue with the Publish module task (always use Publish-Module + unless you want to UseNugetPush). ## [0.107.2] - 2020-09-08 ### Fixed -- Fixed build error when the "Update changelog" PR is created (and no changes exists). -- Fixed when creating a Module from template and the Build.yml does not copyPaths: DscResources. +- Fixed build error when the "Update changelog" PR is created (and no changes + exists). +- Fixed when creating a Module from template and the Build.yml does not + copyPaths: DscResources. ## [0.107.1] - 2020-09-08 ### Fixed -- Fixed #192 where the `Build-Module` command from module builder returns a rooted path (sometimes). +- Fixed #192 where the `Build-Module` command from module builder returns + a rooted path (sometimes). ## [0.107.0] - 2020-09-07 @@ -943,14 +1008,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added Templates for: - - DSC Composite - - Class-based DSC Resource with reasons - - MOF based DSC Resource and tests - - Private Function and tests - - Public Function and tests - - Public function calling a Private function and tests - - Classes and tests - - Enum + - DSC Composite + - Class-based DSC Resource with reasons + - MOF based DSC Resource and tests + - Private Function and tests + - Public Function and tests + - Public function calling a Private function and tests + - Classes and tests + - Enum - Added integration tests for the Plaster templates. - Added support to use an alternate name for the trunk branch in `New-Release.GitHub.build.ps1` ([issue #182](https://github.com/gaelcolas/Sampler/issues/182)). @@ -1188,7 +1253,8 @@ assets with conditional content. ### Fixed -- Fixing the codecoverage threshold issues reported by Daniel (As param set to 0 should not bypass). +- Fixing the codecoverage threshold issues reported by Daniel (As param + set to 0 should not bypass). ### Removed @@ -1198,9 +1264,12 @@ assets with conditional content. ### Added -- Added Module manifest in build.psd1 template to fix issue resolving Project on linux. -- Added DSC Resources & Supporting modules (including one from PSGallery, one from source). -- Added PesterScript parameter in Build.ps1 so that it can be overridden at runtime (in azure-pipelines.yml). +- Added Module manifest in build.psd1 template to fix issue resolving Project + on linux. +- Added DSC Resources & Supporting modules (including one from PSGallery, + one from source). +- Added PesterScript parameter in Build.ps1 so that it can be overridden + at runtime (in azure-pipelines.yml). - Added `Modules` commented out to the `build.yml` - Added CodeCoverageThreshold parameter to fail when under threshold (configurable in `build.yaml`). Will skip all code coverage when set to 0 @@ -1210,7 +1279,8 @@ assets with conditional content. ### Changed -- Made Code Coverage threshold to load from config file, and be skipped completely if set to 0 or absent. +- Made Code Coverage threshold to load from config file, and be skipped + completely if set to 0 or absent. - Updating Code of Conduct to the DSC Community one. ### Fixed @@ -1222,7 +1292,8 @@ assets with conditional content. ### Fixed -- Fixed when the SourcePath is not enough for finding ModuleManifest (ModuleBuilder bug) +- Fixed when the SourcePath is not enough for finding ModuleManifest + (ModuleBuilder bug) ## [0.95.1] - 2019-11-01 @@ -1284,7 +1355,7 @@ assets with conditional content. ### Changed -- Changed the Tags trigger to include "v*" but still exclude "*-*" +- Changed the Tags trigger to include "v*" but still exclude "\*-\*" ## [v0.91.6] - 2019-10-11 @@ -1310,7 +1381,7 @@ assets with conditional content. ### Fixed - Fixing Create ChangeLog PR Get-Variable -- fixing versioning by reverting to gitversions' continuousDeployment mode +- fixing versioning by reverting to gitversion's continuousDeployment mode - fixed #22: marking github releases as pre-release when there's a PreReleaseTag - fix call to add assets to GH release. - for any bug fixes. diff --git a/GitVersion.yml b/GitVersion.yml index 1ef14602..44dfd868 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,39 +1,26 @@ -mode: ContinuousDelivery -next-version: 0.90 -major-version-bump-message: '\s?(breaking|major|breaking\schange)' -minor-version-bump-message: '(adds?|features?|minor)\b' -patch-version-bump-message: '\s?(fix|patch)' +mode: ManualDeployment +next-version: 0.90.0 +major-version-bump-message: '\+semver:\s?(breaking|major)' +minor-version-bump-message: '\+semver:\s?(feature|minor)' +patch-version-bump-message: '\+semver:\s?(fix|patch)' no-bump-message: '\+semver:\s?(none|skip)' -assembly-informational-format: '{NuGetVersionV2}+Sha.{Sha}.Date.{CommitDate}' branches: - master: - tag: preview - regex: ^main$ + main: + label: preview + regex: ^master$|^main$ pull-request: - tag: PR + label: PR feature: - tag: useBranchName + label: '{BranchName}' increment: Minor - regex: f(eature(s)?)?[\/-] - source-branches: ['master'] + regex: ^features?[\/-](?.+) + source-branches: ['main'] hotfix: - tag: fix + label: fix increment: Patch regex: (hot)?fix(es)?[\/-] - source-branches: ['master'] + source-branches: ['main'] ignore: sha: [] merge-message-formats: {} - - -# feature: -# tag: useBranchName -# increment: Minor -# regex: f(eature(s)?)?[/-] -# source-branches: ['master'] -# hotfix: -# tag: fix -# increment: Patch -# regex: (hot)?fix(es)?[/-] -# source-branches: ['master'] diff --git a/README.md b/README.md index 9e455f0e..c322c2ba 100644 --- a/README.md +++ b/README.md @@ -1,226 +1,58 @@ -# Sampler Module [![Azure DevOps builds](https://img.shields.io/azure-devops/build/Synedgy/524b41a5-5330-4967-b2de-bed8fd44da08/1)](https://synedgy.visualstudio.com/Sampler/_build?definitionId=1&_a=summary) -[![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/vpre/Sampler?label=Sampler%20Preview)](https://www.powershellgallery.com/packages/Sampler/) +# Sampler Module [![Azure DevOps builds](https://img.shields.io/azure-devops/build/Synedgy/524b41a5-5330-4967-b2de-bed8fd44da08/1)](https://synedgy.visualstudio.com/Sampler/_build?definitionId=1&_a=summary) + +[![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/v/Sampler?label=Sampler%20Preview&include_prereleases)](https://www.powershellgallery.com/packages/Sampler/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/Sampler?label=Sampler)](https://www.powershellgallery.com/packages/Sampler/) [![Azure DevOps tests](https://img.shields.io/azure-devops/tests/SynEdgy/Sampler/1)](https://synedgy.visualstudio.com/Sampler/_test/analytics?definitionId=1&contextType=build) ![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/Synedgy/Sampler/1) ![PowerShell Gallery](https://img.shields.io/powershellgallery/p/Sampler) -This project is used to scaffold a PowerShell module project, complete with -PowerShell build and deploy pipeline automation. - -The Sampler module in itself serves several purposes: - -- Quickly scaffold a PowerShell module project that can build and enforce some good practices. -- Provide a minimum set of [InvokeBuild](https://github.com/nightroman/Invoke-Build) -tasks that help you build, test, pack and publish your module. -- Help building your module by adding elaborate sample elements like classes, - MOF-based DSC resources, class-based DSC resources, helper modules, embedded helper - modules, and more. -- Avoid the "it works on my machine" and remove the dependence on specific tools - (such as a CI tool). -- Ensures the build process can be run anywhere the same way (whether behind a - firewall, on a developers workstation, or in a build agent). -- Assume nothing is set up, and you don't have local administrator rights. -- Works on Windows, Linux and MacOS. +Sampler is an opinionated scaffolding and build-automation framework for +PowerShell module projects. It gives you a production-ready project +structure, a reproducible build pipeline powered by +[InvokeBuild](https://github.com/nightroman/Invoke-Build), and a library +of reusable tasks — so you can focus on writing your module instead of +maintaining infrastructure. + +## Why Sampler? + +- **Scaffold in seconds**: Generate a new module project with built-in + CI/CD support, code-quality checks, and best-practice conventions. +- **Build, test, pack & publish**: A curated set of InvokeBuild tasks + covers the full lifecycle from source to the PowerShell Gallery. +- **Rich templates**: Add classes, MOF-based DSC resources, class-based + DSC resources, helper modules, composite resources, and more with a + single command. +- **Reproducible everywhere**: The same build runs on a developer + workstation, behind a corporate firewall, or in a CI agent — no + local-admin rights or pre-installed tooling required. +- **Cross-platform**: Works on Windows, Linux, and macOS. Check the video for a quick intro: -> _Note: The video was made when Sampler was in early stages. Since that time_ -> _there have been a lot of improvements and changes, so please read the_ -> _documentation below._ - -[![Sampler demo video](https://img.youtube.com/vi/bbpFBsl8K9k/0.jpg)](https://www.youtube.com/watch?v=bbpFBsl8K9k&ab_channel=DSCCommunity) - -## Prerequisites - -### Resolving dependencies - -The Sampler templates is configured to use PSResourceGet as the method of -resolving dependencies. The property `UsePSResourceGet` is default configured -to `$true` in the file Resolve-Dependency.psd1. If that configuration is -removed or disabled (set to `$false`) then resolving dependencies will -revert to PowerShellGet & PSDepend. - -The specification syntax of the file RequiredModules.psd1 works with all -three methods of resolving dependencies. - -```powershell -@{ - # Gives latest release - Pester = 'latest' - - # Gives specific release (also known as pinning version) - Pester = '4.10.1' - - # Gives latest preview release - 'ComputerManagementDsc' = @{ - Version = 'latest' - Parameters = @{ - AllowPrerelease = $true - } - } - - # Gives specific preview release (also known as pinning version) - 'ComputerManagementDsc' = @{ - Version = '9.1.0-preview0002' - Parameters = @{ - AllowPrerelease = $true - } - } -} -``` - -When using the method _PowerShellGet & PSDepend_ this configuration should -also be added to the file RequiredModules.psd1 to control the behavior -of PSDepend. This is only required if you need to use _PowerShellGet & PSDepend_. -It is not required for PSResourceGet or ModuleFast. - -```powershell -@{ - PSDependOptions = @{ - AddToPath = $true - Target = 'output\RequiredModules' - Parameters = @{ - Repository = 'PSGallery' - } - } -``` - -#### PSResourceGet - -It is possible to use [PSResourceGet](https://github.com/PowerShell/PSResourceGet) -to resolve dependencies. PSResourceGet works with Windows PowerShell and -PowerShell (some restrictions on versions exist). To use PSResourceGet as -a replacement for PowerShellGet it is possible to enable it in the configuration -file `Resolve-Dependency.psd1`. It is also possible to allow the repository -to use PowerShellGet as the default and choose to use PSResourceGet from the -command line by passing the parameter `UsePSResourceGet` to the build script -`build.ps1`, e.g. `.\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet` - -If both PSResourceGet and ModuleFast is enabled then PSResource will be -preferred on Windows PowerShell and PowerShell 7.1 or lower. ModuleFast -will be preferred on PowerShell 7.2 or higher. - -#### ModuleFast - -It is possible to use [ModuleFast](https://github.com/JustinGrote/ModuleFast) -to resolve dependencies. ModuleFast only works with PowerShell 7.2 or higher. -To use ModuleFast as a replacement for PowerShellGet it is possible to -enable it in the configuration file `Resolve-Dependency.psd1`. -It is also possible to allow the repository to use PowerShellGet as the -default and choose to use ModuleFast from the command line by passing -the parameter `UseModuleFast` to the build script `build.ps1`, e.g. -`.\build.ps1 -ResolveDependency -Tasks noop -UseModuleFast`. - -If both PSResourceGet and ModuleFast is enabled then ModuleFast will be -preferred on PowerShell 7.2 or higher. PSResource will be preferred -on Windows PowerShell and PowerShell 7.1 or lower. - -When using ModuleFast as the only method there is more options to specify -modules in the file RequiredModules.psd1. This syntax will limit resolving -dependencies to just ModuleFast (and PowerShell 7.2 or higher) as they are -not supported by the other methods. See the comment-based help of the command -[`Install-ModuleFast`](https://github.com/JustinGrote/ModuleFast/blob/main/ModuleFast.psm1) -for more information of the available syntax. - -```powershell -@{ - # Gives the latest release - 'ComputerManagementDsc' = 'latest' - - # Gives the specific release - 'ComputerManagementDsc' = '9.0.0' - - # Gives the latest patch release for v9.0 - 'ComputerManagementDsc' = ':9.0.*' - - # Gives the latest preview release - 'ComputerManagementDsc' = '!' - - # Gives the latest release (including previews) that is higher that v9.0.0 - 'ComputerManagementDsc' = '!>9.0.0' - - # Must be exactly 9.1.0-preview0002 - 'ComputerManagementDsc' = '9.1.0-preview0002' - 'ComputerManagementDsc' = '@9.1.0-preview0002' - 'ComputerManagementDsc' = ':9.1.0-preview0002' - 'ComputerManagementDsc' = ':[9.1.0-preview0002]' - - # Must be a higher version than 9.1.0-preview0002 - 'ComputerManagementDsc' = '>9.1.0-preview0002' - - # Must be a lower version than 9.1.0-preview0002 - 'ComputerManagementDsc' = '<9.1.0-preview0002' - - # Must be a lower version than or equal to 9.1.0-preview0002 - 'ComputerManagementDsc' = '<=9.1.0-preview0002' - - # Must be a higher version than or equal to 9.1.0-preview0002 - 'ComputerManagementDsc' = '>=9.1.0-preview0002' - - # Exact range, exclusive. Must be lower version than 9.2.0. 9.2.0 is not allowed. - 'ComputerManagementDsc' = ':(,9.2.0)' - - # Exact range, exclusive. Must be higher version than 9.0.0. 9.0.0 is not allowed. - 'ComputerManagementDsc' = ':(9.0.0,)' +[![Modern PowerShell module Development](https://img.youtube.com/vi/_Hr6CeTKbLc/0.jpg)](https://www.youtube.com/watch?v=_Hr6CeTKbLc) - # Exact range, inclusive. Must be version than 9.0.0 or higher up to or equal to 9.2.0. - 'ComputerManagementDsc' = ':[9.0.0,9.2.0]' -} - -``` - -#### PowerShellGet & PSDepend - -Because we resolve dependencies from a nuget feed, whether the public -PowerShell Gallery or your private repository, a working version of PowerShellGet -is required. Using PowerShellGet is the default if no other configuration -is done. We recommend the latest version of PowerShellGet v2. - -### Managing the Module versions (optional) - -Managing the versions of your module is tedious, and is hard to maintain -consistency over time. The usual tricks like checking what the latest version on -PowerShell Gallery is, or use the `BuildNumber` to increment a `0.0.x` version -works but isn't ideal, especially if we want to stick to [semver](https://semver.org/). - -While you can manage the version by updating the module manifest manually or by -letting your CI tool update the `ModuleVersion` environment variable, we think -the best method is to rely on the cross-platform tool [`GitVersion`](https://gitversion.net/docs/). +> _Note:_ The video only shows a tiny part of the `Sampler` usage. +> Make sure to read the additional documentation in the configuration files and the +> Getting started section in the [Sampler - Wiki][1]. -[`GitVersion`](https://gitversion.net/docs/) will generate the version based on -the git history. You control what version to deploy using [git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). +## 2. Prerequisites -Generally, GitVersion will look at the latest version tag, the branch names, commit -messages, to try to determine the Major/Minor/Patch (semantic versioning) based -on detected change (configurable in the file [`GitVersion.yml`](https://gitversion.net/docs/reference/configuration) -that is part of your project). +- PowerShell 5.x or PowerShell 7.x -Therefore, it is recommended that you install `GitVersion` on your development environment -and on your CI environment build agents. +The `build.ps1` script will download all other specified modules required into the `RequiredModules` folder of your module project for you. -There are various ways to [install GitVersion](https://gitversion.net/docs/usage/cli/installation) -on your development environment. If you use Chocolatey (install and upgrade): - -```PowerShell -C:\> choco upgrade gitversion.portable -``` - -This describes how to [install GitVersion in your CI environment build agents](https://gitversion.net/docs/usage/ci) -if you plan to use the deploy pipelines in the CI. - -## Usage +## 3. Usage ### How to create a new project -To create a new project the command `New-SampleModule` should be used. Depending +To create a new project, the command `New-SampleModule` should be used. Depending on the template used with the command the content in project will contain different sample content while some also adds additional pipeline jobs. But all templates (except one) will have the basic tasks to have a working pipeline including build, test and deploy stages. -The sections below show how to use each template. The templates are: +The templates are: - `SimpleModule` - Creates a module with minimal structure and pipeline automation. - `SimpleModule_NoBuild` - Creates a simple module without the build automation. @@ -236,14 +68,19 @@ pipeline scripts. Once the project is created, the `build.ps1` inside the new project folder is how you interact with the built-in pipeline automation, and the file `build.yaml` is where you configure and customize it. -#### `SimpleModule` +The section below shows only how to create a new module using the `SimpleModule` template. +For the complete `Getting Started` instructions, **please see** the [Sampler - Wiki][1]. + +#### SimpleModule Creates a module with minimal structure and pipeline automation. +>[!NOTE] +>Change the `DestinationPath` to the location where the module should be created depending on you platform and workflow. ```powershell Install-Module -Name 'Sampler' -Scope 'CurrentUser' -$newSampleModuleParameters = @{ +$NewSampleModuleParameters = @{ DestinationPath = 'C:\source' ModuleType = 'SimpleModule' ModuleName = 'MySimpleModule' @@ -251,623 +88,21 @@ $newSampleModuleParameters = @{ ModuleDescription = 'MySimpleModule Description' } -New-SampleModule @newSampleModuleParameters -``` - -#### `SimpleModule_NoBuild` - -Creates a simple module without the build automation. - -```powershell -Install-Module -Name 'Sampler' -Scope 'CurrentUser' - -$newSampleModuleParameters = @{ - DestinationPath = 'C:\source' - ModuleType = 'SimpleModule_NoBuild' - ModuleName = 'MySimpleModuleNoBuild' - ModuleAuthor = 'My Name' - ModuleDescription = 'MySimpleModuleNoBuild Description' -} - -New-SampleModule @newSampleModuleParameters -``` - -#### `CompleteSample` - -Creates a module with complete structure and example files. - -```powershell -Install-Module -Name 'Sampler' -Scope 'CurrentUser' - -$newSampleModuleParameters = @{ - DestinationPath = 'C:\source' - ModuleType = 'CompleteSample' - ModuleName = 'MyCompleteSample' - ModuleAuthor = 'My Name' - ModuleDescription = 'MyCompleteSample Description' -} - -New-SampleModule @newSampleModuleParameters -``` - -#### `dsccommunity` - -Creates a DSC module according to the DSC Community baseline with a pipeline -for build, test, and release automation. - -```powershell -Install-Module -Name 'Sampler' -Scope 'CurrentUser' - -$newSampleModuleParameters = @{ - DestinationPath = 'C:\source' - ModuleType = 'dsccommunity' - ModuleName = 'MyDscModule' - ModuleAuthor = 'My Name' - ModuleDescription = 'MyDscModule Description' -} - -New-SampleModule @newSampleModuleParameters -``` - -#### `CustomModule` - -Will prompt you for more details as to what you'd like to scaffold. - -```powershell -Install-Module -Name 'Sampler' -Scope 'CurrentUser' - -$samplerModule = Import-Module -Name Sampler -PassThru - -$invokePlasterParameters = @{ - TemplatePath = Join-Path -Path $samplerModule.ModuleBase -ChildPath 'Templates/Sampler' - DestinationPath = 'C:\source' - ModuleType = 'CustomModule' - ModuleName = 'MyCustomModule' - ModuleAuthor = 'My Name' - ModuleDescription = 'MyCustomModule Description' -} - -Invoke-Plaster @invokePlasterParameters -``` - -#### `GCPackage` - ->**Note:** The `GCPackage` template is not yet available, but can be created using ->the `dsccommunity` template with modifications, see the section [GCPackage scaffolding](#gcpackage-scaffolding). - -### How to work with the multi-module repository - -Typically, such as with a community-driven module, you would have a single -git repository dedicated to each module. However, there are situations where -you might opt for a single git repository to encompass multiple modules. If -you intend to establish a multi-module repository, ensure that each module -is housed within its own folder. Each module's folder structure should be -distinct and should not overlap with the folder structure of other modules. - -> [!TIP] -> Right folder structure - -```bash -GitRootFolder -├───Module1 -├───Module2 -├───SomeModuleGroup #Not a module -│ ├───GroupModule1 -│ └───GroupModule2 -└───Module3 -``` - -> [!CAUTION] -> Wrong folder structure - -```bash -GitRootFolder -├───Module1 -├───Module2 -├───Module3 -│ ├───SubModule1 -│ └───SubModule2 -└───Module3 -``` - ->[!NOTE] ->You can utilize the gitversion tag-prefix to differentiate tags for each module ->separately. [gitVersion configuration](https://gitversion.net/docs/reference/configuration) - -### How to download dependencies for the project - -To be able to build the project, all the dependencies listed in the file -`RequiredModules.psd1` must first be available. This is the beginning of -the build process so that anyone doing a git clone can 're-hydrate' the -project and start testing and producing the build artefact locally with -minimal environmental dependencies. - ->[!NOTE] ->Try to avoid mixing these different methods in the same session. When ->switching to use a different method, open a new PowerShell session so ->none of the modules dependencies are loaded into the session. - -```mermaid -graph LR - -RD[Resolve dependencies] --> Method{Method?} -Method{Method?} -->|"(Legacy, to use, -disable other -methods)"| PowerShellGet(["PowerShellGet"]) -Method -->|"parameter --UseModuleFast"| ModuleFast(["ModuleFast"]) -Method -->|"(Default), -parameter --UsePSResourceGet"| PSResourceGet(["PSResourceGet"]) -PowerShellGet -->|"Invoke-PSDepend"| InvokeRD -ModuleFast -->|"Install-ModuleFast"| InvokeRD -PSResourceGet -->|"Save-PSResource"| InvokeRD -InvokeRD[Use preferred method] <--> PSGallery["PowerShell Gallery"] -InvokeRD ---> Save[["Save to RequiredModules"]] -``` - -The following command will resolve dependencies using PSResourceGet: - -```powershell -cd C:\source\MySimpleModule - -./build.ps1 -ResolveDependency -Tasks noop -``` - -The following command will resolve dependencies using [ModuleFast](https://github.com/JustinGrote/ModuleFast): - -```powershell -cd C:\source\MySimpleModule - -./build.ps1 -ResolveDependency -Tasks noop -UseModuleFast -``` - -The following command will resolve dependencies using [PSResourceGet](https://github.com/PowerShell/PSResourceGet): - -```powershell -cd C:\source\MySimpleModule - -./build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet -``` - -The dependencies will be downloaded (or updated) from the PowerShell Gallery (unless -another repository is specified) and saved in the project folder under -`./output/RequiredModules`. - -> By default, each repository should not rely on your personal development -> environment, so that it's easier to repeat on any machine or build agent. - -Normally this command only needs to be run once, but the command can be run -anytime to update to a newer version of a required module (if one is available), -or if the required modules have changed in the file `RequiredModules.psd1`. - -> **Note:** If a required module is removed in the file `RequiredModules.psd1` -> that module will not be automatically removed from the folder -> `./output/RequiredModules`. - -### How to build the project - -The following command will build the project: - -```powershell -cd C:\source\MySimpleModule - -./build.ps1 -Tasks build -``` - -It is also possible to resolve dependencies and build the project -at the same time using the command: - -```powershell -./build.ps1 -ResolveDependency -Tasks build -``` - -If there are any errors during build they will be shown in the output and the -build will stop. If it is successful the output should end with: - -```plaintext -Build succeeded. 7 tasks, 0 errors, 0 warnings 00:00:06.1049394 -``` - -> **NOTE:** The number of tasks can differ depending on which template that -> was used to create the project. - -### How to set up the build environment in the current PowerShell session - -If you only want to make sure the environment is configured, or you only want -to resolve the dependencies, you can call the built-in task `noop` ("no operation") -which won't do anything other than a quick way to run the bootstrap script (there -is no code that executes in the `noop` task). - -```powershell -./build.ps1 -Tasks noop -``` - ->**Note:** For the built-in `noop` task to work, the dependencies must first ->have been resolved. - -### How to run tests - - -> [!NOTE] -> Which tests are run is determined by the paths configured -> by a key in the _Pester_ configuration in the file `build.yml`. The key -> differs depending on the version of _Pester_ being used. The key is `Script` -> when using _Pester v4_, and `Path` when using _Pester v5_. - -> [!IMPORTANT] ->If running (or debugging) tests in Visual Studio Code you should first make sure ->the session environment is set correctly. This is normally done when you build ->the project. But if there is no need to rebuild the project it is faster to run ->the [built-in task `noop`](#how-to-set-up-the-build-environment-in-the-current-powershell-session) ->in the _PowerShell Integrated Console_. - - -Running all the unit tests, the quality tests and show code coverage can -be achieved by running the command: - -```powershell -`./build.ps1 -Tasks test` +New-SampleModule @NewSampleModuleParameters ``` -Integration tests are not run by default when using the build task `test`. -To run the integration test use the following command: +See the [Sampler - Wiki][1] for additional examples. -```powershell -`./build.ps1 -Tasks test -PesterPath 'tests/Integration' -CodeCoverageThreshold 0` -``` +## 4. References and links -To run all tests in a specific folder use the parameter `PesterPath` and -optionally `CodeCoverageThreshold` set to `0` to turn off code coverage. -This runs all the quality tests: +* [Sampler - Wiki][1] -```powershell -`./build.ps1 -Tasks test -PesterPath 'tests/QA' -CodeCoverageThreshold 0` -``` - -To run a specific test file, again use the parameter `PesterPath` and -optionally `CodeCoverageThreshold` set to `0` to turn off code coverage. -This runs just the specific test file `New-SamplerXmlJaCoCoCounter.tests.ps1`: - - -```powershell -./build.ps1 -Tasks test -PesterPath ./tests/Unit/Private/New-SamplerXmlJaCoCoCounter.tests.ps1 -CodeCoverageThreshold 0 -``` - +## 5. Change log -### How to run the default workflow - -It is possible to do all of the above (resolve dependencies, build, and run tests) -in just one line by running the following: - -```powershell -./build -ResolveDependency -``` - -The parameter `Task` is not used which means this will run the default workflow -(`.`). The tasks for the default workflow are configured in the file `build.yml`. -Normally the default workflow builds the project and runs all the configured test. - -This means by running this it will build and run all configured tests: - -```powershell -./build.ps1 -``` - -### How to list all available tasks - -Because the build tasks are `InvokeBuild` tasks, we can discover them using -the `?` task. So to list the available tasks in a project, run the following -command: - -```powershell -./build.ps1 -Tasks ? -``` - -> **NOTE:** If it is not already done, first make sure to resolve dependencies. -> Dependencies can also hold tasks that are used in the pipeline. - -## About the bootstrap process (`build.ps1`) - -The `build.ps1` is the _entry point_ to invoke any task, or a list of build -tasks (workflow), leveraging the [`Invoke-Build`](https://www.powershellgallery.com/packages/InvokeBuild) -task runner. - -The script does not assume your environment has the required PowerShell modules, -so the bootstrap is done by the project's script file `build.ps1`, and can -resolve the dependencies listed in the project's file `RequiredModules.psd1` -using [`PSDepend`](https://www.powershellgallery.com/packages/PSDepend). - -Invoking `build.ps1` with the `-ResolveDependency` parameter will prepare your -environment like so: - -1. Updates the session environment variable (`$env:PSModulePath`) to resolve - the built module (`.\output`) and the modules in the folder `./output/RequiredModules` - by prepending those paths to `$env:PSModulePath`. By prepending the paths - to the session `$env:PSModulePath` the build process will make those - dependencies available in your session for module discovery and auto-loading, - and also make it possible to use one or more of those modules as part of your built - module. -1. (Optional) Making sure you have a compatible version of the modules _PowerShellGet_ and - _PackageManagement_ (`version -gt 1.6`). If not, these will be installed from the - configured repository. Only required if you plan to use legacy PowerShellGet, default - PSResourceGet is used. -1. Download or install the `PowerShell-yaml` and `PSDepend` modules needed - for further dependency management. -1. Read the `build.yaml` configuration. -1. If the Nuget package provider is not present, install and import Nuget PackageProvider - (proxy enabled). -1. Invoke [PSDepend](https://www.powershellgallery.com/packages/PSDepend) on - the file `RequiredModules.psd1`. It will not install required modules to - your environment, it will save them to your project's folder `./output/RequiredModules`. -1. Hand over the task execution to `Invoke-Build` to run the configured - workflow. - -## About Sampler build workflow - -Let's look at the pipeline of the `Sampler` module itself to better understand -how the pipeline automation is configured for a project created using a -template from the Sampler module. - -> **NOTE:** Depending on the Sampler template used when creating a new project -> there can be additional configuration options - but they can all be added -> manually when those options are needed. The Sampler project itself does not use -> all features available (an example is DSC resources documentation generation). - -### Default Workflow Currently configured - -As seen in the bootstrap process above, the different workflows can be configured -by editing the `build.psd1`: new tasks can be loaded, and the sequence can be -added under the `BuildWorkflow` key by listing the names. - -In our case, the [build.yaml](build.yaml) defines several workflows (`.`, -`build`, `pack`, `hqrmtest`, `test`, and `publish`) that can be called by using: - -```PowerShell - .\build.ps1 -Tasks -``` - -The detail of the **default workflow** is as follow (InvokeBuild defaults to -the workflow named '.' when no tasks is specified): - -```yml -BuildWorkflow: - '.': - - build - - test -``` - -The tasks `build` and `tests` are meta-tasks or workflow calling other tasks: - -```yml - build: - - Clean - - Build_Module_ModuleBuilder - - Build_NestedModules_ModuleBuilder - - Create_changelog_release_output - test: - - Pester_Tests_Stop_On_Fail - - Pester_if_Code_Coverage_Under_Threshold - - hqrmtest -``` - -Those tasks are imported from a module, in this case from -the `.build/` folder, from this `Sampler` module, -but for another module you would use this line in your `build.yml` config: - -```yaml -ModuleBuildTasks: - Sampler: - - '*.build.Sampler.ib.tasks' # this means: import (dot source) all aliases ending with .ib.tasks exported by 'Sampler' module -``` - -You can edit your `build.yml` to change the workflow, add a custom task, -create repository-specific task in a `.build/` folder named `*.build.ps1`. - -```yml - MyTask: { - # do something with some PowerShellCode - Write-Host "Doing something in a task" - } - - build: - - Clean - - MyTask - - call_another_task -``` - -## GCPackage scaffolding - -Creates a module that can be deployed to be used with _Azure Policy_ -_Guest Configuration_. This process will be replaced with a Plaster template. - -1. Start by creating a new project using the template `dsccommunity`. - - ```powershell - Install-Module -Name 'Sampler' -Scope 'CurrentUser' - - $newSampleModuleParameters = @{ - DestinationPath = 'C:\source' - ModuleType = 'dsccommunity' - ModuleName = 'MyGCPackages' - ModuleAuthor = 'My Name' - ModuleDescription = 'MyGCPackages Description' - } - - New-SampleModule @newSampleModuleParameters - ``` - -1. In the file `build.yaml` add the following top-level key: - - ```yaml - BuiltModuleSubdirectory: module - ``` - -1. In the file `build.yaml` modify the `pack` key under the top-level key - `BuildWorkflow` by adding the task `gcpack`: - - ```yaml - pack: - - build - - package_module_nupkg - - gcpack - ``` - -1. In the file `build.yaml` modify the `GitHubConfig` top-level key - as follows: - - ```yaml - GitHubConfig: - GitHubFilesToAdd: - - 'CHANGELOG.md' - ReleaseAssets: - - output/GCPolicyPackages/UserAmyNotPresent*.zip - GitHubConfigUserName: myGitHubUserName - GitHubConfigUserEmail: myEmail@address.com - UpdateChangelogOnPrerelease: false - ``` - -1. In the file `RequiredModules.psd1` add the module _GuestConfiguration_ - and `xPSDesiredStateConfiguration` to the list of dependency modules. - - ```powershell - @{ - # ... current dependencies - - xPSDesiredStateConfiguration = 'latest' - GuestConfiguration = @{ - Version = 'latest' - Parameters = @{ - AllowPrerelease = $true - } - } - } - ``` - -1. Modify the `azure-pipelines.yml` as follows: - 1. Replace build image with `windows-latest`. - 1. In the job `Package_Module` after the job `gitversion` and before the job - `package` add this new job: - - ```yaml - - task: PowerShell@2 - name: Exp_Feature - displayName: 'Enable Experimental features' - inputs: - pwsh: true - targetType: inline - continueOnError: true - script: | - ./build.ps1 -Tasks noop -ResolveDependency - Import-Module GuestConfiguration - Enable-ExperimentalFeature -Name GuestConfiguration.Pester - Enable-ExperimentalFeature -Name GuestConfiguration.SetScenario - Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource -ErrorAction SilentlyContinue - env: - ModuleVersion: $(gitVersion.NuGetVersionV2) - ``` - - 1. Remove the job `Test_HQRM`. - 1. Remove the job `Test_Integration`. - 1. Remove the job `Code_Coverage`. - 1. Update deploy condition to use the Azure DevOps organization name: - - ``` yaml - contains(variables['System.TeamFoundationCollectionUri'], 'myorganizationname') - ```` - - 1. In the job `Deploy_Module` for both the deploy tasks `publishRelease` - and `sendChangelogPR` add the following environment variables: - - ```yaml - ReleaseBranch: main - MainGitBranch: main - ``` - -1. Create a new folder `GCPackages` under the folder `source`. -1. Create a new folder `UserAmyNotPresent` under the new folder `GCPackages`. -1. Under the folder `UserAmyNotPresent` create a new file `UserAmyNotPresent.config.ps1`. -1. In the file `UserAmyNotPresent.config.ps1` add the following: - - ```powershell - Configuration UserAmyNotPresent { - Import-DSCResource -ModuleName 'xPSDesiredStateConfiguration' - - Node UserAmyNotPresent - { - xUser 'UserAmyNotPresent' - { - Ensure = 'Absent' - UserName = 'amy' - } - } - } - ``` - -1. Now resolve dependencies and run the task `gcpack`: - - ```powershell - build.ps1 -task gcpack -ResolveDependency - ``` - -1. The built _Guest Configuration_ package can be found in the folder - `output\GCPolicyPackages\UserAmyNotPresent`. - -## Commands - -Refer to the comment-based help for more information about these commands. - -### `Add-Sample` - -This command is used to invoke a plaster template built-in the -Sampler module. With this function you can bootstrap your module project -by adding classes, functions and associated tests, examples and configuration -elements. - -#### Syntax - - -```plaintext -Add-Sample [[-Sample] ] [[-DestinationPath] ] [] -``` - - -#### Outputs - -None. - -#### Example - -```powershell -Add-Sample -Sample PublicFunction -PublicFunctionName Get-MyStuff -``` - -This example adds a public function to the module (in the current folder), -with a sample unit test that test the public function. - -### `Invoke-SamplerGit` - -This command executes git with the provided arguments and throws an error -if the call failed. - -#### Syntax - - -```plaintext -Invoke-SamplerGit [-Argument] [] -``` - - -#### Outputs - -[System.String] - -#### Example - -```powershell -Invoke-SamplerGit -Argument @('config', 'user.name', 'MyName') -``` +A full list of changes in each version can be found in the [change log][2]. +[1]: https://github.com/gaelcolas/Sampler/wiki +[2]: https://github.com/gaelcolas/Sampler/blob/main/CHANGELOG.md Calls git to set user name in the git config. ### `New-SampleModule` @@ -1058,10 +293,10 @@ Get-MofSchemaName [-Path] [] `[System.Collections.Hashtable]` -Property Name | Type | Description ---- | --- | --- -Name | `[System.String]` | The name of class -FriendlyName | `[System.String]` | The friendly name of the class +| Property Name | Type | Description | +| ------------- | ----------------- | ------------------------------ | +| Name | `[System.String]` | The name of class | +| FriendlyName | `[System.String]` | The friendly name of the class | #### Example @@ -1580,11 +815,11 @@ Split-ModuleVersion [[-ModuleVersion] ] [] `[System.Management.Automation.PSCustomObject]` -Property Name | Type | Description ---- | --- | --- -Version | `[System.String]` | The module version (without prerelease string) -PreReleaseString | `[System.String]` | The prerelease string part -ModuleVersion | `[System.String]` | The full semantic version +| Property Name | Type | Description | +| ---------------- | ----------------- | ---------------------------------------------- | +| Version | `[System.String]` | The module version (without prerelease string) | +| PreReleaseString | `[System.String]` | The prerelease string part | +| ModuleVersion | `[System.String]` | The full semantic version | #### Example @@ -1648,9 +883,9 @@ default path in variable `BuildModuleOutput`. ### `ModuleVersion` -The module version of the built module. Defaults to the property `NuGetVersionV2` +The module version of the built module. Defaults to the property `ModuleVersion` returned by the executable `gitversion`, or if the executable `gitversion` -is not available the the variable defaults to an empty string, and the +is not available the variable defaults to an empty string, and the build module task will use the version found in the Module Manifest. It is also possible to set the session environment variable `$env:ModuleVersion` diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 0911c71f..7d383d78 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -12,17 +12,18 @@ # } #} - InvokeBuild = 'latest' - PSScriptAnalyzer = 'latest' - Pester = 'latest' - Plaster = 'latest' - ModuleBuilder = 'latest' - MarkdownLinkCheck = 'latest' - ChangelogManagement = 'latest' - 'Sampler.GitHubTasks' = 'latest' - 'DscResource.Test' = 'latest' - 'DscResource.AnalyzerRules' = 'latest' - xDscResourceDesigner = 'latest' - PlatyPS = 'latest' - 'DscResource.DocGenerator' = 'latest' + InvokeBuild = 'latest' + PSScriptAnalyzer = 'latest' + Pester = 'latest' + Plaster = 'latest' + ModuleBuilder = 'latest' + MarkdownLinkCheck = 'latest' + ChangelogManagement = 'latest' + 'Sampler.GitHubTasks' = 'latest' + 'DscResource.Test' = 'latest' + 'DscResource.AnalyzerRules' = 'latest' + xDscResourceDesigner = 'latest' + PlatyPS = 'latest' + 'DscResource.DocGenerator' = 'latest' + 'Microsoft.PowerShell.PSResourceGet' = 'latest' } diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 index 96881e00..7faac925 100644 --- a/Resolve-Dependency.ps1 +++ b/Resolve-Dependency.ps1 @@ -3,7 +3,7 @@ Bootstrap script for PSDepend. .PARAMETER DependencyFile - Specifies the configuration file for the this script. The default value is + Specifies the dependency configuration file with modules for the this script. The default value is 'RequiredModules.psd1' relative to this script's path. .PARAMETER PSDependTarget @@ -21,7 +21,7 @@ .PARAMETER Scope Specifies the scope to bootstrap the PackageProvider and PSGet if not available. - THe default value is 'CurrentUser'. + The default value is 'CurrentUser'. .PARAMETER Gallery Specifies the gallery to use when bootstrapping PackageProvider, PSGet and @@ -32,19 +32,20 @@ Specifies the credentials to use with the Gallery specified above. .PARAMETER AllowOldPowerShellGetModule - Allow you to use a locally installed version of PowerShellGet older than - 1.6.0 (not recommended). Default it will install the latest PowerShellGet + Allows to use a locally installed version of PowerShellGet older than + 1.6.0 (not recommended). By default it will install the latest PowerShellGet if an older version than 2.0 is detected. .PARAMETER MinimumPSDependVersion - Allow you to specify a minimum version fo PSDepend, if you're after specific - features. + Allows to specify a minimum version fo PSDepend, if specific + features required. .PARAMETER AllowPrerelease - Not yet written. + Allows to install prerelease versions of modules when resolving dependencies. .PARAMETER WithYAML - Not yet written. + Specifies that provided dependency configuration file is in YAML format and related operations + such as ensuring the presence of PowerShell-Yaml module should be taken. .PARAMETER UseModuleFast Specifies to use ModuleFast instead of PowerShellGet to resolve dependencies @@ -233,7 +234,7 @@ if ($UseModuleFast -and -not (Get-Module -Name 'ModuleFast')) $moduleFastBootstrapScriptBlockParameters.UseMain = $true } - elseif($ModuleFastVersion) + elseif ($ModuleFastVersion) { if ($ModuleFastVersion -notmatch 'v') { diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index f80e3727..2ee35525 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -63,7 +63,7 @@ script and correct parameter values. This will also affect the use of parameter `-UseModuleFast` of the Resolve-Dependency.ps1 or build.ps1 script. #> - #UseModuleFast = $true + # UseModuleFast = $true #ModuleFastVersion = '0.1.2' #ModuleFastBleedingEdge = $true @@ -73,9 +73,9 @@ set to $false then PowerShellGet will be used to resolve dependencies. #> UsePSResourceGet = $true - PSResourceGetVersion = '1.0.1' + #PSResourceGetVersion = '1.2.0' # PowerShellGet compatibility module only works when using PSResourceGet or ModuleFast. - UsePowerShellGetCompatibilityModule = $true - UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' + # UsePowerShellGetCompatibilityModule = $true + # UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' } diff --git a/Sampler/Public/Get-SamplerBuildVersion.ps1 b/Sampler/Public/Get-SamplerBuildVersion.ps1 index 40eb8e8c..2fba3808 100644 --- a/Sampler/Public/Get-SamplerBuildVersion.ps1 +++ b/Sampler/Public/Get-SamplerBuildVersion.ps1 @@ -1,4 +1,3 @@ - <# .SYNOPSIS Calculates or retrieves the version of the Repository. @@ -13,7 +12,7 @@ Path to the Module Manifest that should determine the version if GitVersion is not available. .PARAMETER ModuleVersion - Provide the Version to be splitted and do not rely on GitVersion or the Module's manifest. + Provide the Version to be split and do not rely on GitVersion or the Module's manifest. .EXAMPLE Get-SamplerBuildVersion -ModuleManifestPath source\MyModule.psd1 @@ -31,27 +30,53 @@ function Get-SamplerBuildVersion [Parameter()] [System.String] - $ModuleVersion + $ModuleVersion = $env:ModuleVersion ) if ([System.String]::IsNullOrEmpty($ModuleVersion)) { Write-Verbose -Message 'Module version is not determined yet. Evaluating methods to get new module version.' - $gitVersionAvailable = Get-Command -Name 'gitversion' -ErrorAction 'SilentlyContinue' - $donetGitversionAvailable = Get-Command -Name 'dotnet-gitversion' -ErrorAction 'SilentlyContinue' + $gitVersionAvailable = Get-Command -Name gitversion -ErrorAction SilentlyContinue + $dotnetGitversionAvailable = Get-Command -Name 'dotnet-gitversion' -ErrorAction SilentlyContinue # If dotnet-gitversion is available and gitversion is not, alias it to gitversion. - if ($donetGitversionAvailable -and -not $gitVersionAvailable) + if ($dotnetGitversionAvailable -and -not $gitVersionAvailable) { - New-Alias -Name 'gitversion' -Value 'dotnet-gitversion' -Scope 'Script' -ErrorAction 'SilentlyContinue' + New-Alias -Name gitversion -Value dotnet-gitversion -Scope Script -ErrorAction SilentlyContinue } - if ($gitVersionAvailable -or $donetGitversionAvailable) + if ($gitVersionAvailable -or $dotnetGitversionAvailable) { Write-Verbose -Message 'Using the version from GitVersion.' - $ModuleVersion = (gitversion | ConvertFrom-Json -ErrorAction 'Stop').NuGetVersionV2 + $gitVersionObject = gitversion | ConvertFrom-Json -ErrorAction Stop + $isPreRelease = [bool]$gitVersionObject.PreReleaseLabel + + $ModuleVersion = if ($isPreRelease) + { + if ($gitVersionObject.BranchName -eq 'main') + { + $nextPreReleaseNumber = $gitVersionObject.PreReleaseNumber + $paddedNextPreReleaseNumber = '{0:D4}' -f $nextPreReleaseNumber + + #reutrn the version with the pre-release label and the next pre-release number + '{0}{1}{2}' -f $gitVersionObject.MajorMinorPatch, $gitVersionObject.PreReleaseLabelWithDash, $paddedNextPreReleaseNumber + } + else + { + #return the version with the pre-release label and the number of commits since the version source + '{0}{1}.{2}' -f $gitVersionObject.MajorMinorPatch, $gitVersionObject.PreReleaseLabelWithDash, $gitVersionObject.CommitsSinceVersionSource + } + } + else + { + #return the version without pre-release label + '{0}' -f $gitVersionObject.MajorMinorPatch + } + + Write-Verbose -Message ("GitVersion returned the version '{0}'." -f $ModuleVersion) + } elseif (-not [System.String]::IsNullOrEmpty($ModuleManifestPath)) { diff --git a/Sampler/Templates/Build/Resolve-Dependency.ps1 b/Sampler/Templates/Build/Resolve-Dependency.ps1 index 96881e00..f677feeb 100644 --- a/Sampler/Templates/Build/Resolve-Dependency.ps1 +++ b/Sampler/Templates/Build/Resolve-Dependency.ps1 @@ -233,7 +233,7 @@ if ($UseModuleFast -and -not (Get-Module -Name 'ModuleFast')) $moduleFastBootstrapScriptBlockParameters.UseMain = $true } - elseif($ModuleFastVersion) + elseif ($ModuleFastVersion) { if ($ModuleFastVersion -notmatch 'v') { diff --git a/Sampler/Templates/Build/Resolve-Dependency.psd1.template b/Sampler/Templates/Build/Resolve-Dependency.psd1.template index 08ec2a75..b86e3ba7 100644 --- a/Sampler/Templates/Build/Resolve-Dependency.psd1.template +++ b/Sampler/Templates/Build/Resolve-Dependency.psd1.template @@ -75,9 +75,9 @@ set to $false then PowerShellGet will be used to resolve dependencies. #> UsePSResourceGet = $true - PSResourceGetVersion = '1.0.1' + # PSResourceGetVersion = '1.2.0' # PowerShellGet compatibility module only works when using PSResourceGet or ModuleFast. - UsePowerShellGetCompatibilityModule = $true - UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' + # UsePowerShellGetCompatibilityModule = $true + # UsePowerShellGetCompatibilityModuleVersion = '3.0.23-beta23' } diff --git a/Sampler/Templates/Build/build.ps1 b/Sampler/Templates/Build/build.ps1 index c87affee..f005a4e0 100644 --- a/Sampler/Templates/Build/build.ps1 +++ b/Sampler/Templates/Build/build.ps1 @@ -70,6 +70,7 @@ only works then the method of downloading dependencies is PSResourceGet. This can also be configured in Resolve-Dependency.psd1. #> +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because how $PSDependTarget is assigned to splatting variable $resolveDependencyParams.')] [CmdletBinding()] param ( @@ -356,7 +357,6 @@ process } Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta - } finally { diff --git a/Sampler/Templates/ChocolateyPackage/myPackage/tools/chocolateyuninstall.ps1 b/Sampler/Templates/ChocolateyPackage/myPackage/tools/chocolateyuninstall.ps1 index 54e33551..ccec333d 100644 --- a/Sampler/Templates/ChocolateyPackage/myPackage/tools/chocolateyuninstall.ps1 +++ b/Sampler/Templates/ChocolateyPackage/myPackage/tools/chocolateyuninstall.ps1 @@ -34,7 +34,6 @@ $packageArgs = @{ #validExitCodes= @(0) #please insert other valid exit codes here } -$uninstalled = $false # Get-UninstallRegistryKey is new to 0.9.10, if supporting 0.9.9.x and below, # take a dependency on "chocolatey-core.extension" in your nuspec file. # This is only a fuzzy search if $softwareName includes '*'. Otherwise it is @@ -44,7 +43,7 @@ $uninstalled = $false if ($key.Count -eq 1) { - $key | % { + $key | ForEach-Object -Process { $packageArgs['file'] = "$($_.UninstallString)" #NOTE: You may need to split this if it contains spaces, see below if ($packageArgs['fileType'] -eq 'MSI') diff --git a/Sampler/Templates/ClassFolderResource/tests/Unit/ClassResource/DSC_ClassFolder.tests.ps1 b/Sampler/Templates/ClassFolderResource/tests/Unit/ClassResource/DSC_ClassFolder.tests.ps1 index 464d6141..d5f39c12 100644 --- a/Sampler/Templates/ClassFolderResource/tests/Unit/ClassResource/DSC_ClassFolder.tests.ps1 +++ b/Sampler/Templates/ClassFolderResource/tests/Unit/ClassResource/DSC_ClassFolder.tests.ps1 @@ -115,6 +115,7 @@ InModuleScope $ProjectName { It 'Should return the correct values when Shared is ' -TestCases $testCase { param ( + [Parameter()] [System.Boolean] $Shared ) @@ -260,18 +261,23 @@ InModuleScope $ProjectName { It 'Should return $false when ReadOnly is , and Hidden is ' -TestCases $testCase { param ( + [Parameter()] [System.String] $Path, + [Parameter()] [System.Boolean] $ReadOnly, + [Parameter()] [System.Boolean] $Hidden, + [Parameter()] [System.Boolean] $Shared, + [Parameter()] [System.String] $ShareName ) @@ -467,15 +473,19 @@ InModuleScope $ProjectName { It 'Should call the correct mocks when ReadOnly is , and Hidden is ' -TestCases $testCase { param ( + [Parameter()] [System.Boolean] $ReadOnly, + [Parameter()] [System.Boolean] $Hidden, + [Parameter()] [System.Boolean] $Shared, + [Parameter()] [System.String] $ShareName ) diff --git a/Sampler/Templates/Classes/tests/Unit/Classes/class1.tests.ps1 b/Sampler/Templates/Classes/tests/Unit/Classes/class1.tests.ps1 index 1237a875..b87fcc3a 100644 --- a/Sampler/Templates/Classes/tests/Unit/Classes/class1.tests.ps1 +++ b/Sampler/Templates/Classes/tests/Unit/Classes/class1.tests.ps1 @@ -1,3 +1,7 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path $ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and diff --git a/Sampler/Templates/Classes/tests/Unit/Classes/class11.tests.ps1 b/Sampler/Templates/Classes/tests/Unit/Classes/class11.tests.ps1 index c51e3853..445215ae 100644 --- a/Sampler/Templates/Classes/tests/Unit/Classes/class11.tests.ps1 +++ b/Sampler/Templates/Classes/tests/Unit/Classes/class11.tests.ps1 @@ -1,3 +1,7 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path $ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and diff --git a/Sampler/Templates/Classes/tests/Unit/Classes/class12.tests.ps1 b/Sampler/Templates/Classes/tests/Unit/Classes/class12.tests.ps1 index f6b24dcc..33e6fb6a 100644 --- a/Sampler/Templates/Classes/tests/Unit/Classes/class12.tests.ps1 +++ b/Sampler/Templates/Classes/tests/Unit/Classes/class12.tests.ps1 @@ -1,3 +1,7 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path $ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and diff --git a/Sampler/Templates/Classes/tests/Unit/Classes/class2.tests.ps1 b/Sampler/Templates/Classes/tests/Unit/Classes/class2.tests.ps1 index 16d327a8..a94ca511 100644 --- a/Sampler/Templates/Classes/tests/Unit/Classes/class2.tests.ps1 +++ b/Sampler/Templates/Classes/tests/Unit/Classes/class2.tests.ps1 @@ -1,3 +1,7 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path $ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and diff --git a/Sampler/Templates/MofResource/tests/Unit/Modules/Folder.Common.tests.ps1 b/Sampler/Templates/MofResource/tests/Unit/Modules/Folder.Common.tests.ps1 index 98ebbd10..942a26c4 100644 --- a/Sampler/Templates/MofResource/tests/Unit/Modules/Folder.Common.tests.ps1 +++ b/Sampler/Templates/MofResource/tests/Unit/Modules/Folder.Common.tests.ps1 @@ -1,3 +1,7 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + #region HEADER $ProjectPath = "$PSScriptRoot\..\..\.." | Convert-Path $ProjectName = (Get-ChildItem $ProjectPath\*\*.psd1 | Where-Object { diff --git a/Sampler/Templates/Sampler/README_dsccommunity.md.template b/Sampler/Templates/Sampler/README_dsccommunity.md.template index 775cd3a0..b3ff99a4 100644 --- a/Sampler/Templates/Sampler/README_dsccommunity.md.template +++ b/Sampler/Templates/Sampler/README_dsccommunity.md.template @@ -9,7 +9,7 @@ configuration of PRODUCT. ![Azure DevOps coverage (branch)](https://img.shields.io/azure-devops/coverage/dsccommunity/<%= $PLASTER_PARAM_ModuleName %>/9999/<%= if ($PLASTER_PARAM_GitMainBranch -eq 'master') { 'master' } else { 'main' } %>) [![codecov](https://codecov.io/gh/dsccommunity/<%= $PLASTER_PARAM_ModuleName %>/branch/<%= if ($PLASTER_PARAM_GitMainBranch -eq 'master') { 'master' } else { 'main' } %>/graph/badge.svg)](https://codecov.io/gh/dsccommunity/<%= $PLASTER_PARAM_ModuleName %>) [![Azure DevOps tests](https://img.shields.io/azure-devops/tests/dsccommunity/<%= $PLASTER_PARAM_ModuleName %>/9999/<%= if ($PLASTER_PARAM_GitMainBranch -eq 'master') { 'master' } else { 'main' } %>)](https://dsccommunity.visualstudio.com/<%= $PLASTER_PARAM_ModuleName %>/_test/analytics?definitionId=9999&contextType=build) -[![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/vpre/<%= $PLASTER_PARAM_ModuleName %>?label=<%= $PLASTER_PARAM_ModuleName %>%20Preview)](https://www.powershellgallery.com/packages/<%= $PLASTER_PARAM_ModuleName %>/) +[![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/v/<%= $PLASTER_PARAM_ModuleName %>?label=<%= $PLASTER_PARAM_ModuleName %>%20Preview&include_prelreleases)](https://www.powershellgallery.com/packages/<%= $PLASTER_PARAM_ModuleName %>/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/<%= $PLASTER_PARAM_ModuleName %>?label=<%= $PLASTER_PARAM_ModuleName %>)](https://www.powershellgallery.com/packages/<%= $PLASTER_PARAM_ModuleName %>/) ## Code of Conduct diff --git a/Sampler/Templates/Sampler/azure-pipelines.yml.template b/Sampler/Templates/Sampler/azure-pipelines.yml.template index 8e45b7a2..ca8547b1 100644 --- a/Sampler/Templates/Sampler/azure-pipelines.yml.template +++ b/Sampler/Templates/Sampler/azure-pipelines.yml.template @@ -262,10 +262,9 @@ stages: # filePath: './build.ps1' # arguments: '-tasks merge' # pwsh: true - #- task: PublishCodeCoverageResults@1 + #- task: PublishCodeCoverageResults@2 # displayName: 'Publish Azure Code Coverage' # inputs: - # codeCoverageTool: 'JaCoCo' # summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' # pathToSources: '$(Build.SourcesDirectory)/$(dscBuildVariable.RepositoryName)/' diff --git a/Sampler/Templates/Sampler/azure-pipelines_dsccommunity.yml.template b/Sampler/Templates/Sampler/azure-pipelines_dsccommunity.yml.template index 84ad9da7..e03a0057 100644 --- a/Sampler/Templates/Sampler/azure-pipelines_dsccommunity.yml.template +++ b/Sampler/Templates/Sampler/azure-pipelines_dsccommunity.yml.template @@ -233,10 +233,9 @@ stages: # filePath: './build.ps1' # arguments: '-tasks merge' # pwsh: true - - task: PublishCodeCoverageResults@1 + - task: PublishCodeCoverageResults@2 displayName: 'Publish Code Coverage to Azure DevOps' inputs: - codeCoverageTool: 'JaCoCo' summaryFileLocation: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' pathToSources: '$(Build.SourcesDirectory)/$(sourceFolderName)/' - script: | diff --git a/Sampler/Templates/Sampler/module.tests.ps1.template b/Sampler/Templates/Sampler/module.tests.ps1.template index effe4034..860f7daf 100644 --- a/Sampler/Templates/Sampler/module.tests.ps1.template +++ b/Sampler/Templates/Sampler/module.tests.ps1.template @@ -80,12 +80,13 @@ if ($PLASTER_PARAM_UseGit -eq 'true') that required files are changed. #> + $filesChanged = @() # Only run if there is a remote called origin if (((git remote) -match 'origin')) { $headCommit = &git rev-parse HEAD $defaultBranchCommit = &git rev-parse origin/main - $filesChanged = (&git @('diff', "$defaultBranchCommit...$headCommit", '--name-only') | + $filesChanged += (&git @('diff', "$defaultBranchCommit...$headCommit", '--name-only') | Where-Object { $_ -match "^$escapedGitRelatedModulePath" }) -replace "^$escapedGitRelatedModulePath", "" } diff --git a/Sampler/WikiSource/Getting-started.md b/Sampler/WikiSource/Getting-started.md new file mode 100644 index 00000000..b9a73c48 --- /dev/null +++ b/Sampler/WikiSource/Getting-started.md @@ -0,0 +1,1923 @@ +## Usage + +### How to create a new project + +To create a new project the command `New-SampleModule` should be used. Depending +on the template used with the command the content in project will contain +different sample content while some also adds additional pipeline jobs. But all +templates (except one) will have the basic tasks to have a working pipeline including +build, test and deploy stages. + +The sections below show how to use each template. The templates are: + +- `SimpleModule` - Creates a module with minimal structure and pipeline automation. +- `SimpleModule_NoBuild` - Creates a simple module without the build automation. +- `CompleteSample` - Creates a module with complete structure and example files. +- `dsccommunity` - Creates a DSC module according to the DSC Community baseline + with a pipeline for build, test, and release automation. +- `CustomModule` - Will prompt you for more details as to what you'd like to scaffold. +- `GCPackage` - Creates a module that can be deployed to be used with _Azure Policy_ + _Guest Configuration_. + +As per the video above, you can create a new module project with all files and +pipeline scripts. Once the project is created, the `build.ps1` inside the new +project folder is how you interact with the built-in pipeline automation, and +the file `build.yaml` is where you configure and customize it. + +#### `SimpleModule` + +Creates a module with minimal structure and pipeline automation. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'SimpleModule' + ModuleName = 'MySimpleModule' + ModuleAuthor = 'My Name' + ModuleDescription = 'MySimpleModule Description' +} + +New-SampleModule @newSampleModuleParameters +``` + +### Resolving dependencies + +The Sampler templates is configured to use PSResourceGet as the method of +resolving dependencies. The property `UsePSResourceGet` is default configured +to `$true` in the file Resolve-Dependency.psd1. If that configuration is +removed or disabled (set to `$false`) then resolving dependencies will +revert to PowerShellGet & PSDepend. + +The specification syntax of the file RequiredModules.psd1 works with all +three methods of resolving dependencies. + +```powershell +@{ + # Gives latest release + Pester = 'latest' + + # Gives specific release (also known as pinning version) + Pester = '4.10.1' + + # Gives latest preview release + 'ComputerManagementDsc' = @{ + Version = 'latest' + Parameters = @{ + AllowPrerelease = $true + } + } + + # Gives specific preview release (also known as pinning version) + 'ComputerManagementDsc' = @{ + Version = '9.1.0-preview0002' + Parameters = @{ + AllowPrerelease = $true + } + } +} +``` + +When using the method _PowerShellGet & PSDepend_ this configuration should +also be added to the file RequiredModules.psd1 to control the behavior +of PSDepend. This is only required if you need to use _PowerShellGet & PSDepend_. +It is not required for PSResourceGet or ModuleFast. + +```powershell +@{ + PSDependOptions = @{ + AddToPath = $true + Target = 'output\RequiredModules' + Parameters = @{ + Repository = 'PSGallery' + } + } +``` + +#### PSResourceGet + +It is possible to use [PSResourceGet](https://github.com/PowerShell/PSResourceGet) +to resolve dependencies. PSResourceGet works with Windows PowerShell and +PowerShell (some restrictions on versions exist). To use PSResourceGet as +a replacement for PowerShellGet it is possible to enable it in the configuration +file `Resolve-Dependency.psd1`. It is also possible to allow the repository +to use PowerShellGet as the default and choose to use PSResourceGet from the +command line by passing the parameter `UsePSResourceGet` to the build script +`build.ps1`, e.g. `.\build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet` + +If both PSResourceGet and ModuleFast is enabled then PSResource will be +preferred on Windows PowerShell and PowerShell 7.1 or lower. ModuleFast +will be preferred on PowerShell 7.2 or higher. + +#### ModuleFast + +It is possible to use [ModuleFast](https://github.com/JustinGrote/ModuleFast) +to resolve dependencies. ModuleFast only works with PowerShell 7.2 or higher. +To use ModuleFast as a replacement for PowerShellGet it is possible to +enable it in the configuration file `Resolve-Dependency.psd1`. +It is also possible to allow the repository to use PowerShellGet as the +default and choose to use ModuleFast from the command line by passing +the parameter `UseModuleFast` to the build script `build.ps1`, e.g. +`.\build.ps1 -ResolveDependency -Tasks noop -UseModuleFast`. + +If both PSResourceGet and ModuleFast is enabled then ModuleFast will be +preferred on PowerShell 7.2 or higher. PSResource will be preferred +on Windows PowerShell and PowerShell 7.1 or lower. + +When using ModuleFast as the only method there is more options to specify +modules in the file RequiredModules.psd1. This syntax will limit resolving +dependencies to just ModuleFast (and PowerShell 7.2 or higher) as they are +not supported by the other methods. See the comment-based help of the command +[`Install-ModuleFast`](https://github.com/JustinGrote/ModuleFast/blob/main/ModuleFast.psm1) +for more information of the available syntax. + +```powershell +@{ + # Gives the latest release + 'ComputerManagementDsc' = 'latest' + + # Gives the specific release + 'ComputerManagementDsc' = '9.0.0' + + # Gives the latest patch release for v9.0 + 'ComputerManagementDsc' = ':9.0.*' + + # Gives the latest preview release + 'ComputerManagementDsc' = '!' + + # Gives the latest release (including previews) that is higher that v9.0.0 + 'ComputerManagementDsc' = '!>9.0.0' + + # Must be exactly 9.1.0-preview0002 + 'ComputerManagementDsc' = '9.1.0-preview0002' + 'ComputerManagementDsc' = '@9.1.0-preview0002' + 'ComputerManagementDsc' = ':9.1.0-preview0002' + 'ComputerManagementDsc' = ':[9.1.0-preview0002]' + + # Must be a higher version than 9.1.0-preview0002 + 'ComputerManagementDsc' = '>9.1.0-preview0002' + + # Must be a lower version than 9.1.0-preview0002 + 'ComputerManagementDsc' = '<9.1.0-preview0002' + + # Must be a lower version than or equal to 9.1.0-preview0002 + 'ComputerManagementDsc' = '<=9.1.0-preview0002' + + # Must be a higher version than or equal to 9.1.0-preview0002 + 'ComputerManagementDsc' = '>=9.1.0-preview0002' + + # Exact range, exclusive. Must be lower version than 9.2.0. 9.2.0 is not allowed. + 'ComputerManagementDsc' = ':(,9.2.0)' + + # Exact range, exclusive. Must be higher version than 9.0.0. 9.0.0 is not allowed. + 'ComputerManagementDsc' = ':(9.0.0,)' + + # Exact range, inclusive. Must be version than 9.0.0 or higher up to or equal to 9.2.0. + 'ComputerManagementDsc' = ':[9.0.0,9.2.0]' +} + +``` + +#### PowerShellGet & PSDepend + +Because we resolve dependencies from a nuget feed, whether the public +PowerShell Gallery or your private repository, a working version of PowerShellGet +is required. Using PowerShellGet is the default if no other configuration +is done. We recommend the latest version of PowerShellGet v2. + +### Managing the Module versions (optional) + +Managing the versions of your module is tedious, and is hard to maintain +consistency over time. The usual tricks like checking what the latest version on +PowerShell Gallery is, or use the `BuildNumber` to increment a `0.0.x` version +works but isn't ideal, especially if we want to stick to [semver](https://semver.org/). + +While you can manage the version by updating the module manifest manually or by +letting your CI tool update the `ModuleVersion` environment variable, we think +the best method is to rely on the cross-platform tool [`GitVersion`](https://gitversion.net/docs/). + +[`GitVersion`](https://gitversion.net/docs/) will generate the version based on +the git history. You control what version to deploy using [git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging). + +Generally, GitVersion will look at the latest version tag, the branch names, commit +messages, to try to determine the Major/Minor/Patch (semantic versioning) based +on detected change (configurable in the file [`GitVersion.yml`](https://gitversion.net/docs/reference/configuration) +that is part of your project). + +Therefore, it is recommended that you install `GitVersion` on your development environment +and on your CI environment build agents. + +There are various ways to [install GitVersion](https://gitversion.net/docs/usage/cli/installation) +on your development environment. If you use Chocolatey (install and upgrade): + +```PowerShell +C:\> choco upgrade gitversion.portable +``` + +This describes how to [install GitVersion in your CI environment build agents](https://gitversion.net/docs/usage/ci) +if you plan to use the deploy pipelines in the CI. + +## Usage + +### How to create a new project + +To create a new project the command `New-SampleModule` should be used. Depending +on the template used with the command the content in project will contain +different sample content while some also adds additional pipeline jobs. But all +templates (except one) will have the basic tasks to have a working pipeline including +build, test and deploy stages. + +The sections below show how to use each template. The templates are: + +- `SimpleModule` - Creates a module with minimal structure and pipeline automation. +- `SimpleModule_NoBuild` - Creates a simple module without the build automation. +- `CompleteSample` - Creates a module with complete structure and example files. +- `dsccommunity` - Creates a DSC module according to the DSC Community baseline + with a pipeline for build, test, and release automation. +- `CustomModule` - Will prompt you for more details as to what you'd like to scaffold. +- `GCPackage` - Creates a module that can be deployed to be used with _Azure Policy_ + _Guest Configuration_. + +As per the video above, you can create a new module project with all files and +pipeline scripts. Once the project is created, the `build.ps1` inside the new +project folder is how you interact with the built-in pipeline automation, and +the file `build.yaml` is where you configure and customize it. + +#### `SimpleModule` + +Creates a module with minimal structure and pipeline automation. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'SimpleModule' + ModuleName = 'MySimpleModule' + ModuleAuthor = 'My Name' + ModuleDescription = 'MySimpleModule Description' +} + +New-SampleModule @newSampleModuleParameters +``` + +#### `SimpleModule_NoBuild` + +Creates a simple module without the build automation. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'SimpleModule_NoBuild' + ModuleName = 'MySimpleModuleNoBuild' + ModuleAuthor = 'My Name' + ModuleDescription = 'MySimpleModuleNoBuild Description' +} + +New-SampleModule @newSampleModuleParameters +``` + +#### `CompleteSample` + +Creates a module with complete structure and example files. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'CompleteSample' + ModuleName = 'MyCompleteSample' + ModuleAuthor = 'My Name' + ModuleDescription = 'MyCompleteSample Description' +} + +New-SampleModule @newSampleModuleParameters +``` + +#### `dsccommunity` + +Creates a DSC module according to the DSC Community baseline with a pipeline +for build, test, and release automation. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'dsccommunity' + ModuleName = 'MyDscModule' + ModuleAuthor = 'My Name' + ModuleDescription = 'MyDscModule Description' +} + +New-SampleModule @newSampleModuleParameters +``` + +#### `CustomModule` + +Will prompt you for more details as to what you'd like to scaffold. + +```powershell +Install-Module -Name 'Sampler' -Scope 'CurrentUser' + +$samplerModule = Import-Module -Name Sampler -PassThru + +$invokePlasterParameters = @{ + TemplatePath = Join-Path -Path $samplerModule.ModuleBase -ChildPath 'Templates/Sampler' + DestinationPath = 'C:\source' + ModuleType = 'CustomModule' + ModuleName = 'MyCustomModule' + ModuleAuthor = 'My Name' + ModuleDescription = 'MyCustomModule Description' +} + +Invoke-Plaster @invokePlasterParameters +``` + +#### `GCPackage` + +>**Note:** The `GCPackage` template is not yet available, but can be created using +>the `dsccommunity` template with modifications, see the section [GCPackage scaffolding](#gcpackage-scaffolding). + +### How to work with the multi-module repository + +Typically, such as with a community-driven module, you would have a single +git repository dedicated to each module. However, there are situations where +you might opt for a single git repository to encompass multiple modules. If +you intend to establish a multi-module repository, ensure that each module +is housed within its own folder. Each module's folder structure should be +distinct and should not overlap with the folder structure of other modules. + +> [!TIP] +> Right folder structure + +```bash +GitRootFolder +├───Module1 +├───Module2 +├───SomeModuleGroup #Not a module +│ ├───GroupModule1 +│ └───GroupModule2 +└───Module3 +``` + +> [!CAUTION] +> Wrong folder structure + +```bash +GitRootFolder +├───Module1 +├───Module2 +├───Module3 +│ ├───SubModule1 +│ └───SubModule2 +└───Module3 +``` + +>[!NOTE] +>You can utilize the gitversion tag-prefix to differentiate tags for each module +>separately. [gitVersion configuration](https://gitversion.net/docs/reference/configuration) + +### How to download dependencies for the project + +To be able to build the project, all the dependencies listed in the file +`RequiredModules.psd1` must first be available. This is the beginning of +the build process so that anyone doing a git clone can 're-hydrate' the +project and start testing and producing the build artefact locally with +minimal environmental dependencies. + +>[!NOTE] +>Try to avoid mixing these different methods in the same session. When +>switching to use a different method, open a new PowerShell session so +>none of the modules dependencies are loaded into the session. + +```mermaid +graph LR + +RD[Resolve dependencies] --> Method{Method?} +Method{Method?} -->|"(Legacy, to use, +disable other +methods)"| PowerShellGet(["PowerShellGet"]) +Method -->|"parameter +-UseModuleFast"| ModuleFast(["ModuleFast"]) +Method -->|"(Default), +parameter +-UsePSResourceGet"| PSResourceGet(["PSResourceGet"]) +PowerShellGet -->|"Invoke-PSDepend"| InvokeRD +ModuleFast -->|"Install-ModuleFast"| InvokeRD +PSResourceGet -->|"Save-PSResource"| InvokeRD +InvokeRD[Use preferred method] <--> PSGallery["PowerShell Gallery"] +InvokeRD ---> Save[["Save to RequiredModules"]] +``` + +The following command will resolve dependencies using PSResourceGet: + +```powershell +cd C:\source\MySimpleModule + +./build.ps1 -ResolveDependency -Tasks noop +``` + +The following command will resolve dependencies using [ModuleFast](https://github.com/JustinGrote/ModuleFast): + +```powershell +cd C:\source\MySimpleModule + +./build.ps1 -ResolveDependency -Tasks noop -UseModuleFast +``` + +The following command will resolve dependencies using [PSResourceGet](https://github.com/PowerShell/PSResourceGet): + +```powershell +cd C:\source\MySimpleModule + +./build.ps1 -ResolveDependency -Tasks noop -UsePSResourceGet +``` + +The dependencies will be downloaded (or updated) from the PowerShell Gallery (unless +another repository is specified) and saved in the project folder under +`./output/RequiredModules`. + +> By default, each repository should not rely on your personal development +> environment, so that it's easier to repeat on any machine or build agent. + +Normally this command only needs to be run once, but the command can be run +anytime to update to a newer version of a required module (if one is available), +or if the required modules have changed in the file `RequiredModules.psd1`. + +> **Note:** If a required module is removed in the file `RequiredModules.psd1` +> that module will not be automatically removed from the folder +> `./output/RequiredModules`. + +### How to build the project + +The following command will build the project: + +```powershell +cd C:\source\MySimpleModule + +./build.ps1 -Tasks build +``` + +It is also possible to resolve dependencies and build the project +at the same time using the command: + +```powershell +./build.ps1 -ResolveDependency -Tasks build +``` + +If there are any errors during build they will be shown in the output and the +build will stop. If it is successful the output should end with: + +```plaintext +Build succeeded. 7 tasks, 0 errors, 0 warnings 00:00:06.1049394 +``` + +> **NOTE:** The number of tasks can differ depending on which template that +> was used to create the project. + +### How to set up the build environment in the current PowerShell session + +If you only want to make sure the environment is configured, or you only want +to resolve the dependencies, you can call the built-in task `noop` ("no operation") +which won't do anything other than a quick way to run the bootstrap script (there +is no code that executes in the `noop` task). + +```powershell +./build.ps1 -Tasks noop +``` + +>**Note:** For the built-in `noop` task to work, the dependencies must first +>have been resolved. + +### How to run tests + + +> [!NOTE] +> Which tests are run is determined by the paths configured +> by a key in the _Pester_ configuration in the file `build.yml`. The key +> differs depending on the version of _Pester_ being used. The key is `Script` +> when using _Pester v4_, and `Path` when using _Pester v5_. + +> [!IMPORTANT] +>If running (or debugging) tests in Visual Studio Code you should first make sure +>the session environment is set correctly. This is normally done when you build +>the project. But if there is no need to rebuild the project it is faster to run +>the [built-in task `noop`](#how-to-set-up-the-build-environment-in-the-current-powershell-session) +>in the _PowerShell Integrated Console_. + + +Running all the unit tests, the quality tests and show code coverage can +be achieved by running the command: + +```powershell +`./build.ps1 -Tasks test` +``` + +Integration tests are not run by default when using the build task `test`. +To run the integration test use the following command: + +```powershell +`./build.ps1 -Tasks test -PesterPath 'tests/Integration' -CodeCoverageThreshold 0` +``` + +To run all tests in a specific folder use the parameter `PesterPath` and +optionally `CodeCoverageThreshold` set to `0` to turn off code coverage. +This runs all the quality tests: + +```powershell +`./build.ps1 -Tasks test -PesterPath 'tests/QA' -CodeCoverageThreshold 0` +``` + +To run a specific test file, again use the parameter `PesterPath` and +optionally `CodeCoverageThreshold` set to `0` to turn off code coverage. +This runs just the specific test file `New-SamplerXmlJaCoCoCounter.tests.ps1`: + + +```powershell +./build.ps1 -Tasks test -PesterPath ./tests/Unit/Private/New-SamplerXmlJaCoCoCounter.tests.ps1 -CodeCoverageThreshold 0 +``` + + +### How to run the default workflow + +It is possible to do all of the above (resolve dependencies, build, and run tests) +in just one line by running the following: + +```powershell +./build -ResolveDependency +``` + +The parameter `Task` is not used which means this will run the default workflow +(`.`). The tasks for the default workflow are configured in the file `build.yml`. +Normally the default workflow builds the project and runs all the configured test. + +This means by running this it will build and run all configured tests: + +```powershell +./build.ps1 +``` + +### How to list all available tasks + +Because the build tasks are `InvokeBuild` tasks, we can discover them using +the `?` task. So to list the available tasks in a project, run the following +command: + +```powershell +./build.ps1 -Tasks ? +``` + +> **NOTE:** If it is not already done, first make sure to resolve dependencies. +> Dependencies can also hold tasks that are used in the pipeline. + +## About the bootstrap process (`build.ps1`) + +The `build.ps1` is the _entry point_ to invoke any task, or a list of build +tasks (workflow), leveraging the [`Invoke-Build`](https://www.powershellgallery.com/packages/InvokeBuild) +task runner. + +The script does not assume your environment has the required PowerShell modules, +so the bootstrap is done by the project's script file `build.ps1`, and can +resolve the dependencies listed in the project's file `RequiredModules.psd1` +using [`PSDepend`](https://www.powershellgallery.com/packages/PSDepend). + +Invoking `build.ps1` with the `-ResolveDependency` parameter will prepare your +environment like so: + +1. Updates the session environment variable (`$env:PSModulePath`) to resolve + the built module (`.\output`) and the modules in the folder `./output/RequiredModules` + by prepending those paths to `$env:PSModulePath`. By prepending the paths + to the session `$env:PSModulePath` the build process will make those + dependencies available in your session for module discovery and auto-loading, + and also make it possible to use one or more of those modules as part of your built + module. +1. (Optional) Making sure you have a compatible version of the modules _PowerShellGet_ and + _PackageManagement_ (`version -gt 1.6`). If not, these will be installed from the + configured repository. Only required if you plan to use legacy PowerShellGet, default + PSResourceGet is used. +1. Download or install the `PowerShell-yaml` and `PSDepend` modules needed + for further dependency management. +1. Read the `build.yaml` configuration. +1. If the Nuget package provider is not present, install and import Nuget PackageProvider + (proxy enabled). +1. Invoke [PSDepend](https://www.powershellgallery.com/packages/PSDepend) on + the file `RequiredModules.psd1`. It will not install required modules to + your environment, it will save them to your project's folder `./output/RequiredModules`. +1. Hand over the task execution to `Invoke-Build` to run the configured + workflow. + +## About Sampler build workflow + +Let's look at the pipeline of the `Sampler` module itself to better understand +how the pipeline automation is configured for a project created using a +template from the Sampler module. + +> **NOTE:** Depending on the Sampler template used when creating a new project +> there can be additional configuration options - but they can all be added +> manually when those options are needed. The Sampler project itself does not use +> all features available (an example is DSC resources documentation generation). + +### Default Workflow Currently configured + +As seen in the bootstrap process above, the different workflows can be configured +by editing the `build.psd1`: new tasks can be loaded, and the sequence can be +added under the `BuildWorkflow` key by listing the names. + +In our case, the `build.yaml` defines several workflows (`.`, +`build`, `pack`, `hqrmtest`, `test`, and `publish`) that can be called by using: + +```PowerShell + .\build.ps1 -Tasks +``` + +The detail of the **default workflow** is as follow (InvokeBuild defaults to +the workflow named '.' when no tasks is specified): + +```yml +BuildWorkflow: + '.': + - build + - test +``` + +The tasks `build` and `tests` are meta-tasks or workflow calling other tasks: + +```yml + build: + - Clean + - Build_Module_ModuleBuilder + - Build_NestedModules_ModuleBuilder + - Create_changelog_release_output + test: + - Pester_Tests_Stop_On_Fail + - Pester_if_Code_Coverage_Under_Threshold + - hqrmtest +``` + +Those tasks are imported from a module, in this case from +the `.build/` folder, from this `Sampler` module, +but for another module you would use this line in your `build.yml` config: + +```yaml +ModuleBuildTasks: + Sampler: + - '*.build.Sampler.ib.tasks' # this means: import (dot source) all aliases ending with .ib.tasks exported by 'Sampler' module +``` + +You can edit your `build.yml` to change the workflow, add a custom task, +create repository-specific task in a `.build/` folder named `*.build.ps1`. + +```yml + MyTask: { + # do something with some PowerShellCode + Write-Host "Doing something in a task" + } + + build: + - Clean + - MyTask + - call_another_task +``` + +## GCPackage scaffolding + +Creates a module that can be deployed to be used with _Azure Policy_ +_Guest Configuration_. This process will be replaced with a Plaster template. + +1. Start by creating a new project using the template `dsccommunity`. + + ```powershell + Install-Module -Name 'Sampler' -Scope 'CurrentUser' + + $newSampleModuleParameters = @{ + DestinationPath = 'C:\source' + ModuleType = 'dsccommunity' + ModuleName = 'MyGCPackages' + ModuleAuthor = 'My Name' + ModuleDescription = 'MyGCPackages Description' + } + + New-SampleModule @newSampleModuleParameters + ``` + +1. In the file `build.yaml` add the following top-level key: + + ```yaml + BuiltModuleSubdirectory: module + ``` + +1. In the file `build.yaml` modify the `pack` key under the top-level key + `BuildWorkflow` by adding the task `gcpack`: + + ```yaml + pack: + - build + - package_module_nupkg + - gcpack + ``` + +1. In the file `build.yaml` modify the `GitHubConfig` top-level key + as follows: + + ```yaml + GitHubConfig: + GitHubFilesToAdd: + - 'CHANGELOG.md' + ReleaseAssets: + - output/GCPolicyPackages/UserAmyNotPresent*.zip + GitHubConfigUserName: myGitHubUserName + GitHubConfigUserEmail: myEmail@address.com + UpdateChangelogOnPrerelease: false + ``` + +1. In the file `RequiredModules.psd1` add the module _GuestConfiguration_ + and `xPSDesiredStateConfiguration` to the list of dependency modules. + + ```powershell + @{ + # ... current dependencies + + xPSDesiredStateConfiguration = 'latest' + GuestConfiguration = @{ + Version = 'latest' + Parameters = @{ + AllowPrerelease = $true + } + } + } + ``` + +1. Modify the `azure-pipelines.yml` as follows: + 1. Replace build image with `windows-latest`. + 1. In the job `Package_Module` after the job `gitversion` and before the job + `package` add this new job: + + ```yaml + - task: PowerShell@2 + name: Exp_Feature + displayName: 'Enable Experimental features' + inputs: + pwsh: true + targetType: inline + continueOnError: true + script: | + ./build.ps1 -Tasks noop -ResolveDependency + Import-Module GuestConfiguration + Enable-ExperimentalFeature -Name GuestConfiguration.Pester + Enable-ExperimentalFeature -Name GuestConfiguration.SetScenario + Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource -ErrorAction SilentlyContinue + env: + ModuleVersion: $(gitVersion.NuGetVersionV2) + ``` + + 1. Remove the job `Test_HQRM`. + 1. Remove the job `Test_Integration`. + 1. Remove the job `Code_Coverage`. + 1. Update deploy condition to use the Azure DevOps organization name: + + ``` yaml + contains(variables['System.TeamFoundationCollectionUri'], 'myorganizationname') + ```` + + 1. In the job `Deploy_Module` for both the deploy tasks `publishRelease` + and `sendChangelogPR` add the following environment variables: + + ```yaml + ReleaseBranch: main + MainGitBranch: main + ``` + +1. Create a new folder `GCPackages` under the folder `source`. +1. Create a new folder `UserAmyNotPresent` under the new folder `GCPackages`. +1. Under the folder `UserAmyNotPresent` create a new file `UserAmyNotPresent.config.ps1`. +1. In the file `UserAmyNotPresent.config.ps1` add the following: + + ```powershell + Configuration UserAmyNotPresent { + Import-DSCResource -ModuleName 'xPSDesiredStateConfiguration' + + Node UserAmyNotPresent + { + xUser 'UserAmyNotPresent' + { + Ensure = 'Absent' + UserName = 'amy' + } + } + } + ``` + +1. Now resolve dependencies and run the task `gcpack`: + + ```powershell + build.ps1 -task gcpack -ResolveDependency + ``` + +1. The built _Guest Configuration_ package can be found in the folder + `output\GCPolicyPackages\UserAmyNotPresent`. + +## Commands + +Refer to the comment-based help for more information about these commands. + +### `Add-Sample` + +This command is used to invoke a plaster template built-in the +Sampler module. With this function you can bootstrap your module project +by adding classes, functions and associated tests, examples and configuration +elements. + +#### Syntax + + +```plaintext +Add-Sample [[-Sample] ] [[-DestinationPath] ] [] +``` + + +#### Outputs + +None. + +#### Example + +```powershell +Add-Sample -Sample PublicFunction -PublicFunctionName Get-MyStuff +``` + +This example adds a public function to the module (in the current folder), +with a sample unit test that test the public function. + +### `Invoke-SamplerGit` + +This command executes git with the provided arguments and throws an error +if the call failed. + +#### Syntax + + +```plaintext +Invoke-SamplerGit [-Argument] [] +``` + + +#### Outputs + +[System.String] + +#### Example + +```powershell +Invoke-SamplerGit -Argument @('config', 'user.name', 'MyName') +``` + +Calls git to set user name in the git config. + +### `New-SampleModule` + +This command helps you scaffold your PowerShell module project by creating +the folder structure of your module, and optionally add the pipeline files +to help with compiling the module, publishing to a repository like +_PowerShell Gallery_ and GitHub, and testing quality and style such as +per the DSC Community guidelines. + +#### Syntax + + +```plaintext +New-SampleModule -DestinationPath [-ModuleType ] [-ModuleAuthor ] + -ModuleName [-ModuleDescription ] [-CustomRepo ] + [-ModuleVersion ] [-LicenseType ] [-SourceDirectory ] + [] + +New-SampleModule -DestinationPath [-ModuleAuthor ] -ModuleName + [-ModuleDescription ] [-CustomRepo ] [-ModuleVersion ] + [-LicenseType ] [-SourceDirectory ] [-Features ] + [] +``` + + +#### Outputs + +None. + +#### Example + +See section [Usage](#usage). + +## Commands for Build Tasks + +These commands are primarily meant to be used in tasks that exist either +in Sampler or in third-party modules. + +Refer to the comment-based help for more information about these commands. + +### `Convert-SamplerHashtableToString` + +Convert a Hashtable to a string representation. For instance, calling the +function with this hashtable: + +```powershell +@{a=1;b=2; c=3; d=@{dd='abcd'}} +``` + +will return: + +```plaintext +a=1; b=2; c=3; d={dd=abcd} +``` + +#### Syntax + + +```plaintext +Convert-SamplerHashtableToString [[-Hashtable] ] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Convert-SamplerhashtableToString -Hashtable @{a=1;b=2; c=3; d=@{dd='abcd'}} +``` + +This example will return the string representation of the provided hashtable. + +### `Get-BuiltModuleVersion` + +Will read the properties `ModuleVersion` and `PrivateData.PSData.Prerelease` tag +of the module manifest for a module that has been built by Sampler. The command +looks into the **OutputDirectory** where the project's module should have been +built. + +#### Syntax + + +```plaintext +Get-BuiltModuleVersion [-OutputDirectory] [[-BuiltModuleSubdirectory] ] + [-ModuleName] [-VersionedOutputDirectory] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-BuiltModuleVersion -OutputDirectory 'output' -ProjectName 'MyModuleName' +``` + +This example will return the module version of the built module 'MyModuleName'. + +### `Get-ClassBasedResourceName` + +This command returns all the class-based DSC resource names in a script file. +The script file is parsed for classes with the `[DscResource()]` attribute. + +> **Note:** For MOF-based DSC resources, look at the command +>[`Get-MofSchemaName`](#get-mofschemaname). + +#### Syntax + + +```plaintext +Get-ClassBasedResourceName [-Path] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-ClassBasedResourceName -Path 'source/Classes/MyDscResource.ps1' +``` + +This example will return the class-based DSC resource names in the script +file **MyDscResource.ps1**. + + +```powershell +Import-Module -Name 'MyResourceModule' + +$module = Get-Module -Name 'MyResourceModule' + +Get-ClassBasedResourceName -Path (Join-Path -Path $module.ModuleBase -ChildPath $module.RootModule) +``` + + +This example will return the class-based DSC resource names in built module +script file for the module named 'MyResourceModule'. + +### `Get-CodeCoverageThreshold` + +This command returns the **CodeCoverageThreshold** from the build configuration +(or overridden if the parameter `RuntimeCodeCoverageThreshold` is passed). + +#### Syntax + + +```plaintext +Get-CodeCoverageThreshold [[-RuntimeCodeCoverageThreshold] ] + [[-BuildInfo] ] [] +``` + + +#### Outputs + +`[System.Int]` + +#### Example + +```powershell +Get-CodeCoverageThreshold -RuntimeCodeCoverageThreshold 0 +``` + +This example will override the code coverage threshold in the build +configuration and return the value pass in the parameter **RuntimeCodeCoverageThreshold**. + +### `Get-MofSchemaName` + +This command looks within a DSC resource's .MOF schema file to find the name +and friendly name of the class. + +#### Syntax + + +```plaintext +Get-MofSchemaName [-Path] [] +``` + + +#### Outputs + +`[System.Collections.Hashtable]` + +Property Name | Type | Description +--- | --- | --- +Name | `[System.String]` | The name of class +FriendlyName | `[System.String]` | The friendly name of the class + +#### Example + +```powershell +Get-MofSchemaName -Path Source/DSCResources/MyResource/MyResource.schema.mof +``` + +This example will return a hashtable containing the name and friendly name +of the MOF-based resource **MyResource**. + +### `Get-OperatingSystemShortName` + +This command tells what the platform is; `Windows`, `Linux`, or `MacOS`. + +#### Syntax + + +```plaintext +Get-OperatingSystemShortName [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-OperatingSystemShortName +``` + +This example will return what platform it is run on. + +### `Get-PesterOutputFileFileName` + +This command creates a file name to be used as Pester output XML file name. +The file name will be composed in the format: +`${ProjectName}_v${ModuleVersion}.${OsShortName}.${PowerShellVersion}.xml` + +#### Syntax + + +```plaintext +Get-OperatingSystemShortName [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-PesterOutputFileFileName -ProjectName 'Sampler' -ModuleVersion '0.110.4-preview001' -OsShortName 'Windows' -PowerShellVersion '5.1' +``` + + +This example will return the string `Sampler_v0.110.4-preview001.Windows.5.1.xml`. + +### `Get-SamplerAbsolutePath` + +This command will resolve the absolute value of a path, whether it's +potentially relative to another path, relative to the current working +directory, or it's provided with an absolute path. + +The path does not need to exist, but the command will use the right +`[System.Io.Path]::DirectorySeparatorChar` for the OS, and adjust the +`..` and `.` of a path by removing parts of a path when needed. + +> **Note:** When the root drive is omitted on Windows, the path is not +> considered absolute. + +#### Syntax + + +```plaintext +Get-SamplerAbsolutePath [[-Path] ] [[-RelativeTo] ] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-SamplerAbsolutePath -Path '/src' -RelativeTo 'C:\Windows' +``` + +This example will return the string `C:\src` on Windows. + +```powershell +Get-SamplerAbsolutePath -Path 'MySubFolder' -RelativeTo '/src' +``` + +This example will return the string `C:\src\MySubFolder` on Windows. + +### `Get-SamplerBuiltModuleBase` + +This command returns the module base of the built module. + +#### Syntax + + +```plaintext +Get-SamplerBuiltModuleBase [-OutputDirectory] [[-BuiltModuleSubdirectory] ] + [-ModuleName] [-VersionedOutputDirectory] [[-ModuleVersion] ] + [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-SamplerBuiltModuleBase -OutputDirectory 'C:\src\output' -BuiltModuleSubdirectory 'Module' -ModuleName 'stuff' -ModuleVersion '3.1.2-preview001' +``` + + +This example will return the string `C:\src\output\Module\stuff\3.1.2`. + +### `Get-SamplerBuiltModuleManifest` + +This command returns the path to the built module's manifest. + +#### Syntax + + +```plaintext +Get-SamplerBuiltModuleManifest [-OutputDirectory] [[-BuiltModuleSubdirectory] ] + [-ModuleName] [-VersionedOutputDirectory] [[-ModuleVersion] ] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-SamplerBuiltModuleManifest -OutputDirectory 'C:\src\output' -BuiltModuleSubdirectory 'Module' -ModuleName 'stuff' -ModuleVersion '3.1.2-preview001' +``` + + +This example will return the string `C:\src\output\Module\stuff\3.1.2\stuff.psd1`. + +### `Get-SamplerCodeCoverageOutputFile` + +This command resolves the code coverage output file path from the project's +build configuration. + +#### Syntax + + +```plaintext +Get-SamplerCodeCoverageOutputFile [-BuildInfo] [-PesterOutputFolder] + [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-SamplerCodeCoverageOutputFile -BuildInfo $buildInfo -PesterOutputFolder 'C:\src\MyModule\Output\testResults' +``` + + +This example will return the code coverage output file path. + +### `Get-SamplerCodeCoverageOutputFileEncoding` + +This command resolves the code coverage output file encoding from the project's +build configuration. + +#### Syntax + + +```plaintext +Get-SamplerCodeCoverageOutputFileEncoding [-BuildInfo] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-SamplerCodeCoverageOutputFileEncoding -BuildInfo $buildInfo +``` + + +This example will return the code coverage output file encoding. + +### `Get-SamplerModuleInfo` + +This command loads a module manifest and returns the hashtable. +This implementation works around the issue where Windows PowerShell has +issues with the pwsh `$env:PSModulePath` such as in _VS Code_ with the _VS Code_ +_PowerShell extension_. + +#### Syntax + + +```plaintext +Get-SamplerModuleInfo [-ModuleManifestPath] [] +``` + + +#### Outputs + +`[System.Collections.Hashtable]` + +#### Example + + +```powershell +Get-SamplerModuleInfo -ModuleManifestPath 'C:\src\MyProject\output\MyProject\MyProject.psd1' +``` + + +This example will return the module manifest's hashtable. + +### `Get-SamplerModuleRootPath` + +This command reads the module manifest (.psd1) and if the `ModuleRoot` property +is defined it will resolve its absolute path based on the module manifest's +path. If there is no `ModuleRoot` property defined, then this function will +return `$null`. + +#### Syntax + + +```plaintext +Get-SamplerModuleRootPath [-ModuleManifestPath] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + + +```powershell +Get-SamplerModuleRootPath -ModuleManifestPath C:\src\MyModule\output\MyModule\2.3.4\MyModule.psd1 +``` + + +This example will return the path to module script file, e.g. `C:\src\MyModule\output\MyModule\2.3.4\MyModule.psm1`. + +### `Get-SamplerProjectName` + +This command returns the project name based on the module manifest, if no +module manifest is available it will return `$null`. + +#### Syntax + + +```plaintext +Get-SamplerProjectName [-BuildRoot] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-SamplerProjectName -BuildRoot 'C:\src\MyModule' +``` + +This example will return the project name of the module in the path `C:\src\MyModule`. + +### `Get-SamplerSourcePath` + +This command returns the project's source path based on the module manifest, +if no module manifest is available it will return `$null`. + +#### Syntax + + +```plaintext +Get-SamplerSourcePath [-BuildRoot] [] +``` + + +#### Outputs + +`[System.String]` + +#### Example + +```powershell +Get-SamplerSourcePath -BuildRoot 'C:\src\MyModule' +``` + +This example will return the project's source path of the module in the +path `C:\src\MyModule`. + +### `Merge-JaCoCoReport` + +This command merges two JaCoCo reports and return the resulting merged JaCoCo +report. + +> **Note:** Also see the command [Update-JaCoCoStatistic](#ppdate-jacocostatistic). + +#### Syntax + + +```plaintext +Merge-JaCoCoReport [-OriginalDocument] [-MergeDocument] + [] +``` + + +#### Outputs + +`[System.Xml.XmlDocument]` + +#### Example + + +```powershell +Merge-JaCoCoReport -OriginalDocument 'C:\src\MyModule\Output\JaCoCoRun_linux.xml' -MergeDocument 'C:\src\MyModule\Output\JaCoCoRun_windows.xml' +``` + + +This example will merge the JaCoCo report `JaCoCoRun_windows.xml` into the +JaCoCo report `JaCoCoRun_linux.xml` and then return the resulting JaCoCo report. + +### `New-SamplerJaCoCoDocument` + +This command creates a new JaCoCo XML document based on the provided missed +and hit lines. This command is usually used together with the output object +from Pester that also have been passed through ModuleBuilder's command +`Convert-LineNumber`. + +#### Syntax + + +```plaintext +New-SamplerJaCoCoDocument [-MissedCommands] [-HitCommands] + [-PackageName] [[-PackageDisplayName] ] [] +``` + + +#### Outputs + +`[System.Xml.XmlDocument]` + +#### Example + + +```powershell +# Assuming Pester 4, for Pester 5 change the commands accordingly. +$pesterObject = Invoke-Pester ./tests/unit -CodeCoverage -PassThru + +$pesterObject.CodeCoverage.MissedCommands | + Convert-LineNumber -ErrorAction 'Stop' -PassThru | Out-Null + +$pesterObject.CodeCoverage.HitCommands | + Convert-LineNumber -ErrorAction 'Stop' -PassThru | Out-Null + +New-SamplerJaCoCoDocument ` + -MissedCommands $pesterObject.CodeCoverage.MissedCommands ` + -HitCommands $pesterObject.CodeCoverage.HitCommands ` + -PackageName 'source' +``` + + +This example will create a new JaCoCo report based on the commands that +was hit or missed from the Pester run. It will use the ModuleBuilder's +command `Convert-LineNumber` to correlate the correct line number from +the built module script file to the source script files. + + +```powershell +New-SamplerJaCoCoDocument ` + -MissedCommands @{ + Class = 'ResourceBase' + Function = 'Compare' + HitCount = 0 + SourceFile = '.\Classes\001.ResourceBase.ps1' + SourceLineNumber = 4 + } ` + -HitCommands @{ + Class = 'ResourceBase' + Function = 'Compare' + HitCount = 2 + SourceFile = '.\Classes\001.ResourceBase.ps1' + SourceLineNumber = 3 + } ` + -PackageName 'source' +``` + + +This example will create a new JaCoCo report based on the two hashtables +containing hit or missed line. + +### `Out-SamplerXml` + +This command outputs an XML document to the file specified in the parameter +**Path**. + +#### Syntax + + +```plaintext +Out-SamplerXml [-XmlDocument] [-Path] [[-Encoding] ] + [] +``` + + +#### Outputs + +None. + +#### Example + + +```powershell +Out-SamplerXml -Path 'C:\temp\my.xml' -XmlDocument '' -Encoding 'UTF8' +``` + + +This example will create a new XML file based on the XML document passed +in the parameter **XmlDocument**. + +### `Set-SamplerTaskVariable` + +This is an alias that points to a script file that is meant to be dot-sourced +from (in) a build task. The script will set common task variables for a build +task. This function should normally never be called outside of a build task, but +an exception can be tests; tests can call the alias to set the values prior to +running tests. + +> **Note:** Running the command `Get-Help -Name 'Set-SamplerTaskVariable'` will +> only return help for the alias. To see the comment-based help for the script, +> run: +> +> ```powershell +> Import-Module -Name Sampler +> +> Get-Help -Name (Get-Alias -Name 'Set-SamplerTaskVariable').Definition -Detailed +> ``` + +#### Syntax + + +```plaintext +Set-SamplerTaskVariable [-AsNewBuild] [] +``` + + +#### Outputs + +None. Sets variables in the current PowerShell session. See comment-based help +for more information about the variables that are set. + +#### Example + + +```powershell +. Set-SamplerTaskVariable +``` + + +Call the scriptblock and tells the script to evaluate the module version +by not checking after the module manifest in the built module. + + +```powershell +. Set-SamplerTaskVariable -AsNewBuild +``` + + +Call the scriptblock set script variables. The parameter **AsNewBuild** tells +the script to skip variables that can only be set when the module has been +built. + +### `Split-ModuleVersion` + +This command parses a SemVer2 version string, and also a version string returned +by a certain property of GitVersion (containing additional metadata). + +#### Syntax + + +```plaintext +Split-ModuleVersion [[-ModuleVersion] ] [] +``` + + +#### Outputs + +`[System.Management.Automation.PSCustomObject]` + +Property Name | Type | Description +--- | --- | --- +Version | `[System.String]` | The module version (without prerelease string) +PreReleaseString | `[System.String]` | The prerelease string part +ModuleVersion | `[System.String]` | The full semantic version + +#### Example + + +```powershell +Split-ModuleVersion -ModuleVersion '1.15.0-pr0224-0022+Sha.47ae45eb2cfed02b249f239a7c55e5c71b26ab76.Date.2020-01-07' +``` + + +This example will return a hashtable with the different parts of the module +version for a version string that was returned by GitVersion. + +### `Update-JaCoCoStatistic` + +This command updates statistics of a JaCoCo report. This is meant to be +run after the command [`Merge-JaCoCoReport`](#merge-jacocoreport) has been +used. + +#### Syntax + + +```plaintext +Update-JaCoCoStatistic [-Document] [] +``` + + +#### Outputs + +`[System.Xml.XmlDocument]` + +#### Example + + +```powershell +Update-JaCoCoStatistic -Document (Merge-JaCoCoReport OriginalDocument $report1 -MergeDocument $report2) +``` + + +This example will return a XML document containing the JaCoCo report with +the updated statistics. + +## Build Task Variables + +A task variable is used in a build task and it can be added as a script +parameter to build.ps1 or set as as an environment variable. It can often +be used if defined in parent scope or read from the $BuildInfo properties +defined in the configuration file. + +### `BuildModuleOutput` + +This is the path where the module will be built. The path will, for example, +be used for the parameter `OutputDirectory` when calling the cmdlet +`Build-Module` of the PowerShell module _Invoke-Build_. Defaults to +the path for `OutputDirectory`, and concatenated with `BuiltModuleSubdirectory` +if it is set. + +### `BuiltModuleSubdirectory` + +An optional path that will suffix the `OutputDirectory` to build the +default path in variable `BuildModuleOutput`. + +### `ModuleVersion` + +The module version of the built module. Defaults to the property `NuGetVersionV2` +returned by the executable `gitversion`, or if the executable `gitversion` +is not available the the variable defaults to an empty string, and the +build module task will use the version found in the Module Manifest. + +It is also possible to set the session environment variable `$env:ModuleVersion` +in the PowerShell session or set the variable `$ModuleVersion` in the +PowerShell session (the parent scope to `Invoke-Build`) before running the +task `build` + +This `ModuleVersion` task variable can be overridden by using the key `SemVer` +in the file `build.yml`, e.g. `SemVer: '99.0.0-preview1'`. This can be used +if the preferred method of using GitVersion is not available. + +The order that the module version is determined is as follows: + +1. the parameter `ModuleVersion` is set from the command line (passing parameter + to build task) +1. if no parameter was passed it defaults to using the property from the + environment variable `$env:ModuleVersion` or parent scope variable + `$ModuleVersion` +1. if the `ModuleVersion` is still not found it will try to use `GitVersion` + if it is available +1. if `GitVersion` is not available the module version is set from the module + manifest in the source path using the properties `ModuleVersion` and + `PrivateData.PSData.Prerelease` +1. if module version is set using key `SemVer` in `build.yml` it will + override 1), 2), 3), and 4) +1. ~~if `SemVar` is set through parameter from the command line then it will~~ + ~~override 1), 2), 3), 4), and 5)~~. This is not yet supported. + +### `OutputDirectory` + +The base directory of all output from the build tasks. This is the path +where artifacts will be built or saved such as the built module, required +modules downloaded at build time, test results, etc. This folder should +be ignored by git as its content is ephemeral. It defaults to the folder +'output', a path relative to the root of the repository (same as `Invoke-Build`'s +[`$BuildRoot`](https://github.com/nightroman/Invoke-Build/wiki/Special-Variables#buildroot)). +You can override this setting with an absolute path should you need to. + +### `ProjectPath` + +The root path to the project. Defaults to [`$BuildRoot`](https://github.com/nightroman/Invoke-Build/wiki/Special-Variables#buildroot). + +### `ProjectName` + +The project name. Defaults to the BaseName of the module manifest it finds +in either the folder 'source', 'src, or a folder with the same name as the +module. + +### `ReleaseNotesPath` + +THe path to the release notes markdown file. Defaults to the path for +`OutputDirectory` concatenated with `ReleaseNotes.md`. + +### `SourcePath` + +The path to the source folder. Defaults to the same path where the module +manifest is found in either the folder 'source', 'src', or a folder with +the same name as the module. + +## Tasks + +### `Create_Changelog_Branch` + +This build task creates pushes a branch with the changelog updated with +the current release version. + +This is an example of how to use the task in the _azure-pipelines.yml_ file: + +```yaml +- task: PowerShell@2 + name: sendChangelogPR + displayName: 'Send Changelog PR' + inputs: + filePath: './build.ps1' + arguments: '-tasks Create_Changelog_Branch' + pwsh: true + env: + MainGitBranch: 'main' + BasicAuthPAT: $(BASICAUTHPAT) +``` + +This can be use in conjunction with the `Create_Release_Git_Tag` task +that creates the release tag. + +```yaml + publish: + - Create_Release_Git_Tag + - Create_Changelog_Branch +``` + +#### Task parameters + +Some task parameters are vital for the resource to work. See comment based +help for the description for each available parameter. Below is the most +important. + +#### Task configuration + +The build configuration (_build.yaml_) can be used to control the behavior +of the build task. + +```yaml +#################################################### +# Changelog Configuration # +#################################################### +ChangelogConfig: + FilesToAdd: + - 'CHANGELOG.md' + UpdateChangelogOnPrerelease: false + +#################################################### +# Git Configuration # +#################################################### +GitConfig: + UserName: bot + UserEmail: bot@company.local +``` + +#### Section ChangelogConfig + +##### Property FilesToAdd + +This specifies one or more files to add to the commit when creating the +PR branch. If left out it will default to the one file _CHANGELOG.md_. + +##### Property UpdateChangelogOnPrerelease + +- `true`: Always create a changelog PR, even on preview releases. +- `false`: Only create a changelog PR for full releases. Default. + +#### Section GitConfig + +This configures git. user name and e-mail address of the user before task pushes the +tag. + +##### Property UserName + +User name of the user that should push the tag. + +##### Property UserEmail + +E-mail address of the user that should push the tag. + +### `Create_Release_Git_Tag` + +This build task creates and pushes a preview release tag to the default branch. + +>Note: This task is primarily meant to be used for SCM's that does not have +>releases that connects to tags like GitHub does with GitHub Releases, but +>this task can also be used as an alternative when using GitHub as SCM. + +This is an example of how to use the task in the _build.yaml_ file: + +```yaml + publish: + - Create_Release_Git_Tag +``` + +#### Task parameters + +Some task parameters are vital for the resource to work. See comment based +help for the description for each available parameter. Below is the most +important. + +#### Task configuration + +The build configuration (_build.yaml_) can be used to control the behavior +of the build task. + +```yaml +#################################################### +# Git Configuration # +#################################################### +GitConfig: + UserName: bot + UserEmail: bot@company.local +``` + +#### Section GitConfig + +This configures git. user name and e-mail address of the user before task pushes the +tag. + +##### Property UserName + +User name of the user that should push the tag. + +##### Property UserEmail + +E-mail address of the user that should push the tag. + +### `Set_PSModulePath` + +This task sets the `PSModulePath` according to the configuration in the `build.yml` +file. + +This task can be important when compiling DSC resource modules or +DSC composite resource modules. When a DSC resource module is available in +'Program Files' and the Required Modules folder, DSC sees this as a conflict. + +> Note: The paths `$BuiltModuleSubdirectory` and `$RequiredModulesDirectory` are +> always prepended to the `PSModulePath`. + +This sequence sets the `PSModulePath` before starting the tests. + +```yaml + test: + - Set_PSModulePath + - Pester_Tests_Stop_On_Fail + - Pester_If_Code_Coverage_Under_Threshold +``` + +#### Task parameters + +Some task parameters are vital for the resource to work. See comment based +help for the description for each available parameter. Below is the most +important. + +#### Task configuration + +The build configuration (_build.yaml_) can be used to control the behavior +of the build task. + +```yaml +#################################################### +# Setting Sampler PSModulePath # +#################################################### +SetPSModulePath: + PSModulePath: C:\Users\Install\OneDrive\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules;c:\Users\Install\.vscode\extensions\ms-vscode.powershell-2022.5.1\modules; + RemovePersonal: false + RemoveProgramFiles: false + RemoveWindows: false + SetSystemDefault: false +``` + +#### Section SetPSModulePath + +##### Property SetPSModulePath + +Sets the `PSModulePath` to the specified value. + +##### Property RemovePersonal + +Removed the personal path from `PSModulePath`, like `C:\Users\Install\Documents\WindowsPowerShell\Modules`. + +#### Section RemoveProgramFiles + +Removed the 'Program Files' path from `PSModulePath`, like `C:\Program Files\WindowsPowerShell\Modules`. + +##### Property RemoveWindows + +Removed the Windows path from `PSModulePath`, like `C:\Windows\system32\WindowsPowerShell\v1.0\Modules`. + +> **Note: It is not recommended to remove the Windows path from `PSModulePath`.** + +##### Property SetSystemDefault + +Sets the module path to what is defined for the machine. The machines `PSModulePath` is retrieved with this call: + +```powershell +[System.Environment]::GetEnvironmentVariable('PSModulePath', 'Machine') +``` diff --git a/Sampler/WikiSource/Home.md b/Sampler/WikiSource/Home.md index 06049831..c47cf790 100644 --- a/Sampler/WikiSource/Home.md +++ b/Sampler/WikiSource/Home.md @@ -10,18 +10,13 @@ for this repository. ## Getting started -To get started either: - -- Install from the PowerShell Gallery using PowerShellGet by running the - following command: - -```powershell -Install-Module -Name Sampler -Repository PSGallery -``` +See the section [[Getting started]] ## Prerequisites -- PowerShell 5.1 or higher +- PowerShell 5.or higher + +The build command will download all other modules required (if you choose too) into the `RequiredModules` folder of your module project for you. ## Change log diff --git a/Sampler/assets/Sampler_512.png b/Sampler/assets/Sampler_512.png deleted file mode 100644 index 8a8985d2..00000000 Binary files a/Sampler/assets/Sampler_512.png and /dev/null differ diff --git a/Sampler/assets/sampler.png b/Sampler/assets/sampler.png new file mode 100644 index 00000000..20b07a57 Binary files /dev/null and b/Sampler/assets/sampler.png differ diff --git a/Sampler/assets/sampler.svg b/Sampler/assets/sampler.svg index 31adfcf4..74188125 100644 --- a/Sampler/assets/sampler.svg +++ b/Sampler/assets/sampler.svg @@ -1,35 +1,637 @@ - - - - background - - - - Layer 1 - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sampler/scripts/Set-SamplerTaskVariable.ps1 b/Sampler/scripts/Set-SamplerTaskVariable.ps1 index 6e32b92a..ecdb9a63 100644 --- a/Sampler/scripts/Set-SamplerTaskVariable.ps1 +++ b/Sampler/scripts/Set-SamplerTaskVariable.ps1 @@ -56,6 +56,7 @@ by not checking after the module manifest in the built module. #> +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Because the variables like BuildModuleOutput are not usedin the script but in the tasks that dot-source this script.')] param ( [Parameter()] diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 28f2dd70..4b731139 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,17 +24,15 @@ stages: - job: Package_Module displayName: 'Package Module' pool: - vmImage: 'windows-latest' + vmImage: 'ubuntu-latest' steps: - - pwsh: | - dotnet tool install --global GitVersion.Tool --version 5.* - $gitVersionObject = dotnet-gitversion | ConvertFrom-Json - $gitVersionObject.PSObject.Properties.ForEach{ - Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." - Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" - } - Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" - displayName: Calculate ModuleVersion (GitVersion) + + - task: DotNetCoreCLI@2 + inputs: + command: custom + custom: tool + arguments: install --global GitVersion.Tool + displayName: Install GitVersionTool - task: PowerShell@2 name: package @@ -43,8 +41,7 @@ stages: filePath: './build.ps1' arguments: '-ResolveDependency -tasks pack' pwsh: true - env: - ModuleVersion: $(NuGetVersionV2) + - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifact' inputs: @@ -287,10 +284,9 @@ stages: filePath: './build.ps1' arguments: '-tasks merge' pwsh: true - - task: PublishCodeCoverageResults@1 + - task: PublishCodeCoverageResults@2 displayName: 'Publish Azure Code Coverage' inputs: - codeCoverageTool: 'JaCoCo' summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' pathToSources: '$(Build.SourcesDirectory)/$(dscBuildVariable.RepositoryName)/' - script: | diff --git a/build.ps1 b/build.ps1 index ce6b76f9..74335450 100644 --- a/build.ps1 +++ b/build.ps1 @@ -70,6 +70,7 @@ only works then the method of downloading dependencies is PSResourceGet. This can also be configured in Resolve-Dependency.psd1. #> +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because how $PSDependTarget is assigned to splatting variable $resolveDependencyParams.')] [CmdletBinding()] param ( @@ -365,7 +366,6 @@ process } Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta - } finally { diff --git a/build.yaml b/build.yaml index 0e226a58..1924bf86 100644 --- a/build.yaml +++ b/build.yaml @@ -96,6 +96,7 @@ DscTest: - "Common Tests - Validate Localization" - "Common Tests - Validate Example Files To Be Published" - "Common Tests - Validate Example Files" + - "Common Tests - Required Script Analyzer Rules" Output: Verbosity: Detailed CIFormat: Auto @@ -132,11 +133,11 @@ BuildWorkflow: # The meta task Generate_Wiki_Content is not used so that Linux and macOS is supported # - Generate_Conceptual_Help # Used for DSC resources - Create_Wiki_Output_Folder - - Copy_Source_Wiki_Folder # - Generate_Markdown_For_DSC_Resources # Used for DSC resources - Generate_Markdown_For_Public_Commands - Generate_External_Help_File_For_Public_Commands - Clean_Markdown_Of_Public_Commands + - Copy_Source_Wiki_Folder - Generate_Wiki_Sidebar - Clean_Markdown_Metadata - Package_Wiki_Content @@ -144,7 +145,8 @@ BuildWorkflow: pack: - build - docs - - package_module_nupkg + - package_psresource_nupkg + # - package_module_nupkg # This is the old version of the pack task hqrmtest: - Invoke_HQRM_Tests_Stop_On_Fail diff --git a/tests/QA/module.tests.ps1 b/tests/QA/module.tests.ps1 index 8f88c223..0232efba 100644 --- a/tests/QA/module.tests.ps1 +++ b/tests/QA/module.tests.ps1 @@ -60,9 +60,10 @@ Describe 'Changelog Management' -Tag 'Changelog' { [bool](&(Get-Process -Id $PID).Path -NoProfile -Command 'git rev-parse --is-inside-work-tree 2>$null')) ) { # Get the list of changed files compared with branch main + $filesChanged = @() $headCommit = &git rev-parse HEAD $defaultBranchCommit = &git rev-parse origin/main - $filesChanged = &git @('diff', "$defaultBranchCommit...$headCommit", '--name-only') + $filesChanged += &git @('diff', "$defaultBranchCommit...$headCommit", '--name-only') $filesStagedAndUnstaged = &git @('diff', 'HEAD', '--name-only') $filesChanged += $filesStagedAndUnstaged diff --git a/tests/Unit/Public/Get-SamplerBuildVersion.tests.ps1 b/tests/Unit/Public/Get-SamplerBuildVersion.tests.ps1 index 73a697ac..8a4cdfb3 100644 --- a/tests/Unit/Public/Get-SamplerBuildVersion.tests.ps1 +++ b/tests/Unit/Public/Get-SamplerBuildVersion.tests.ps1 @@ -116,7 +116,7 @@ Describe 'Get-SamplerBuildVersion' { Context 'When passing a module version' { BeforeAll { Mock -CommandName gitversion -MockWith { - return '{"NuGetVersionV2": "2.1.3"}' + return '{"MajorMinorPatch":"2.1.3"}' } } @@ -130,7 +130,7 @@ Describe 'Get-SamplerBuildVersion' { Context 'When passing a preview module version' { BeforeAll { Mock -CommandName gitversion -MockWith { - return '{"NuGetVersionV2": "2.1.3-preview0023"}' + return '{"MajorMinorPatch":"2.1.3","PreReleaseLabel":"preview","PreReleaseLabelWithDash":"-preview","PreReleaseNumber":23,"BranchName":"main","CommitsSinceVersionSource":50}' } } @@ -216,7 +216,7 @@ Describe 'Get-SamplerBuildVersion' { Context 'When passing a module version' { BeforeAll { Mock -CommandName gitversion -MockWith { - return '{"NuGetVersionV2": "2.1.3"}' + return '{"MajorMinorPatch":"2.1.3"}' } } @@ -227,10 +227,10 @@ Describe 'Get-SamplerBuildVersion' { } } - Context 'When passing a preview module version' { + Context 'When passing a preview module version in main branch' { BeforeAll { Mock -CommandName gitversion -MockWith { - return '{"NuGetVersionV2": "2.1.3-preview0023"}' + return '{"MajorMinorPatch":"2.1.3","PreReleaseLabel":"preview","PreReleaseLabelWithDash":"-preview","PreReleaseNumber":23,"BranchName":"main","CommitsSinceVersionSource":50}' } } @@ -240,6 +240,20 @@ Describe 'Get-SamplerBuildVersion' { $result | Should -Be '2.1.3-preview0023' } } + + Context 'When passing a preview module version in fix branch' { + BeforeAll { + Mock -CommandName gitversion -MockWith { + return '{"MajorMinorPatch":"2.1.3","PreReleaseLabel":"preview","PreReleaseLabelWithDash":"-preview","PreReleaseNumber":23,"BranchName":"fix/Something","CommitsSinceVersionSource":50}' + } + } + + It 'Should return the correct module version' { + $result = Sampler\Get-SamplerBuildVersion -ModuleManifestPath $TestDrive + + $result | Should -Be '2.1.3-preview.50' + } + } } } @@ -266,4 +280,46 @@ Describe 'Get-SamplerBuildVersion' { } } } + + Context 'When the environment variable ModuleVersion is set' { + BeforeAll { + InModuleScope -ScriptBlock { + # Stub for gitversion.exe so we can mock the result. + function script:gitversion + { + throw '{0}: StubNotImplemented' -f $MyInvocation.MyCommand + } + } + } + + AfterAll { + InModuleScope -ScriptBlock { + Remove-Item -Path 'function:gitversion' -Force + } + } + + Context "When having a preview module version in main branch and the version is stored in the environment variable 'ModuleVersion'" { + BeforeAll { + Mock -CommandName gitversion -MockWith { + return '{"MajorMinorPatch":"2.1.3","PreReleaseLabel":"preview","PreReleaseLabelWithDash":"-preview","PreReleaseNumber":23,"BranchName":"main","CommitsSinceVersionSource":50}' + } + + $env:ModuleVersion = '2.1.3-preview0025' + } + + It 'Should return the correct module version' { + $result = Sampler\Get-SamplerBuildVersion -ModuleManifestPath $TestDrive + + $result | Should -Be '2.1.3-preview0025' + } + + It 'Should not have called gitversion' { + Should -Invoke -CommandName 'gitversion' -Exactly -Times 0 -Scope It + } + + AfterAll { + Remove-Item -Path env:ModuleVersion -ErrorAction SilentlyContinue + } + } + } } diff --git a/tests/Unit/scripts/Set-SamplerTaskVariable.Tests.ps1 b/tests/Unit/scripts/Set-SamplerTaskVariable.Tests.ps1 index df8e74fb..0e67707a 100644 --- a/tests/Unit/scripts/Set-SamplerTaskVariable.Tests.ps1 +++ b/tests/Unit/scripts/Set-SamplerTaskVariable.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'Set-SamplerTaskVariable' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { <# Need to add scope global to get the alias that is exported by the module, not the alias that is exported by build.ps1 into local session (to be diff --git a/tests/Unit/tasks/Build-Module.ModuleBuilder.build.Tests.ps1 b/tests/Unit/tasks/Build-Module.ModuleBuilder.build.Tests.ps1 index 07398d3b..2d6d08f6 100644 --- a/tests/Unit/tasks/Build-Module.ModuleBuilder.build.Tests.ps1 +++ b/tests/Unit/tasks/Build-Module.ModuleBuilder.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'Build-Module.ModuleBuilder' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'Build-Module.ModuleBuilder.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'Build-Module.ModuleBuilder.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/Changelog.changelogmanagement.build.Tests.ps1 b/tests/Unit/tasks/Changelog.changelogmanagement.build.Tests.ps1 new file mode 100644 index 00000000..342d89a9 --- /dev/null +++ b/tests/Unit/tasks/Changelog.changelogmanagement.build.Tests.ps1 @@ -0,0 +1,243 @@ +BeforeAll { + $script:moduleName = 'Sampler' + + # If the module is not found, run the build task 'noop'. + if (-not (Get-Module -Name $script:moduleName -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + Remove-Module -Name $script:moduleName +} + +Describe 'Changelog.changelogmanagement' { + It 'Should have exported the alias correctly' { + $taskAlias = Get-Alias -Name 'Changelog.changelogmanagement.build.Sampler.ib.tasks' + + $taskAlias.Name | Should -Be 'Changelog.changelogmanagement.build.Sampler.ib.tasks' + $taskAlias.ReferencedCommand | Should -Be 'Changelog.changelogmanagement.build.ps1' + $taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]Changelog\.changelogmanagement\.build\.ps1' + } +} + +Describe 'Create_changelog_release_output' { + BeforeAll { + # Dot-source mocks + . $PSScriptRoot/../TestHelpers/MockSetSamplerTaskVariable + + $taskAlias = Get-Alias -Name 'Changelog.changelogmanagement.build.Sampler.ib.tasks' + + $mockTaskParameters = @{ + OutputDirectory = Join-Path -Path $TestDrive -ChildPath 'output' + ProjectName = 'MyModule' + } + } + + Context 'When creating the changelog output for a PowerShell module' { + BeforeAll { + Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-ChangelogData -MockWith { + return @{ + Released = @{ + Version = '2.0.0' + RawData = 'Mock changelog release output' + } + } + } -RemoveParameterValidation 'Path' + + Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'ReleaseNotes\.md' + } -MockWith { + return 'Mock changelog release output' + } + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + <# + The variable $BuiltModuleManifest will be set in the task + (mocked by MockSetSamplerTaskVariable) with a path to the + $TestDrive. + Here we make sure the path exist so that WriteAllLines() works + that is called in the task. + #> + New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force + + return '# ReleaseNotes =' + } + + Mock -CommandName Test-Path -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + return $true + } + + Mock -CommandName Update-Manifest + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Update-Manifest -ParameterFilter { + $Value -eq 'Mock changelog release output' + } -Exactly -Times 1 -Scope It + } + + Context 'When the release notes are longer than 10000 characters' { + BeforeAll { + Mock -CommandName Get-ChangelogData -MockWith { + return @{ + Released = @{ + Version = '2.0.0' + # The string 'This will be removed' will be stripped. + RawData = '0123456789' * 1000 + 'This will be removed' + } + } + } -RemoveParameterValidation 'Path' + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Update-Manifest -ParameterFilter { + $Value -eq '0123456789' * 1000 + } -Exactly -Times 1 -Scope It + } + } + } + + Context 'When there are no ReleaseNotes.md but a CHANGELOG.md' { + BeforeAll { + Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-ChangelogData -MockWith { + return @{ + Released = @{ + Version = '2.0.0' + RawData = 'Mock changelog release output' + } + } + } -RemoveParameterValidation 'Path' + + Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'ReleaseNotes\.md' + } -MockWith { + return $null + } + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'CHANGELOG\.md' + } -MockWith { + return 'Mock changelog release output' + } + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + <# + The variable $BuiltModuleManifest will be set in the task + (mocked by MockSetSamplerTaskVariable) with a path to the + $TestDrive. + Here we make sure the path exist so that WriteAllLines() works + that is called in the task. + #> + New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null + + return '# ReleaseNotes =' + } + + Mock -CommandName Test-Path -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + return $true + } + + Mock -CommandName Update-Manifest + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Update-Manifest -ParameterFilter { + $Value -eq 'Mock changelog release output' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When there are no release notes' { + BeforeAll { + Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-ChangelogData -MockWith { + return @{ + Released = @{ + Version = '2.0.0' + RawData = 'Mock changelog release output' + } + } + } -RemoveParameterValidation 'Path' + + Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'ReleaseNotes\.md' + } -MockWith { + return $null + } + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'CHANGELOG\.md' + } -MockWith { + return $null + } + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + <# + The variable $BuiltModuleManifest will be set in the task + (mocked by MockSetSamplerTaskVariable) with a path to the + $TestDrive. + Here we make sure the path exist so that WriteAllLines() works + that is called in the task. + #> + New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null + + return '# ReleaseNotes =' + } + + Mock -CommandName Test-Path -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + return $true + } + + Mock -CommandName Update-Manifest + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Update-Manifest -Exactly -Times 0 -Scope It + } + } +} diff --git a/tests/Unit/tasks/ChocolateyPackage.build.Tests.ps1 b/tests/Unit/tasks/ChocolateyPackage.build.Tests.ps1 index 0d2b6938..faa21115 100644 --- a/tests/Unit/tasks/ChocolateyPackage.build.Tests.ps1 +++ b/tests/Unit/tasks/ChocolateyPackage.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'ChocolateyPackage' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'ChocolateyPackage.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'ChocolateyPackage.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/Clean.ModuleBuilder.build.Tests.ps1 b/tests/Unit/tasks/Clean.ModuleBuilder.build.Tests.ps1 index 285ab824..c11ccf0d 100644 --- a/tests/Unit/tasks/Clean.ModuleBuilder.build.Tests.ps1 +++ b/tests/Unit/tasks/Clean.ModuleBuilder.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'Clean.ModuleBuilder' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'Clean.ModuleBuilder.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'Clean.ModuleBuilder.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/Create_Changelog_Branch.build.Tests.ps1 b/tests/Unit/tasks/Create_Changelog_Branch.build.Tests.ps1 index 0e742411..250d5973 100644 --- a/tests/Unit/tasks/Create_Changelog_Branch.build.Tests.ps1 +++ b/tests/Unit/tasks/Create_Changelog_Branch.build.Tests.ps1 @@ -23,7 +23,7 @@ Describe 'Create_Changelog_Branch' { $taskAlias = Get-Alias -Name "$buildTaskName.build.Sampler.ib.tasks" } - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias.Name | Should -Be 'Create_Changelog_Branch.build.Sampler.ib.tasks' $taskAlias.ReferencedCommand | Should -Be 'Create_Changelog_Branch.build.ps1' $taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]Create_Changelog_Branch\.build\.ps1' diff --git a/tests/Unit/tasks/Create_Release_Git_Tag.build.Tests.ps1 b/tests/Unit/tasks/Create_Release_Git_Tag.build.Tests.ps1 index d1f36fa0..dc00e2b4 100644 --- a/tests/Unit/tasks/Create_Release_Git_Tag.build.Tests.ps1 +++ b/tests/Unit/tasks/Create_Release_Git_Tag.build.Tests.ps1 @@ -23,7 +23,7 @@ Describe 'Create_Release_Git_Tag' { $taskAlias = Get-Alias -Name "$buildTaskName.build.Sampler.ib.tasks" } - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias.Name | Should -Be 'Create_Release_Git_Tag.build.Sampler.ib.tasks' $taskAlias.ReferencedCommand | Should -Be 'Create_Release_Git_Tag.build.ps1' $taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]Create_Release_Git_Tag\.build\.ps1' diff --git a/tests/Unit/tasks/DeployAll.PSDeploy.build.Tests.ps1 b/tests/Unit/tasks/DeployAll.PSDeploy.build.Tests.ps1 index 8a3befe2..5affc5cd 100644 --- a/tests/Unit/tasks/DeployAll.PSDeploy.build.Tests.ps1 +++ b/tests/Unit/tasks/DeployAll.PSDeploy.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'DeployAll.PSDeploy' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'DeployAll.PSDeploy.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'DeployAll.PSDeploy.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/GuestConfig.build.Tests.ps1 b/tests/Unit/tasks/GuestConfig.build.Tests.ps1 index 7cc07134..5adc11cd 100644 --- a/tests/Unit/tasks/GuestConfig.build.Tests.ps1 +++ b/tests/Unit/tasks/GuestConfig.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'GuestConfig' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'GuestConfig.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'GuestConfig.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/Invoke-Pester.pester.build.Tests.ps1 b/tests/Unit/tasks/Invoke-Pester.pester.build.Tests.ps1 index e444ee6a..ffe1a8df 100644 --- a/tests/Unit/tasks/Invoke-Pester.pester.build.Tests.ps1 +++ b/tests/Unit/tasks/Invoke-Pester.pester.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'Invoke-Pester' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'Invoke-Pester.pester.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'Invoke-Pester.pester.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/JaCoCo.coverage.build.Tests.ps1 b/tests/Unit/tasks/JaCoCo.coverage.build.Tests.ps1 index f3181f3d..e394199f 100644 --- a/tests/Unit/tasks/JaCoCo.coverage.build.Tests.ps1 +++ b/tests/Unit/tasks/JaCoCo.coverage.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'JaCoCo.coverage' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'JaCoCo.coverage.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'JaCoCo.coverage.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/SetPsModulePath.build.Tests.ps1 b/tests/Unit/tasks/SetPsModulePath.build.Tests.ps1 index 1aea5a45..d3b74108 100644 --- a/tests/Unit/tasks/SetPsModulePath.build.Tests.ps1 +++ b/tests/Unit/tasks/SetPsModulePath.build.Tests.ps1 @@ -26,7 +26,7 @@ Describe 'SetPsModulePath' -Tag x { } } - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias.Name | Should -Be 'SetPsModulePath.build.Sampler.ib.tasks' $taskAlias.ReferencedCommand | Should -Be 'SetPsModulePath.build.ps1' diff --git a/tests/Unit/tasks/generateHelp.PlatyPS.build.Tests.ps1 b/tests/Unit/tasks/generateHelp.PlatyPS.build.Tests.ps1 index 613b3ceb..efcddc4d 100644 --- a/tests/Unit/tasks/generateHelp.PlatyPS.build.Tests.ps1 +++ b/tests/Unit/tasks/generateHelp.PlatyPS.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'generateHelp.PlatyPS' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'generateHelp.PlatyPS.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'generateHelp.PlatyPS.build.Sampler.ib.tasks' diff --git a/tests/Unit/tasks/publish.nuget.build.Tests.ps1 b/tests/Unit/tasks/publish.nuget.build.Tests.ps1 new file mode 100644 index 00000000..5b3a7c5e --- /dev/null +++ b/tests/Unit/tasks/publish.nuget.build.Tests.ps1 @@ -0,0 +1,98 @@ +BeforeAll { + $script:moduleName = 'Sampler' + + # If the module is not found, run the build task 'noop'. + if (-not (Get-Module -Name $script:moduleName -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + Remove-Module -Name $script:moduleName +} + +Describe 'publish.nuget' { + It 'Should have exported the alias correctly' { + $taskAlias = Get-Alias -Name 'publish.nuget.build.Sampler.ib.tasks' + + $taskAlias.Name | Should -Be 'publish.nuget.build.Sampler.ib.tasks' + $taskAlias.ReferencedCommand | Should -Be 'publish.nuget.build.ps1' + $taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]publish\.nuget\.build\.ps1' + } +} + +Describe 'publish_nupkg_to_gallery' { + BeforeAll { + # Dot-source mocks + . $PSScriptRoot/../TestHelpers/MockSetSamplerTaskVariable + + $taskAlias = Get-Alias -Name 'publish.nuget.build.Sampler.ib.tasks' + + $mockTaskParameters = @{ + OutputDirectory = Join-Path -Path $TestDrive -ChildPath 'output' + ProjectName = 'MyModule' + GalleryApiToken = 'MyToken' + } + } + + Context 'When publish Nuget package' { + BeforeAll { + # Stub for executable nuget + function nuget {} + + Mock -CommandName nuget -MockWith { + return '0' + } + + Mock -CommandName Get-ChildItem -ParameterFilter { + $Path -match '\.nupkg' + } -MockWith { + return $TestDrive + } + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'publish_nupkg_to_gallery' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName nuget -Exactly -Times 1 -Scope It + } + } + + Context 'When publish Nuget package with .NET SDK' { + BeforeAll { + # Stub for executable dotnet + function dotnet {} + + Mock -CommandName dotnet -MockWith { + return '0' + } + + Mock -CommandName Get-Command -ParameterFilter { + $Name -eq 'nuget' -and $ErrorAction -eq 'SilentlyContinue' + } -MockWith { + $null + } + + Mock -CommandName Get-ChildItem -ParameterFilter { + $Path -match '\.nupkg' + } -MockWith { + return $TestDrive + } + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'publish_nupkg_to_gallery' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName dotnet -Exactly -Times 1 -Scope It + } + } +} diff --git a/tests/Unit/tasks/release.module.build.Tests.ps1 b/tests/Unit/tasks/release.module.build.Tests.ps1 index 59243b1e..d4e4342a 100644 --- a/tests/Unit/tasks/release.module.build.Tests.ps1 +++ b/tests/Unit/tasks/release.module.build.Tests.ps1 @@ -17,7 +17,7 @@ AfterAll { } Describe 'release.module' { - It 'Should have exported the alias correct' { + It 'Should have exported the alias correctly' { $taskAlias = Get-Alias -Name 'release.module.build.Sampler.ib.tasks' $taskAlias.Name | Should -Be 'release.module.build.Sampler.ib.tasks' @@ -26,293 +26,6 @@ Describe 'release.module' { } } -Describe 'Create_changelog_release_output' { - BeforeAll { - # Dot-source mocks - . $PSScriptRoot/../TestHelpers/MockSetSamplerTaskVariable - - $taskAlias = Get-Alias -Name 'release.module.build.Sampler.ib.tasks' - - $mockTaskParameters = @{ - OutputDirectory = Join-Path -Path $TestDrive -ChildPath 'output' - ProjectName = 'MyModule' - } - } - - Context 'When creating the changelog output for a PowerShell module' { - BeforeAll { - Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-ChangelogData -MockWith { - return @{ - Released = @{ - Version = '2.0.0' - RawData = 'Mock changelog release output' - } - } - } -RemoveParameterValidation 'Path' - - Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'ReleaseNotes\.md' - } -MockWith { - return 'Mock changelog release output' - } - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - <# - The variable $BuiltModuleManifest will be set in the task - (mocked by MockSetSamplerTaskVariable) with a path to the - $TestDrive. - Here we make sure the path exist so that WriteAllLines() works - that is called in the task. - #> - New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force - - return '# ReleaseNotes =' - } - - Mock -CommandName Test-Path -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - return $true - } - - Mock -CommandName Update-Manifest - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName Update-Manifest -ParameterFilter { - $Value -eq 'Mock changelog release output' - } -Exactly -Times 1 -Scope It - } - - Context 'When the release notes are longer than 10000 characters' { - BeforeAll { - Mock -CommandName Get-ChangelogData -MockWith { - return @{ - Released = @{ - Version = '2.0.0' - # The string 'This will be removed' will be stripped. - RawData = '0123456789' * 1000 + 'This will be removed' - } - } - } -RemoveParameterValidation 'Path' - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName Update-Manifest -ParameterFilter { - $Value -eq '0123456789' * 1000 - } -Exactly -Times 1 -Scope It - } - } - } - - Context 'When there are no ReleaseNotes.md but a CHANGELOG.md' { - BeforeAll { - Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-ChangelogData -MockWith { - return @{ - Released = @{ - Version = '2.0.0' - RawData = 'Mock changelog release output' - } - } - } -RemoveParameterValidation 'Path' - - Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'ReleaseNotes\.md' - } -MockWith { - return $null - } - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'CHANGELOG\.md' - } -MockWith { - return 'Mock changelog release output' - } - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - <# - The variable $BuiltModuleManifest will be set in the task - (mocked by MockSetSamplerTaskVariable) with a path to the - $TestDrive. - Here we make sure the path exist so that WriteAllLines() works - that is called in the task. - #> - New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null - - return '# ReleaseNotes =' - } - - Mock -CommandName Test-Path -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - return $true - } - - Mock -CommandName Update-Manifest - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName Update-Manifest -ParameterFilter { - $Value -eq 'Mock changelog release output' - } -Exactly -Times 1 -Scope It - } - } - - Context 'When there are no release notes' { - BeforeAll { - Mock -CommandName Update-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-ChangelogData -MockWith { - return @{ - Released = @{ - Version = '2.0.0' - RawData = 'Mock changelog release output' - } - } - } -RemoveParameterValidation 'Path' - - Mock -CommandName ConvertFrom-Changelog -RemoveParameterValidation 'Path' - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'ReleaseNotes\.md' - } -MockWith { - return $null - } - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'CHANGELOG\.md' - } -MockWith { - return $null - } - - Mock -CommandName Get-Content -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - <# - The variable $BuiltModuleManifest will be set in the task - (mocked by MockSetSamplerTaskVariable) with a path to the - $TestDrive. - Here we make sure the path exist so that WriteAllLines() works - that is called in the task. - #> - New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null - - return '# ReleaseNotes =' - } - - Mock -CommandName Test-Path -ParameterFilter { - $Path -match 'builtModule' - } -MockWith { - return $true - } - - Mock -CommandName Update-Manifest - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'Create_changelog_release_output' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName Update-Manifest -Exactly -Times 0 -Scope It - } - } -} - -Describe 'publish_nupkg_to_gallery' { - BeforeAll { - # Dot-source mocks - . $PSScriptRoot/../TestHelpers/MockSetSamplerTaskVariable - - $taskAlias = Get-Alias -Name 'release.module.build.Sampler.ib.tasks' - - $mockTaskParameters = @{ - OutputDirectory = Join-Path -Path $TestDrive -ChildPath 'output' - ProjectName = 'MyModule' - GalleryApiToken = 'MyToken' - } - } - - Context 'When publish Nuget package' { - BeforeAll { - # Stub for executable nuget - function nuget {} - - Mock -CommandName nuget -MockWith { - return '0' - } - - Mock -CommandName Get-ChildItem -ParameterFilter { - $Path -match '\.nupkg' - } -MockWith { - return $TestDrive - } - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'publish_nupkg_to_gallery' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName nuget -Exactly -Times 1 -Scope It - } - } - - Context 'When publish Nuget package with .NET SDK' { - BeforeAll { - # Stub for executable dotnet - function dotnet {} - - Mock -CommandName dotnet -MockWith { - return '0' - } - - Mock -CommandName Get-Command -ParameterFilter { - $Name -eq 'nuget' -and $ErrorAction -eq 'SilentlyContinue' - } -MockWith { - $null - } - - Mock -CommandName Get-ChildItem -ParameterFilter { - $Path -match '\.nupkg' - } -MockWith { - return $TestDrive - } - } - - It 'Should run the build task without throwing' { - { - Invoke-Build -Task 'publish_nupkg_to_gallery' -File $taskAlias.Definition @mockTaskParameters - } | Should -Not -Throw - - Should -Invoke -CommandName dotnet -Exactly -Times 1 -Scope It - } - } -} - Describe 'package_module_nupkg' { BeforeAll { # Dot-source mocks @@ -542,6 +255,16 @@ Describe 'publish_module_to_gallery' { return '# ReleaseNotes =' } + if (-not (Get-Command -Name 'Get-PSResourceRepository' -ErrorAction SilentlyContinue)) + { + function Get-PSResourceRepository {} + } + + if (-not (Get-Command -Name 'Publish-PSResource' -ErrorAction SilentlyContinue)) + { + function Publish-PSResource {} + } + Mock -CommandName Get-PSResourceRepository Mock -CommandName Publish-PSResource } diff --git a/tests/Unit/tasks/release.psresource.build.Tests.ps1 b/tests/Unit/tasks/release.psresource.build.Tests.ps1 new file mode 100644 index 00000000..e43e7a99 --- /dev/null +++ b/tests/Unit/tasks/release.psresource.build.Tests.ps1 @@ -0,0 +1,226 @@ +BeforeAll { + $script:moduleName = 'Sampler' + + # If the module is not found, run the build task 'noop'. + if (-not (Get-Module -Name $script:moduleName -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # Re-import the module using force to get any code changes between runs. + Import-Module -Name $script:moduleName -Force -ErrorAction 'Stop' +} + +AfterAll { + Remove-Module -Name $script:moduleName +} + +Describe 'release.psresource' { + It 'Should have exported the alias correctly' { + $taskAlias = Get-Alias -Name 'release.psresource.build.Sampler.ib.tasks' + + $taskAlias.Name | Should -Be 'release.psresource.build.Sampler.ib.tasks' + $taskAlias.ReferencedCommand | Should -Be 'release.psresource.build.ps1' + $taskAlias.Definition | Should -Match 'Sampler[\/|\\]\d+\.\d+\.\d+[\/|\\]tasks[\/|\\]release\.psresource\.build\.ps1' + } +} + +Describe 'package_psresource_nupkg' { + BeforeAll { + # Dot-source mocks + . $PSScriptRoot/../TestHelpers/MockSetSamplerTaskVariable + + $taskAlias = Get-Alias -Name 'release.psresource.build.Sampler.ib.tasks' + + $mockTaskParameters = @{ + OutputDirectory = Join-Path -Path $TestDrive -ChildPath 'output' + ProjectName = 'MyModule' + } + } + + Context 'When packaging a Nuget package' { + BeforeAll { + import-module -Name Microsoft.PowerShell.PSResourceGet + Mock -CommandName Unregister-PSResourceRepository + Mock -CommandName Register-PSResourceRepository + + Mock -CommandName Get-ChildItem -ParameterFilter { + $Path -match '\.nupkg' + } -MockWith { + return $TestDrive + } + + Mock -CommandName Remove-Item + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + <# + The variable $BuiltModuleManifest will be set in the task + (mocked by MockSetSamplerTaskVariable) with a path to the + $TestDrive. + Here we make sure the path exist so that WriteAllLines() works + that is called in the task. + #> + New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null + + return @' +@{ + ModuleVersion = '2.0.0' + GUID = '00000000-0000-0000-0000-000000000000' + Author = 'Test' + CompanyName = 'Test' + Copyright = 'Test' + Description = 'Test module' + FunctionsToExport = '*' + CmdletsToExport = '*' + VariablesToExport = '*' + AliasesToExport = '*' + PrivateData = @{ + PSData = @{ + # ReleaseNotes = 'Test release notes' + } + } +} +'@ + } + + Mock -CommandName Get-SamplerModuleInfo -MockWith { + return @{ + RequiredModules = @() + } + } -RemoveParameterValidation 'ModuleManifestPath' + + Mock -CommandName Publish-PSResource + } + + It 'Should run the build task without throwing' { + { + Invoke-Build -Task 'package_psresource_nupkg' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Publish-PSResource -Exactly -Times 1 -Scope It + } + } + + Context 'When packaging a Nuget package with a required PSResource' { + BeforeAll { + Mock -CommandName Unregister-PSResourceRepository + Mock -CommandName Register-PSResourceRepository + + # Mock Import-Module to return module info based on what is being imported. + # When piping a ModuleSpecification, the input binds to -FullyQualifiedName, not -Name. + # The first call imports the main module (BuiltModuleManifest path), subsequent calls import dependencies. + $script:importModuleCallCount = 0 + Mock -CommandName Import-Module -MockWith { + $script:importModuleCallCount++ + + if ($script:importModuleCallCount -eq 1) + { + # First call: the main module (MyModule) from $BuiltModuleManifest + [PSCustomObject]@{ + Name = 'MyModule' + ModuleBase = $BuiltModuleManifest | Split-Path -Parent + Path = $BuiltModuleManifest + Version = '2.0.0' + } + } + else + { + # Subsequent calls: the dependent module + [PSCustomObject]@{ + Name = 'MyDependentModule' + ModuleBase = $TestDrive | Join-Path -ChildPath 'MyDependentModule' + Path = $TestDrive | Join-Path -ChildPath 'MyDependentModule\MyDependentModule.psd1' + Version = '6.6.6' + } + } + } -ParameterFilter { + $Name -ne 'Microsoft.PowerShell.PSResourceGet' + } + + Mock -CommandName Get-ChildItem -ParameterFilter { + $Path -match '\.nupkg' + } -MockWith { + return $TestDrive + } + + Mock -CommandName Remove-Item + + Mock -CommandName Get-Content -ParameterFilter { + $Path -match 'builtModule' + } -MockWith { + <# + The variable $BuiltModuleManifest will be set in the task + (mocked by MockSetSamplerTaskVariable) with a path to the + $TestDrive. + Here we make sure the path exist so that WriteAllLines() works + that is called in the task. + #> + New-Item -Path ($BuiltModuleManifest | Split-Path -Parent) -ItemType Directory -Force | Out-Null + + return @' +@{ + ModuleVersion = '2.0.0' + GUID = '00000000-0000-0000-0000-000000000000' + Author = 'Test' + CompanyName = 'Test' + Copyright = 'Test' + Description = 'Test module' + # RequiredModules = @('MyDependentModule') + FunctionsToExport = '*' + CmdletsToExport = '*' + VariablesToExport = '*' + AliasesToExport = '*' + PrivateData = @{ + PSData = @{ + # ReleaseNotes = 'Test release notes' + } + } +} +'@ + } + + Mock -CommandName Get-SamplerModuleInfo -MockWith { + return @{ + RequiredModules = @('MyDependentModule') + Name = 'MyModule' + ModuleVersion = '2.0.0' + } + } -RemoveParameterValidation 'ModuleManifestPath' -ParameterFilter { + $ModuleManifestPath -eq $BuiltModuleManifest + } + + Mock -CommandName Get-SamplerModuleInfo -MockWith { + return @{ + ModuleVersion = '6.6.6' + RequiredModules = @() + Name = 'MyDependentModule' + } + } -RemoveParameterValidation 'ModuleManifestPath' -ParameterFilter { + $ModuleManifestPath -ne $BuiltModuleManifest + } + + Mock -CommandName Find-PSResource -ParameterFilter { + $Repository -eq 'output' + } + + Mock -CommandName Get-PSResourceRepository -MockWith { + return @{ Name = 'output' } + } + + Mock -CommandName Publish-PSResource + } + + It 'Should run the build task without throwing' { + $script:importModuleCallCount = 0 + + { + Invoke-Build -Task 'package_psresource_nupkg' -File $taskAlias.Definition @mockTaskParameters + } | Should -Not -Throw + + Should -Invoke -CommandName Publish-PSResource -Exactly -Times 2 + } + } +}