SemanticRelease.psm1
|
Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Source: src/public/Get-SemanticReleaseCommitMessage.ps1 function Get-SemanticReleaseCommitMessage { [CmdletBinding()] param( [string]$ProjectRoot = (Get-Location).Path ) return Read-SemanticReleaseCommitMessage -ProjectRoot $ProjectRoot } # Source: src/public/Get-SemanticReleaseCommitRange.ps1 function Get-SemanticReleaseCommitRange { [CmdletBinding()] param( [string]$ProjectRoot = (Get-Location).Path ) return Get-SemanticReleaseCommitRangeBasis -ProjectRoot $ProjectRoot } # Source: src/public/Get-SemanticReleaseLabel.ps1 function Get-SemanticReleaseLabel { [CmdletBinding()] param( [Parameter(Mandatory)][string[]]$Message ) return Resolve-SemanticReleaseVersionLabel -Message $Message } # Source: src/public/Get-SemanticReleaseNextVersion.ps1 function Get-SemanticReleaseNextVersion { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion, [ValidateSet('Major', 'Minor', 'Patch')] [string]$Label = 'Patch', [switch]$PreviewRelease, [switch]$StableRelease ) return Get-SemanticReleaseVersionPlan -CurrentVersion $CurrentVersion -Label $Label -PreviewRelease:$PreviewRelease -StableRelease:$StableRelease } # Source: src/public/Set-SemanticReleaseJsonVersion.ps1 function Set-SemanticReleaseJsonVersion { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory)][string]$Path, [string]$Key = 'Version', [ValidateSet('Major', 'Minor', 'Patch')] [string]$Label = 'Patch', [ValidateSet('Default', 'Preview', 'Stable')] [string]$ReleaseType = 'Default' ) $jsonData = Read-SemanticReleaseJsonData -JsonPath $Path if (-not $jsonData.ContainsKey($Key)) { throw "JSON key '$Key' was not found in $Path" } $previousVersion = [string]$jsonData[$Key] $isPreviewRelease = $ReleaseType -eq 'Preview' $isStableRelease = $ReleaseType -eq 'Stable' $versionPlan = Get-SemanticReleaseVersionPlan -CurrentVersion ([semver]$previousVersion) -Label $Label -PreviewRelease:$isPreviewRelease -StableRelease:$isStableRelease $newVersion = $versionPlan.NewVersion.ToString() $target = [System.IO.Path]::GetFileName($Path) $action = "Set $Key to $newVersion" $changeInfo = @{ Path = $Path Key = $Key PreviousVersion = $previousVersion NewVersion = $newVersion } if (-not $PSCmdlet.ShouldProcess($target, $action)) { return Get-SemanticReleaseJsonVersionWriteResult -ChangeInfo $changeInfo } $jsonData[$Key] = $newVersion Write-SemanticReleaseJsonData -JsonPath $Path -Data $jsonData return Get-SemanticReleaseJsonVersionWriteResult -ChangeInfo $changeInfo -Applied } # Source: src/private/release/Get-SemanticReleaseCommitRangeBasis.ps1 function Get-SemanticReleaseCommitRangeBasis { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProjectRoot ) if (-not (Test-SemanticReleaseGitAvailable)) { return Get-SemanticReleaseCommitRangeBasisResult -ProjectRoot $ProjectRoot -IsRepository:$false } $repositoryResult = Invoke-SemanticReleaseGitCommand -ProjectRoot $ProjectRoot -Arguments @('rev-parse', '--is-inside-work-tree') $repositoryState = Get-SemanticReleaseGitCommandOutputText -Result $repositoryResult if ($repositoryResult.ExitCode -ne 0 -or $repositoryState -ne 'true') { return Get-SemanticReleaseCommitRangeBasisResult -ProjectRoot $ProjectRoot -IsRepository:$false } $lastTagResult = Invoke-SemanticReleaseGitCommand -ProjectRoot $ProjectRoot -Arguments @('describe', '--tags', '--abbrev=0') $lastTag = Get-SemanticReleaseGitCommandOutputText -Result $lastTagResult if ($lastTagResult.ExitCode -eq 0 -and -not [string]::IsNullOrWhiteSpace($lastTag)) { return Get-SemanticReleaseCommitRangeBasisResult -ProjectRoot $ProjectRoot -IsRepository -LastTag $lastTag } return Get-SemanticReleaseCommitRangeBasisResult -ProjectRoot $ProjectRoot -IsRepository } function Get-SemanticReleaseCommitRangeBasisResult { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProjectRoot, [switch]$IsRepository, [string]$LastTag ) $hasVersionTag = -not [string]::IsNullOrWhiteSpace($LastTag) $revisionRange = $null if ($hasVersionTag) { $revisionRange = "$LastTag..HEAD" } return [pscustomobject]@{ ProjectRoot = $ProjectRoot IsRepository = [bool]$IsRepository HasVersionTag = $hasVersionTag LastTag = $LastTag RevisionRange = $revisionRange } } # Source: src/private/release/Get-SemanticReleaseJsonVersionWriteResult.ps1 function Get-SemanticReleaseJsonVersionWriteResult { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$ChangeInfo, [switch]$Applied ) return [pscustomobject]@{ Path = [string]$ChangeInfo.Path Target = [System.IO.Path]::GetFileName([string]$ChangeInfo.Path) Key = [string]$ChangeInfo.Key PreviousVersion = [string]$ChangeInfo.PreviousVersion NewVersion = [string]$ChangeInfo.NewVersion Applied = [bool]$Applied } } # Source: src/private/release/Get-SemanticReleaseVersionPlan.ps1 function Get-SemanticReleaseVersionPlan { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion, [ValidateSet('Major', 'Minor', 'Patch')] [string]$Label = 'Patch', [switch]$PreviewRelease, [switch]$StableRelease ) $versionPart = Get-SemanticReleaseVersionPartForPlan -CurrentVersion $CurrentVersion -Label $Label -PreviewRelease:$PreviewRelease $releaseType = Resolve-SemanticReleasePreReleaseLabel -CurrentVersion $CurrentVersion -PreviewRelease:$PreviewRelease -StableRelease:$StableRelease $newVersion = [semver]::new($versionPart.Major, $versionPart.Minor, $versionPart.Patch, $releaseType, $null) return [pscustomobject]@{ CurrentVersion = $CurrentVersion Label = $Label PreviewRelease = [bool]$PreviewRelease StableRelease = [bool]$StableRelease NewVersion = $newVersion } } function Get-SemanticReleaseVersionPartForPlan { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion, [Parameter(Mandatory)][string]$Label, [switch]$PreviewRelease ) if ($PreviewRelease) { if (-not [string]::IsNullOrWhiteSpace($CurrentVersion.PreReleaseLabel)) { return [pscustomobject]@{ Major = $CurrentVersion.Major Minor = $CurrentVersion.Minor Patch = $CurrentVersion.Patch } } return Resolve-SemanticReleaseVersionPart -CurrentVersion $CurrentVersion -Label Patch } return Resolve-SemanticReleaseVersionPart -CurrentVersion $CurrentVersion -Label $Label } # Source: src/private/release/Read-SemanticReleaseCommitMessage.ps1 function Read-SemanticReleaseCommitMessage { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProjectRoot ) $commitRangeBasis = Get-SemanticReleaseCommitRangeBasis -ProjectRoot $ProjectRoot if (-not $commitRangeBasis.IsRepository) { return @() } $format = '%s%n%b%n--END-COMMIT--' $logResult = Get-SemanticReleaseCommitLogResult -ProjectRoot $ProjectRoot -Format $format -CommitRangeBasis $commitRangeBasis return @(ConvertFrom-SemanticReleaseCommitLogResult -Result $logResult) } function Get-SemanticReleaseCommitLogResult { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProjectRoot, [Parameter(Mandatory)][string]$Format, [Parameter(Mandatory)][pscustomobject]$CommitRangeBasis ) if ($CommitRangeBasis.HasVersionTag) { return Invoke-SemanticReleaseGitCommand -ProjectRoot $ProjectRoot -Arguments @('log', $CommitRangeBasis.RevisionRange, "--format=$Format") } return Invoke-SemanticReleaseGitCommand -ProjectRoot $ProjectRoot -Arguments @('log', "--format=$Format") } function ConvertFrom-SemanticReleaseCommitLogResult { [CmdletBinding()] param( [Parameter(Mandatory)][pscustomobject]$Result ) if ($Result.ExitCode -ne 0 -or @($Result.Output).Count -eq 0) { return @() } $text = @($Result.Output) -join [Environment]::NewLine $commitList = $text -split '(?m)^--END-COMMIT--\r?$' return @($commitList | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) } # Source: src/private/release/Resolve-SemanticReleasePreReleaseLabel.ps1 function Resolve-SemanticReleasePreReleaseLabel { [CmdletBinding()] param( [AllowNull()][semver]$CurrentVersion, [switch]$PreviewRelease, [switch]$StableRelease ) if ($PreviewRelease) { return Resolve-SemanticReleasePreviewLabel -CurrentVersion $CurrentVersion } if ($StableRelease) { return $null } return $null } function Resolve-SemanticReleasePreviewLabel { [CmdletBinding()] param( [AllowNull()][semver]$CurrentVersion ) if ($null -eq $CurrentVersion -or [string]::IsNullOrWhiteSpace($CurrentVersion.PreReleaseLabel)) { return 'preview' } return Get-SemanticReleaseNextPreReleaseLabel -PreReleaseLabel $CurrentVersion.PreReleaseLabel } function Get-SemanticReleaseNextPreReleaseLabel { [CmdletBinding()] param( [Parameter(Mandatory)][string]$PreReleaseLabel ) $match = [regex]::Match($PreReleaseLabel, '^(?<Stem>.*?)(?<Number>\d+)?$') $stem = $match.Groups['Stem'].Value $preReleaseNumber = $match.Groups['Number'].Value if ([string]::IsNullOrWhiteSpace($preReleaseNumber)) { return "$stem$( Get-SemanticReleaseInitialPreReleaseNumber )" } return "$stem$( Get-SemanticReleaseIncrementedPreReleaseNumber -PreReleaseNumber $preReleaseNumber )" } function Get-SemanticReleaseInitialPreReleaseNumber { [CmdletBinding()] param() return '01' } function Get-SemanticReleaseIncrementedPreReleaseNumber { [CmdletBinding()] param( [Parameter(Mandatory)][string]$PreReleaseNumber ) $nextNumber = [int]$PreReleaseNumber + 1 if ($PreReleaseNumber.Length -eq 1) { return $nextNumber } return $nextNumber.ToString("D$( $PreReleaseNumber.Length )") } # Source: src/private/release/Resolve-SemanticReleaseVersionLabel.ps1 function Resolve-SemanticReleaseVersionLabel { [CmdletBinding()] param( [Parameter(Mandatory)][string[]]$Message ) if ($Message | Where-Object { $_ -match '(?im)BREAKING CHANGE|^[a-z]+(\(.+\))?!:' }) { return 'Major' } if ($Message | Where-Object { $_ -match '(?im)^\s*feat(\(.+\))?:' }) { return 'Minor' } if ($Message | Where-Object { $_ -match '(?im)^\s*fix(\(.+\))?:' }) { return 'Patch' } return 'Patch' } # Source: src/private/release/Resolve-SemanticReleaseVersionPart.ps1 function Resolve-SemanticReleaseVersionPart { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion, [ValidateSet('Major', 'Minor', 'Patch')] [string]$Label = 'Patch' ) if (Test-SemanticReleaseShouldFinalizePrereleaseTarget -CurrentVersion $CurrentVersion -Label $Label) { return Get-SemanticReleaseVersionPartObject -CurrentVersion $CurrentVersion } switch ($Label) { 'Major' { return [pscustomobject]@{ Major = $CurrentVersion.Major + 1 Minor = 0 Patch = 0 } } 'Minor' { return [pscustomobject]@{ Major = $CurrentVersion.Major Minor = $CurrentVersion.Minor + 1 Patch = 0 } } default { return [pscustomobject]@{ Major = $CurrentVersion.Major Minor = $CurrentVersion.Minor Patch = $CurrentVersion.Patch + 1 } } } } function Test-SemanticReleaseShouldFinalizePrereleaseTarget { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion, [Parameter(Mandatory)][string]$Label ) if ([string]::IsNullOrWhiteSpace($CurrentVersion.PreReleaseLabel)) { return $false } return (Get-SemanticReleaseTargetLabelForPrerelease -CurrentVersion $CurrentVersion) -eq $Label } function Get-SemanticReleaseTargetLabelForPrerelease { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion ) if ($CurrentVersion.Patch -gt 0) { return 'Patch' } if ($CurrentVersion.Minor -gt 0) { return 'Minor' } return 'Major' } function Get-SemanticReleaseVersionPartObject { [CmdletBinding()] param( [Parameter(Mandatory)][semver]$CurrentVersion ) return [pscustomobject]@{ Major = $CurrentVersion.Major Minor = $CurrentVersion.Minor Patch = $CurrentVersion.Patch } } # Source: src/private/shared/Get-SemanticReleaseGitCommandOutputText.ps1 function Get-SemanticReleaseGitCommandOutputText { [CmdletBinding()] param( [Parameter(Mandatory)][pscustomobject]$Result ) return (@($Result.Output) -join [Environment]::NewLine).Trim() } # Source: src/private/shared/Invoke-SemanticReleaseGitCommand.ps1 function Invoke-SemanticReleaseGitCommand { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProjectRoot, [Parameter(Mandatory)][string[]]$Arguments ) $output = & git -C $ProjectRoot @Arguments 2> $null return [pscustomobject]@{ ExitCode = $LASTEXITCODE Output = @($output) } } # Source: src/private/shared/Read-SemanticReleaseJsonData.ps1 function Read-SemanticReleaseJsonData { [CmdletBinding()] param( [Parameter(Mandatory)][string]$JsonPath ) $jsonContent = Get-Content -LiteralPath $JsonPath -Raw if ([string]::IsNullOrWhiteSpace($jsonContent)) { throw "JSON file is empty: $JsonPath" } try { $jsonData = $jsonContent | ConvertFrom-Json -AsHashtable } catch { throw "JSON file is not valid JSON: $JsonPath. $( $_.Exception.Message )" } if ($jsonData -isnot [hashtable]) { throw "JSON file must contain a top-level JSON object: $JsonPath" } return $jsonData } # Source: src/private/shared/Test-SemanticReleaseGitAvailable.ps1 function Test-SemanticReleaseGitAvailable { [CmdletBinding()] param() return $null -ne (Get-Command -Name 'git' -ErrorAction SilentlyContinue) } # Source: src/private/shared/Write-SemanticReleaseJsonData.ps1 function Write-SemanticReleaseJsonData { [CmdletBinding()] param( [Parameter(Mandatory)][string]$JsonPath, [Parameter(Mandatory)][hashtable]$Data ) $jsonContent = $Data | ConvertTo-Json -Depth 20 Set-Content -LiteralPath $JsonPath -Value $jsonContent -Encoding utf8 } |