diff --git a/GitVersion.yml b/GitVersion.yml index 6de4ea1..8b280b7 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,5 @@ mode: ContinuousDeployment +next-version: 1.0.0 assembly-versioning-scheme: 'MajorMinorPatchTag' assembly-informational-format: '{Major}.{Minor}.{Patch}{PreReleaseTagWithDash}+Sha.{Sha}.Date.{CommitDate}' commit-message-incrementing: MergeMessageOnly diff --git a/Source/ModuleBuilder.psd1 b/Source/ModuleBuilder.psd1 index 5e36b63..2aa48cc 100644 --- a/Source/ModuleBuilder.psd1 +++ b/Source/ModuleBuilder.psd1 @@ -6,12 +6,12 @@ PrivateData = @{ # PrivateData.PSData is the PowerShell Gallery data PSData = @{ - # Prerelease string of this module - Prerelease = '-beta01' + # Prerelease string should be here, so we can set it + Prerelease = 'beta' - # ReleaseNotes of this module + # Release Notes have to be here, so we can update them ReleaseNotes = ' - 1.0.0-beta01: Pre-release version of Build-Module + First release to the PowerShell gallery ... ' # Tags applied to this module. These help with module discovery in online galleries. @@ -50,3 +50,4 @@ PowerShellVersion = '5.1' CompatiblePSEditions = @('Core','Desktop') } + diff --git a/Source/Private/InitializeBuild.ps1 b/Source/Private/InitializeBuild.ps1 index 74e85eb..22156db 100644 --- a/Source/Private/InitializeBuild.ps1 +++ b/Source/Private/InitializeBuild.ps1 @@ -15,11 +15,15 @@ function InitializeBuild { [CmdletBinding()] param( # The root folder where the module source is (including the Build.psd1 and the module Manifest.psd1) - [string]$SourcePath + [string]$SourcePath, + + # Pass the invocation from the parent in, so InitializeBuild can read parameter values + [Parameter(DontShow)] + $Invocation = $(Get-Variable MyInvocation -Scope 1 -ValueOnly) ) - # Read the caller's parameter values + # NOTE: This reads the parameter values from Build-Module! $ParameterValues = @{} - foreach($parameter in (Get-Variable MyInvocation -Scope 1 -ValueOnly).MyCommand.Parameters.GetEnumerator()) { + foreach($parameter in $Invocation.MyCommand.Parameters.GetEnumerator()) { $key = $parameter.Key if($null -ne ($value = Get-Variable -Name $key -ValueOnly -ErrorAction Ignore )) { if($value -ne ($null -as $parameter.Value.ParameterType)) { diff --git a/Source/Public/Build-Module.ps1 b/Source/Public/Build-Module.ps1 index 970d302..fbe2795 100644 --- a/Source/Public/Build-Module.ps1 +++ b/Source/Public/Build-Module.ps1 @@ -29,9 +29,18 @@ function Build-Module { Build-Module -Prefix "using namespace System.Management.Automation" This example shows how to build a simple module from it's manifest, adding a using statement at the top as a prefix + + .Example + $gitVersion = gitversion | ConvertFrom-Json | Select -Expand InformationalVersion + Build-Module -SemVer $gitVersion + + This example shows how to use a semantic version from gitversion to version your build. + Note, this is how we version ModuleBuilder, so if you want to see it in action, check out our azure-pipelines.yml + https://github.com/PoshCode/ModuleBuilder/blob/master/azure-pipelines.yml #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Build is approved now")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCmdletCorrectly", "")] + [CmdletBinding(DefaultParameterSetName="SemanticVersion")] param( # The path to the module folder, manifest or build.psd1 [Parameter(Position = 0, ValueFromPipelineByPropertyName)] @@ -50,8 +59,26 @@ function Build-Module { [Alias("Destination")] [string]$OutputDirectory, + # Semantic version, like 1.0.3-beta01+sha.22c35ffff166f34addc49a3b80e622b543199cc5 + # If the SemVer has metadata (after a +), then the full Semver will be added to the ReleaseNotes + [Parameter(ParameterSetName="SemanticVersion")] + [string]$SemVer, + + # The module version (must be a valid System.Version such as PowerShell supports for modules) [Alias("ModuleVersion")] - [version]$Version, + [Parameter(ParameterSetName="ModuleVersion", Mandatory)] + [version]$Version = $(if($V = $SemVer.Split("+")[0].Split("-")[0]){$V}), + + # Setting pre-release forces the release to be a pre-release. + # Must be valid pre-release tag like PowerShellGet supports + [Parameter(ParameterSetName="ModuleVersion")] + [string]$Prerelease = $($SemVer.Split("+")[0].Split("-")[1]), + + # Build metadata (like the commit sha or the date). + # If a value is provided here, then the full Semantic version will be inserted to the release notes: + # Like: ModuleName v(Version(-Prerelease?)+BuildMetadata) + [Parameter(ParameterSetName="ModuleVersion")] + [string]$BuildMetadata = $($SemVer.Split("+")[1]), # Folders which should be copied intact to the module output # Can be relative to the module folder @@ -101,6 +128,18 @@ function Build-Module { } process { try { + # BEFORE we InitializeBuild we need to "fix" the version + if($PSCmdlet.ParameterSetName -ne "SemanticVersion") { + Write-Verbose "Calculate the Semantic Version from the $Version - $Prerelease + $BuildMetadata" + $SemVer = $Version + if($Prerelease) { + $SemVer = $Version + '-' + $Prerelease + } + if($BuildMetadata) { + $SemVer = $SemVer + '+' + $BuildMetadata + } + } + # Push into the module source (it may be a subfolder) $ModuleInfo = InitializeBuild $SourcePath # Output file names @@ -159,10 +198,43 @@ function Build-Module { } } - Write-Verbose "Update Manifest to $OutputManifest" + try { + if ($Version) { + Write-Verbose "Update Manifest at $OutputManifest with version: $Version" + Update-Metadata -Path $OutputManifest -PropertyName ModuleVersion -Value $Version + } + } catch { + Write-Warning "Failed to update version to $Version. $_" + } + + if ($Prerelease) { + Write-Verbose "Update Manifest at $OutputManifest with Prerelease: $Prerelease" + Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.Prerelease -Value $Prerelease + } else { + Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.Prerelease -Value "" + } - if ($Version) { - Update-Metadata -Path $OutputManifest -PropertyName ModuleVersion -Value $Version + if ($BuildMetadata) { + Write-Verbose "Update Manifest at $OutputManifest with metadata: $BuildMetadata from $SemVer" + $RelNote = Get-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -ErrorAction SilentlyContinue + if ($null -ne $RelNote) { + $Line = "$($ModuleInfo.Name) v$($SemVer)" + if ([string]::IsNullOrWhiteSpace($RelNote)) { + Write-Verbose "New ReleaseNotes:`n$Line" + Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $Line + } elseif ($RelNote -match "^\s*\n") { + # Leading whitespace includes newlines + Write-Verbose "Existing ReleaseNotes:$RelNote" + $RelNote = $RelNote -replace "^(?s)(\s*)\S.*$|^$","`${1}$($Line)`$_" + Write-Verbose "New ReleaseNotes:$RelNote" + Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $RelNote + } else { + Write-Verbose "Existing ReleaseNotes:`n$RelNote" + $RelNote = $RelNote -replace "^(?s)(\s*)\S.*$|^$","`${1}$($Line)`n`$_" + Write-Verbose "New ReleaseNotes:`n$RelNote" + Update-Metadata -Path $OutputManifest -PropertyName PrivateData.PSData.ReleaseNotes -Value $RelNote + } + } } # This is mostly for testing ... diff --git a/Tests/Public/Build-Module.Tests.ps1 b/Tests/Public/Build-Module.Tests.ps1 index 58fd160..0f1bde5 100644 --- a/Tests/Public/Build-Module.Tests.ps1 +++ b/Tests/Public/Build-Module.Tests.ps1 @@ -19,7 +19,7 @@ Describe "Build-Module" { It "has an optional parameter for setting the Version"{ $parameters.ContainsKey("Version") | Should -Be $true $parameters["Version"].ParameterType | Should -Be ([version]) - $parameters["Version"].Attributes.Where{$_ -is [Parameter]}.Mandatory | Should -Be $false + $parameters["Version"].ParameterSets.Keys | Should -Not -Be "__AllParameterSets" } It "has an optional parameter for setting the Encoding"{ @@ -185,4 +185,113 @@ Describe "Build-Module" { Assert-MockCalled SetModuleContent -ModuleName ModuleBuilder -Times 0 } } + + Context "Setting the version to a SemVer string" { + $SemVer = "1.0.0-beta03+sha.22c35ffff166f34addc49a3b80e622b543199cc5.Date.2018-10-11" + $global:ExpectedVersion = "1.0.0" + Push-Location TestDrive:\ -StackName BuildModuleTest + New-Item -ItemType Directory -Path TestDrive:\MyModule\ -Force + New-Item -ItemType Directory -Path "TestDrive:\$ExpectedVersion\" -Force + + Mock SetModuleContent -ModuleName ModuleBuilder {} + Mock Update-Metadata -ModuleName ModuleBuilder {} + Mock InitializeBuild -ModuleName ModuleBuilder { + # These are actually all the values that we need + @{ + OutputDirectory = "TestDrive:\$Version" + Name = "MyModule" + ModuleBase = "TestDrive:\MyModule\" + CopyDirectories = @() + Encoding = "UTF8" + PublicFilter = "Public\*.ps1" + } + } + + Mock Test-Path {$True} -Parameter {$Path -eq "TestDrive:\$ExpectedVersion"} -ModuleName ModuleBuilder + Mock Remove-Item {} -Parameter {$Path -eq "TestDrive:\$ExpectedVersion"} -ModuleName ModuleBuilder + Mock Set-Location {} -ModuleName ModuleBuilder + Mock Copy-Item {} -ModuleName ModuleBuilder + # Release notes + Mock Get-Metadata { "First Release" } -ModuleName ModuleBuilder + Mock Join-Path { + [IO.Path]::Combine($Path, $ChildPath) + } -ModuleName ModuleBuilder + + Mock Get-ChildItem { + [IO.FileInfo]$(Join-Path $(Convert-Path "TestDrive:\") "MyModule\Public\Get-MyInfo.ps1") + } -ModuleName ModuleBuilder + + Mock New-Item {} -Parameter { + $Path -eq "TestDrive:\$ExpectedVersion" -and + $ItemType -eq "Directory" -and + $Force -eq $true + } -ModuleName ModuleBuilder + + try { + Build-Module -SemVer $SemVer + } catch { + Pop-Location -StackName BuildModuleTest + throw + } + + It "Should build to an output folder with the simple version." { + Assert-MockCalled Remove-Item -ModuleName ModuleBuilder + Assert-MockCalled New-Item -ModuleName ModuleBuilder + } + + It "Should update the module version to the simple version." { + Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter { + $PropertyName -eq "ModuleVersion" -and $Value -eq $ExpectedVersion + } + } + It "Should update the module pre-release version" { + Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.Prerelease" -and $Value -eq "beta03" + } + } + It "When there are simple release notes, it should insert a line with the module name and full semver" { + Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$($SemVer)`nFirst Release" + } + } + + It "When there's no release notes, it should insert the module name and full semver" { + # If there's no release notes, but it was left uncommented + Mock Get-Metadata { "" } -ModuleName ModuleBuilder + + try { + Build-Module -SemVer $SemVer + } catch { + Pop-Location -StackName BuildModuleTest + throw + } + + Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq "MyModule v$SemVer" + } + } + + It "When there's a prefix empty line, it should insert the module name and full semver the same way" { + # If there's no release notes, but it was left uncommented + Mock Get-Metadata { " + Multi-line Release Notes + With a prefix carriage return" } -ModuleName ModuleBuilder + + try { + Build-Module -SemVer $SemVer + } catch { + Pop-Location -StackName BuildModuleTest + throw + } + + Assert-MockCalled Update-Metadata -ModuleName ModuleBuilder -ParameterFilter { + $PropertyName -eq "PrivateData.PSData.ReleaseNotes" -and $Value -eq " + MyModule v$SemVer + Multi-line Release Notes + With a prefix carriage return" + } + } + + Pop-Location -StackName BuildModuleTest + } } \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5d23ecc..c7b4455 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,7 +14,7 @@ steps: - powershell: .\bootstrap.ps1 displayName: 'Restore pre-requisites' -- powershell: .\build.ps1 -OutputDirectory $(Build.ArtifactStagingDirectory)\$(Build.DefinitionName) -Version $(GitVersion.AssemblySemVer) -Verbose +- powershell: .\build.ps1 -OutputDirectory $(Build.ArtifactStagingDirectory)\$(Build.DefinitionName) -SemVer $(GitVersion.InformationalVersion) -Verbose displayName: 'Run build script' - task: richardfennellBM.BM-VSTS-PesterRunner-Task.Pester-Task.Pester@8 diff --git a/build.ps1 b/build.ps1 index 67f2596..b1522db 100644 --- a/build.ps1 +++ b/build.ps1 @@ -10,7 +10,7 @@ param( # The version of the output module [Alias("ModuleVersion")] - [version]$Version + [string]$SemVer ) # Sanitize parameters to pass to Build-Module @@ -19,7 +19,6 @@ $null = $PSBoundParameters.Remove('Test') $ErrorActionPreference = "Stop" Push-Location $PSScriptRoot -StackName BuildBuildModule try { - # Build ModuleBuilder with ModuleBuilder: Write-Verbose "Compiling ModuleBuilderBootstrap module" $OFS = "`n`n"