Public/Build/Update-Changelog.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
function Update-Changelog
{
    [CmdletBinding()]
    param
    (
        # The path to the changelog file
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string]
        $ChangelogPath,

        # The type of release (major, minor, patch)
        [Parameter(
            Mandatory = $false
        )]
        [ValidateSet('major', 'minor', 'patch')]
        [string]
        $ReleaseType,

        # The feature list for this release
        [Parameter(
            Mandatory = $false
        )]
        [array]
        $Features,

        # Any bugfixes in this release
        [Parameter(
            Mandatory = $false
        )]
        [array]
        $Bugfixes,

        # Any known issues in this release
        [Parameter(
            Mandatory = $false
        )]
        [array]
        $KnownIssues,

        # The URL of the repo that the changelog belongs to
        # If none is provided the cmdlet will attempt to work it out from the current changelog and prompt if needed
        [Parameter(
            Mandatory = $false
        )]
        [string]
        $RepoUrl,

        # If set will attempt to auto-generate features from the commit history (ignored if $Features are passed into the cmdlet)
        [Parameter(
            Mandatory = $false
        )]
        [switch]
        $AutoGenerateFeatures,

        # Skip optional prompts
        [Parameter(
            Mandatory = $false
        )]
        [switch]
        $SkipOptionalPrompts
    )

    Write-Verbose 'Checking changelog path is valid'
    if (!(Test-Path $ChangelogPath))
    {
        throw "$ChangelogPath does not appear to be a valid path to a changelog"
    }

    # Read current changelog information
    try
    {
        $CurrentChangelogInfo = Read-Changelog -ChangelogPath $ChangelogPath
    }
    catch
    {
        throw "Failed to get current changelog information.`n$($_.Exception.Message)"
    }

    # If we don't have a URL already then try to extract it from the changelog
    if (!$RepoUrl)
    {
        $RepoURL = $CurrentChangelogInfo.RepoURL
    }

    # Start by getting mandatory information
    Write-Verbose 'Checking all required information is present'

    # Find out what type of release we are doing
    # We re-cast ReleaseType to a new variable as PowerShell does something special with cmdlet parameters when they are set-up
    # Meaning we can get some weird errors if we try to re-use them
    $ReleaseTypeCheck = $ReleaseType
    $ValidReleaseTypes = @('major', 'minor', 'patch')
    while ($ReleaseTypeCheck -notin $ValidReleaseTypes)
    {
        $ReleaseTypeCheck = Get-Response `
            -Prompt "What kind of release is this? (major/minor/patch)`n
    Major (Breaking changes from previous version)`n
    Minor (Backwards compatible changes from previous version)`n
    Patch (Minor backwards compatible bug fixes from previous version)"
 `
            -ResponseType 'string' `
            -Mandatory
    }

    # Increment our version number based on what kind of release we're doing
    switch ($ReleaseTypeCheck)
    {
        'major'
        {
            $Version = [version]::New("$($CurrentChangelogInfo.CurrentVersion.Major + 1).0.0")
        }
        'minor'
        {
            $Version = [version]::New("$($CurrentChangelogInfo.CurrentVersion.Major).$($CurrentChangelogInfo.CurrentVersion.Minor + 1).0")
        }
        'patch'
        {
            $Version = [version]::New("$($CurrentChangelogInfo.CurrentVersion.Major).$($CurrentChangelogInfo.CurrentVersion.Minor).$($CurrentChangelogInfo.CurrentVersion.Build + 1)")
        }
    }
    Write-Verbose "Version is now $($Version.ToString())"

    if (!$Features)
    {
        Write-Verbose 'Prompting for features'
        # Offer to auto generate a list of release features from the git commit history for this branch (if we haven't already specified it in the params above)
        if (!$AutoGenerateFeatures)
        {
            $AutoGenerateFeatures = Get-Response `
                -Prompt 'Would you like to generate a list of new features for this release from this branches commit history?' `
                -ResponseType 'bool'
        }
        if ($AutoGenerateFeatures)
        {
            Write-Verbose 'Auto-generating a list of features based off of commit history'
            try
            {
                # First get the current branch
                $CurrentBranch = Invoke-NativeCommand `
                    -FilePath 'git' `
                    -Arguments 'rev-parse --abbrev-ref HEAD' `
                    -PassThru `
                    -SuppressOutput | Select-Object -ExpandProperty OutputContent
                Write-Verbose "CurrentBranch detected as $CurrentBranch"

                # Create a git log search filter that will list all commits between the previous tag and the current HEAD
                $CommitSearcher = "v$($CurrentChangelogInfo.CurrentVersion.ToString())..HEAD"

                # Query the git log for all merge changes since the previous tag
                # Output only the body string (%b) and dump the result into an array
                # We use merges to try and cut down on the number of false positives, if this proves to be not verbose enough
                # we can switch back to messages (%s)
                $Features = (Invoke-NativeCommand `
                        -FilePath 'git' `
                        -Arguments "log --merges $CommitSearcher --pretty=`"%b`"" `
                        -PassThru `
                        -SuppressOutput | Select-Object -ExpandProperty OutputContent) -split "`n"
                
                if (!$Features)
                {
                    Write-Error 'Failed to automatically get a list of commits'
                }
            }
            catch
            {
                throw $_.Exception.Message
            }
        }
        else
        {
            $Features = Get-Response `
                -Prompt 'What are the new features that this release brings?' `
                -ResponseType 'array' `
                -Mandatory
        }
    }

    # If we haven't got the URL by now then prompt for it
    if (!$RepoUrl)
    {
        $RepoUrl = Get-Response `
            -Prompt 'What is the URL of the repo that the changelog belongs to?' `
            -ResponseType 'string' `
            -Mandatory
    }

    # Get any optional params
    if (-not $SkipOptionalPrompts)
    {
        Write-Verbose 'Prompting for optional information'
        # We use '-notin $PSBoundParameters.Keys' instead of '-not' or '!' as this ensures we get the correct result each time...trust me
        if ('BugFixes' -notin $PSBoundParameters.Keys)
        {
            $Bugfixes = Get-Response `
                -Prompt 'What Bugfixes does this release bring?' `
                -ResponseType 'array'
        }

        if ('KnownIssues' -notin $PSBoundParameters.Keys)
        {
            $KnownIssues = Get-Response `
                -Prompt 'What known issues are present in this release?' `
                -ResponseType 'array'
        }
    }

    # Generate the new changelog block
    $ChangelogBlockParams = @{
        Version  = $Version
        RepoUrl  = $RepoUrl
        Features = $Features
    }
    if ($Bugfixes)
    {
        $ChangelogBlockParams.Add('Bugfixes', $Bugfixes)
    }
    if ($KnownIssues)
    {
        $ChangelogBlockParams.Add('KnownIssues', $KnownIssues)
    }

    Write-Verbose 'Generating new changelog block'
    try
    {
        $ChangelogBlock = New-ChangelogBlock @ChangelogBlockParams
    }
    catch
    {
        throw $_.Exception.Message
    }
    Write-Debug $ChangelogBlock

    # All being good lets update our changelog!
    try
    {
        $CurrentChangelogInfo | Add-ChangelogEntry -NewContent $ChangelogBlock
    }
    catch
    {
        Write-Error "Failed to update changelog.$($_.Exception.Message)"
    }
}