...

Text file src/github.com/Azure/azure-sdk-for-go/eng/common/scripts/ChangeLog-Operations.ps1

Documentation: github.com/Azure/azure-sdk-for-go/eng/common/scripts

     1# Common Changelog Operations
     2. "${PSScriptRoot}\logging.ps1"
     3. "${PSScriptRoot}\SemVer.ps1"
     4
     5$RELEASE_TITLE_REGEX = "(?<releaseNoteTitle>^\#+\s+(?<version>$([AzureEngSemanticVersion]::SEMVER_REGEX))(\s+(?<releaseStatus>\(.+\))))"
     6$SECTION_HEADER_REGEX_SUFFIX = "##\s(?<sectionName>.*)"
     7$CHANGELOG_UNRELEASED_STATUS = "(Unreleased)"
     8$CHANGELOG_DATE_FORMAT = "yyyy-MM-dd"
     9$RecommendedSectionHeaders = @("Features Added", "Breaking Changes", "Bugs Fixed", "Other Changes")
    10
    11# Returns a Collection of changeLogEntry object containing changelog info for all version present in the gived CHANGELOG
    12function Get-ChangeLogEntries {
    13  param (
    14    [Parameter(Mandatory = $true)]
    15    [String]$ChangeLogLocation
    16  )
    17
    18  if (!(Test-Path $ChangeLogLocation)) {
    19    LogError "ChangeLog[${ChangeLogLocation}] does not exist"
    20    return $null
    21  }
    22  LogDebug "Extracting entries from [${ChangeLogLocation}]."
    23  return Get-ChangeLogEntriesFromContent (Get-Content -Path $ChangeLogLocation)
    24}
    25
    26function Get-ChangeLogEntriesFromContent {
    27  param (
    28    [Parameter(Mandatory = $true)]
    29    $changeLogContent
    30  )
    31
    32  if ($changeLogContent -is [string])
    33  {
    34    $changeLogContent = $changeLogContent.Split("`n")
    35  }
    36  elseif($changeLogContent -isnot [array])
    37  {
    38    LogError "Invalid ChangelogContent passed"
    39    return $null
    40  }
    41
    42  $changelogEntry = $null
    43  $sectionName = $null
    44  $changeLogEntries = [Ordered]@{}
    45  $initialAtxHeader= "#"
    46
    47  if ($changeLogContent[0] -match "(?<HeaderLevel>^#+)\s.*")
    48  {
    49    $initialAtxHeader = $matches["HeaderLevel"]
    50  }
    51
    52  $sectionHeaderRegex = "^${initialAtxHeader}${SECTION_HEADER_REGEX_SUFFIX}"
    53  $changeLogEntries | Add-Member -NotePropertyName "InitialAtxHeader" -NotePropertyValue $initialAtxHeader
    54  $releaseTitleAtxHeader = $initialAtxHeader + "#"
    55
    56  try {
    57    # walk the document, finding where the version specifiers are and creating lists
    58    foreach ($line in $changeLogContent) {
    59      if ($line -match $RELEASE_TITLE_REGEX) {
    60        $changeLogEntry = [pscustomobject]@{
    61          ReleaseVersion = $matches["version"]
    62          ReleaseStatus  =  $matches["releaseStatus"]
    63          ReleaseTitle   = "$releaseTitleAtxHeader {0} {1}" -f $matches["version"], $matches["releaseStatus"]
    64          ReleaseContent = @()
    65          Sections = @{}
    66        }
    67        $changeLogEntries[$changeLogEntry.ReleaseVersion] = $changeLogEntry
    68      }
    69      else {
    70        if ($changeLogEntry) {
    71          if ($line.Trim() -match $sectionHeaderRegex)
    72          {
    73            $sectionName = $matches["sectionName"].Trim()
    74            $changeLogEntry.Sections[$sectionName] = @()
    75            $changeLogEntry.ReleaseContent += $line
    76            continue
    77          }
    78
    79          if ($sectionName)
    80          {
    81            $changeLogEntry.Sections[$sectionName] += $line
    82          }
    83
    84          $changeLogEntry.ReleaseContent += $line
    85        }
    86      }
    87    }
    88  }
    89  catch {
    90    Write-Error "Error parsing Changelog."
    91    Write-Error $_
    92  }
    93  return $changeLogEntries
    94}
    95
    96# Returns single changeLogEntry object containing the ChangeLog for a particular version
    97function Get-ChangeLogEntry {
    98  param (
    99    [Parameter(Mandatory = $true)]
   100    [String]$ChangeLogLocation,
   101    [Parameter(Mandatory = $true)]
   102    [String]$VersionString
   103  )
   104  $changeLogEntries = Get-ChangeLogEntries -ChangeLogLocation $ChangeLogLocation
   105
   106  if ($changeLogEntries -and $changeLogEntries.Contains($VersionString)) {
   107    return $changeLogEntries[$VersionString]
   108  }
   109  return $null
   110}
   111
   112#Returns the changelog for a particular version as string
   113function Get-ChangeLogEntryAsString {
   114  param (
   115    [Parameter(Mandatory = $true)]
   116    [String]$ChangeLogLocation,
   117    [Parameter(Mandatory = $true)]
   118    [String]$VersionString
   119  )
   120
   121  $changeLogEntry = Get-ChangeLogEntry -ChangeLogLocation $ChangeLogLocation -VersionString $VersionString
   122  return ChangeLogEntryAsString $changeLogEntry
   123}
   124
   125function ChangeLogEntryAsString($changeLogEntry) {
   126  if (!$changeLogEntry) {
   127    return "[Missing change log entry]"
   128  }
   129  [string]$releaseTitle = $changeLogEntry.ReleaseTitle
   130  [string]$releaseContent = $changeLogEntry.ReleaseContent -Join [Environment]::NewLine
   131  return $releaseTitle, $releaseContent -Join [Environment]::NewLine
   132}
   133
   134function Confirm-ChangeLogEntry {
   135  param (
   136    [Parameter(Mandatory = $true)]
   137    [String]$ChangeLogLocation,
   138    [Parameter(Mandatory = $true)]
   139    [String]$VersionString,
   140    [boolean]$ForRelease = $false,
   141    [Switch]$SantizeEntry
   142  )
   143
   144  $changeLogEntries = Get-ChangeLogEntries -ChangeLogLocation $ChangeLogLocation
   145  $changeLogEntry = $changeLogEntries[$VersionString]
   146
   147  if (!$changeLogEntry) {
   148    LogError "ChangeLog[${ChangeLogLocation}] does not have an entry for version ${VersionString}."
   149    return $false
   150  }
   151
   152  if ($SantizeEntry)
   153  {
   154    Remove-EmptySections -ChangeLogEntry $changeLogEntry -InitialAtxHeader $changeLogEntries.InitialAtxHeader
   155    Set-ChangeLogContent -ChangeLogLocation $ChangeLogLocation -ChangeLogEntries $changeLogEntries
   156  }
   157
   158  Write-Host "Found the following change log entry for version '${VersionString}' in [${ChangeLogLocation}]."
   159  Write-Host "-----"
   160  Write-Host (ChangeLogEntryAsString $changeLogEntry)
   161  Write-Host "-----"
   162
   163  if ([System.String]::IsNullOrEmpty($changeLogEntry.ReleaseStatus)) {
   164    LogError "Entry does not have a correct release status. Please ensure the status is set to a date '($CHANGELOG_DATE_FORMAT)' or '$CHANGELOG_UNRELEASED_STATUS' if not yet released. See https://aka.ms/azsdk/guideline/changelogs for more info."
   165    return $false
   166  }
   167
   168  if ($ForRelease -eq $True)
   169  {
   170    LogDebug "Verifying as a release build because ForRelease parameter is set to true"
   171    return Confirm-ChangeLogForRelease -changeLogEntry $changeLogEntry -changeLogEntries $changeLogEntries
   172  }
   173
   174  # If the release status is a valid date then verify like its about to be released
   175  $status = $changeLogEntry.ReleaseStatus.Trim().Trim("()")
   176  if ($status -as [DateTime])
   177  {
   178    LogDebug "Verifying like it's a release build because the changelog entry has a valid date."
   179    return Confirm-ChangeLogForRelease -changeLogEntry $changeLogEntry -changeLogEntries $changeLogEntries
   180  }
   181
   182  return $true
   183}
   184
   185function New-ChangeLogEntry {
   186  param (
   187    [Parameter(Mandatory = $true)]
   188    [ValidateNotNullOrEmpty()]
   189    [String]$Version,
   190    [String]$Status=$CHANGELOG_UNRELEASED_STATUS,
   191    [String]$InitialAtxHeader="#",
   192    [String[]]$Content
   193  )
   194
   195  # Validate RelaseStatus
   196  $Status = $Status.Trim().Trim("()")
   197  if ($Status -ne "Unreleased") {
   198    try {
   199      $Status = ([DateTime]$Status).ToString($CHANGELOG_DATE_FORMAT)
   200    }
   201    catch {
   202        LogWarning "Invalid date [ $Status ] passed as status for Version [$Version]. Please use a valid date in the format '$CHANGELOG_DATE_FORMAT' or use '$CHANGELOG_UNRELEASED_STATUS'"
   203        return $null
   204    }
   205  }
   206  $Status = "($Status)"
   207
   208  # Validate Version
   209  try {
   210    $Version = ([AzureEngSemanticVersion]::ParseVersionString($Version)).ToString()
   211  }
   212  catch {
   213    LogWarning "Invalid version [ $Version ]."
   214    return $null
   215  }
   216
   217  if (!$Content) {
   218    $Content = @()
   219    $Content += ""
   220
   221    $sectionsAtxHeader = $InitialAtxHeader + "##"
   222    foreach ($recommendedHeader in $RecommendedSectionHeaders)
   223    {
   224      $Content += "$sectionsAtxHeader $recommendedHeader"
   225      $Content += ""
   226    }
   227  }
   228
   229  $releaseTitleAtxHeader = $initialAtxHeader + "#"
   230
   231  $newChangeLogEntry = [pscustomobject]@{
   232    ReleaseVersion = $Version
   233    ReleaseStatus  = $Status
   234    ReleaseTitle   = "$releaseTitleAtxHeader $Version $Status"
   235    ReleaseContent = $Content
   236  }
   237
   238  return $newChangeLogEntry
   239}
   240
   241function Set-ChangeLogContent {
   242  param (
   243    [Parameter(Mandatory = $true)]
   244    [String]$ChangeLogLocation,
   245    [Parameter(Mandatory = $true)]
   246    $ChangeLogEntries
   247  )
   248
   249  $changeLogContent = @()
   250  $changeLogContent += "$($ChangeLogEntries.InitialAtxHeader) Release History"
   251  $changeLogContent += ""
   252
   253  $ChangeLogEntries = Sort-ChangeLogEntries -changeLogEntries $ChangeLogEntries
   254
   255  foreach ($changeLogEntry in $ChangeLogEntries) {
   256    $changeLogContent += $changeLogEntry.ReleaseTitle
   257    if ($changeLogEntry.ReleaseContent.Count -eq 0) {
   258      $changeLogContent += @("","")
   259    }
   260    else {
   261      $changeLogContent += $changeLogEntry.ReleaseContent
   262    }
   263  }
   264
   265  Set-Content -Path $ChangeLogLocation -Value $changeLogContent
   266}
   267
   268function Remove-EmptySections {
   269  param (
   270    [Parameter(Mandatory = $true)]
   271    $ChangeLogEntry,
   272    $InitialAtxHeader = "#"
   273  )
   274
   275  $sectionHeaderRegex = "^${InitialAtxHeader}${SECTION_HEADER_REGEX_SUFFIX}"
   276  $releaseContent = $ChangeLogEntry.ReleaseContent
   277
   278  if ($releaseContent.Count -gt 0)
   279  {
   280    $parsedSections = $ChangeLogEntry.Sections
   281    $sanitizedReleaseContent = New-Object System.Collections.ArrayList(,$releaseContent)
   282  
   283    foreach ($key in @($parsedSections.Keys)) 
   284    {
   285      if ([System.String]::IsNullOrWhiteSpace($parsedSections[$key]))
   286      {
   287        for ($i = 0; $i -lt $sanitizedReleaseContent.Count; $i++)
   288        {
   289          $line = $sanitizedReleaseContent[$i]
   290          if ($line -match $sectionHeaderRegex -and $matches["sectionName"].Trim() -eq $key)
   291          {
   292            $sanitizedReleaseContent.RemoveAt($i)
   293            while($i -lt $sanitizedReleaseContent.Count -and [System.String]::IsNullOrWhiteSpace($sanitizedReleaseContent[$i]))
   294            {
   295              $sanitizedReleaseContent.RemoveAt($i)
   296            }
   297            $ChangeLogEntry.Sections.Remove($key)
   298            break
   299          }
   300        }
   301      }
   302    }
   303    $ChangeLogEntry.ReleaseContent = $sanitizedReleaseContent.ToArray()
   304  }
   305}
   306
   307function  Get-LatestReleaseDateFromChangeLog
   308{
   309  param (
   310    [Parameter(Mandatory = $true)]
   311    $ChangeLogLocation
   312  )
   313  $changeLogEntries = Get-ChangeLogEntries -ChangeLogLocation $ChangeLogLocation
   314  $latestVersion = $changeLogEntries[0].ReleaseStatus.Trim("()")
   315  return ($latestVersion -as [DateTime])
   316}
   317
   318function Sort-ChangeLogEntries {
   319  param (
   320    [Parameter(Mandatory = $true)]
   321    $changeLogEntries
   322  )
   323
   324  try
   325  {
   326    $changeLogEntries = $ChangeLogEntries.Values | Sort-Object -Descending -Property ReleaseStatus, `
   327      @{e = {[AzureEngSemanticVersion]::new($_.ReleaseVersion)}}
   328  }
   329  catch {
   330    LogError "Problem sorting version in ChangeLogEntries"
   331    exit(1)
   332  }
   333  return $changeLogEntries
   334}
   335
   336function Confirm-ChangeLogForRelease {
   337  param (
   338    [Parameter(Mandatory = $true)]
   339    $changeLogEntry,
   340    [Parameter(Mandatory = $true)]
   341    $changeLogEntries
   342  )
   343
   344  $entries = Sort-ChangeLogEntries -changeLogEntries $changeLogEntries
   345
   346  $isValid = $true
   347  if ($changeLogEntry.ReleaseStatus -eq $CHANGELOG_UNRELEASED_STATUS) {
   348    LogError "Entry has no release date set. Please ensure to set a release date with format '$CHANGELOG_DATE_FORMAT'. See https://aka.ms/azsdk/guideline/changelogs for more info."
   349    $isValid = $false
   350  }
   351  else {
   352    $status = $changeLogEntry.ReleaseStatus.Trim().Trim("()")
   353    try {
   354      $releaseDate = [DateTime]$status
   355      if ($status -ne ($releaseDate.ToString($CHANGELOG_DATE_FORMAT)))
   356      {
   357        LogError "Date must be in the format $($CHANGELOG_DATE_FORMAT). See https://aka.ms/azsdk/guideline/changelogs for more info."
   358        $isValid = $false
   359      }
   360
   361      if (@($entries.ReleaseStatus)[0] -ne $changeLogEntry.ReleaseStatus)
   362      {
   363        LogError "Invalid date [ $status ]. The date for the changelog being released must be the latest in the file."
   364        $isValid = $false
   365      }
   366    }
   367    catch {
   368        LogError "Invalid date [ $status ] passed as status for Version [$($changeLogEntry.ReleaseVersion)]. See https://aka.ms/azsdk/guideline/changelogs for more info."
   369        $isValid = $false
   370    }
   371  }
   372
   373  if ([System.String]::IsNullOrWhiteSpace($changeLogEntry.ReleaseContent)) {
   374    LogError "Entry has no content. Please ensure to provide some content of what changed in this version. See https://aka.ms/azsdk/guideline/changelogs for more info."
   375    $isValid = $false
   376  }
   377
   378  $foundRecommendedSection = $false
   379  $emptySections = @()
   380  foreach ($key in $changeLogEntry.Sections.Keys)
   381  {
   382    $sectionContent = $changeLogEntry.Sections[$key]
   383    if ([System.String]::IsNullOrWhiteSpace(($sectionContent | Out-String)))
   384    {
   385      $emptySections += $key
   386    }
   387    if ($RecommendedSectionHeaders -contains $key)
   388    {
   389      $foundRecommendedSection = $true
   390    }
   391  }
   392  if ($emptySections.Count -gt 0)
   393  {
   394    LogError "The changelog entry has the following sections with no content ($($emptySections -join ', ')). Please ensure to either remove the empty sections or add content to the section."
   395    $isValid = $false
   396  }
   397  if (!$foundRecommendedSection)
   398  {
   399    LogWarning "The changelog entry did not contain any of the recommended sections ($($RecommendedSectionHeaders -join ', ')), please add at least one. See https://aka.ms/azsdk/guideline/changelogs for more info."
   400  }
   401  return $isValid
   402}

View as plain text