diff --git a/eng/pipelines/templates/stages/archetype-js-release.yml b/eng/pipelines/templates/stages/archetype-js-release.yml index 151a23619015..0e7aca235ca4 100644 --- a/eng/pipelines/templates/stages/archetype-js-release.yml +++ b/eng/pipelines/templates/stages/archetype-js-release.yml @@ -81,6 +81,16 @@ stages: echo "##vso[task.setvariable variable=Package.Archive]$DETECTED_PACKAGE_NAME" displayName: Detecting package archive + - pwsh: | + write-host "$(Package.Archive)" + $result = eng/scripts/get-npm-tags.ps1 -packageArtifact $(Package.Archive) -workingDirectory $(System.DefaultWorkingDirectory)/temp + write-host "Tag: $($result.Tag)" + write-host "Tag: $($result.AdditionalTag)" + echo "##vso[task.setvariable variable=Tag]$($result.Tag)" + echo "##vso[task.setvariable variable=AdditionalTag]$($result.AdditionalTag)" + condition: and(succeeded(), ne(variables['Skip.AutoAddTag'], 'true')) + displayName: 'Set Tag and Additional Tag' + - task: PowerShell@2 displayName: 'Publish to npmjs.org' inputs: @@ -89,6 +99,12 @@ stages: arguments: '-pathToArtifacts $(Package.Archive) -accessLevel "public" -tag $(Tag) -additionalTag "$(AdditionalTag)" -registry ${{parameters.Registry}} -npmToken $(azure-sdk-npm-token)' pwsh: true + - pwsh: | + eng/scripts/cleanup-npm-next-tag.ps1 -packageArtifact $(Package.Archive) -workingDirectory $(System.DefaultWorkingDirectory) -npmToken $(azure-sdk-npm-token) + displayName: 'Cleanup Npm Next Tag' + condition: and(succeeded(), ne(variables['Skip.RemoveOldTag'], 'true')) + + - ${{if ne(artifact.skipPublishDocMs, 'true')}}: - deployment: PublishDocs displayName: Docs.MS Release diff --git a/eng/scripts/cleanup-npm-next-tag.ps1 b/eng/scripts/cleanup-npm-next-tag.ps1 new file mode 100644 index 000000000000..c8d34f1fad54 --- /dev/null +++ b/eng/scripts/cleanup-npm-next-tag.ps1 @@ -0,0 +1,57 @@ +param ( + [Parameter(mandatory = $true)] + $packageArtifact, + [Parameter(mandatory = $true)] + $workingDirectory, + [Parameter(mandatory = $true)] + $npmToken +) + +$HelpersPath = Join-Path $PSScriptRoot "helpers" +. (Join-Path $HelpersPath npm-helpers.ps1) + +$pkgProps = Get-javascript-PackageInfoFromPackageFile -pkg $packageArtifact -workingDirectory $workingDirectory +if ($pkgProps -eq $null) +{ + Write-Error "Failed to parse package artifact $packageArtifact to get package name" + exit 1 +} +$packageName = $pkgProps.PackageId +Write-Host "Package name: $packageName" +Write-Host "Find latest and next versions in npm registry for package" + +<# +1. Get latest GA and latest preview from npm +2. Check if latest GA is higher than next +3. Remove next tag from preview version if latest is higher than preview version +#> + +$npmVersionInfo = GetNpmTagVersions -packageName $packageName +if ($npmVersionInfo -eq $null) +{ + # Version info object should not be null even if package is not present in npm + Write-Error "Failed to get version info from NPM registry." + exit 1 +} +$nextVersion = [AzureEngSemanticVersion]::ParseVersionString($npmVersionInfo.next) +$latestVersion = [AzureEngSemanticVersion]::ParseVersionString($npmVersionInfo.latest) + +if ( ($latestVersion -ne $null) -and ($nextVersion -ne $null) -and (!$latestVersion.IsPreRelease)) +{ + if ($latestVersion.CompareTo($nextVersion) -eq 1) + { + Write-Host "Latest Version $latestVersion is higher than next tagged version $nextVersion." + Write-Host "Removing next tag from $nextVersion." + $scriptsPath = Join-Path $EngPath "scripts" + . (Join-Path $scriptsPath npm-admin-tasks.ps1) -taskType "RemoveTag" -packageName $packageName -pkgVersion $nextVersion.ToString() -tagName "next" -npmToken $npmToken + } + else + { + Write-Host "Latest tagged version is lower than or same as next tagged version." + Write-Host "Skipping remove 'next' tag task." + } +} +else +{ + Write-Host "Latest or next tag is missing on npm or latest version is not GA release to compare versions." +} diff --git a/eng/scripts/get-npm-tags.ps1 b/eng/scripts/get-npm-tags.ps1 new file mode 100644 index 000000000000..7a7113be54b7 --- /dev/null +++ b/eng/scripts/get-npm-tags.ps1 @@ -0,0 +1,20 @@ +param ( + [Parameter(mandatory = $true)] + $packageArtifact, + [Parameter(mandatory = $true)] + $workingDirectory +) + +$HelpersPath = Join-Path $PSScriptRoot "helpers" +. (Join-Path $HelpersPath npm-helpers.ps1) + +$pkgProps = Get-javascript-PackageInfoFromPackageFile -pkg $packageArtifact -workingDirectory $workingDirectory +if ($pkgProps -eq $null) +{ + Write-Error "Failed to parse package artifact $packageArtifact to get package name" + exit 1 +} +$packageName = $pkgProps.PackageId +$packageVersion = $pkgProps.PackageVersion +$result = GetNewNpmTags -packageName $packageName -packageVersion $packageVersion +return $result diff --git a/eng/scripts/helpers/npm-helpers.ps1 b/eng/scripts/helpers/npm-helpers.ps1 new file mode 100644 index 000000000000..11bc265b8ae5 --- /dev/null +++ b/eng/scripts/helpers/npm-helpers.ps1 @@ -0,0 +1,157 @@ +$EngPath = Resolve-Path "${PSScriptRoot}/../.." +$Engcommon = Join-Path $EngPath "common" +$EngCommonScriptsPath = Join-Path $Engcommon "scripts" +. (Join-Path $EngCommonScriptsPath common.ps1) + +function GetNpmTagVersions($packageName) +{ + try + { + $existingVersion = Invoke-RestMethod -Method GET -Uri "https://registry.npmjs.com/${PackageName}" + $latest = ($existingVersion."dist-tags").latest + $next = ($existingVersion."dist-tags").next + Write-Host "Latest version: $latest" + Write-Host "Next version: $next" + if ($latest -eq $null) { + Write-Host "'latest' tag is not present in npm registry for package $packageName" + } + if ($next -eq $null) { + Write-Host "'Next' tag is not present in npm registry for package $packageName" + } + + return New-Object PSObject -Property @{ + latest = $latest + next = $next + } + } + catch + { + return $null + } +} + +function GetNpmPackageVersions ($packageName) +{ + try + { + Write-Host "Checking versions present on npm for package $packageName" + $existingVersion = Invoke-RestMethod -Method GET -Uri "https://registry.npmjs.com/${packageName}" + return ($existingVersion.versions | Get-Member -MemberType NoteProperty).Name + } + catch + { + return $null + } +} + +function FindRecentPackageVersion($packageName) +{ + $versions = (GetNpmPackageVersions -packageName $packageName).Where({$_ -notmatch "alpha|dev"}) + if ($versions.Count -gt 0) + { + $versions = [AzureEngSemanticVersion]::SortVersionStrings($versions) + $highestNpmVersion = $versions[0] + Write-Host "Recent version uploaded to NPM: $highestNpmVersion" + return $highestNpmVersion + } + + return $null +} + +function GetNewNpmTags($packageName, $packageVersion) +{ + $newVersion = [AzureEngSemanticVersion]::ParseVersionString($packageVersion) + Write-Host "Package name: $packageName" + Write-Host "Package version: $packageVersion" + Write-Host "Find latest and next versions in npm registry for package" + + <# + 1. Get latest GA and latest preview from npm + 2. Check new version to be published to find is it GA or preview + 3. If new Version is GA: + a. If higher than current latest, or first GA then set LATEST tag + 4. If new version is preview and higher than current higher version in npm: + a. Set LATEST if package has never GA released + b. Set NEXT tag + #> + $npmVersionInfo = GetNpmTagVersions -packageName $packageName + $latestVersion = [AzureEngSemanticVersion]::ParseVersionString($npmVersionInfo.latest) + $setLatest = $false + $setNext = $false + # Set Latest tag if new version is higher than current GA or if package has never GA released before + if ((!$newVersion.IsPreRelease) -and ($latestVersion -eq $null -or $newVersion.CompareTo($latestVersion) -eq 1)) { + $setLatest = $true + } + + if ($newVersion.PrereleaseLabel -eq "preview" -or $newVersion.PrereleaseLabel -eq "beta") + { + Write-Host "Checking for next version tag" + # Set next tag if new preview is higher than highest present on npm + $highestNpmVersion = FindRecentPackageVersion -packageName $packageName + $highestNpmVersion = [AzureEngSemanticVersion]::ParseVersionString($highestNpmVersion) + # New version is preview and if package is getting released first time or higher than currently available + if ($highestNpmVersion -eq $null -or $newVersion.CompareTo($highestNpmVersion) -eq 1) + { + $setNext = $true + # Set latest tag if package was never GA released + if ($latestVersion -eq $null -or $latestVersion.IsPreRelease) { + $setLatest = $true + } + } + } + + $tag = "" + $additionalTag = "" + if ($setLatest) + { + $tag = "latest" + if ($setNext) { + $additionalTag = "next" + } + } + elseif ($setNext) { + $tag = "next" + } + + $result = New-Object PSObject -Property @{ + Tag = $tag + AdditionalTag = $additionalTag + } + write-Host $result + return $result +} + +function CreateTestCase($packageName, $packageVersion, $eTag, $eAdditional) +{ + $r = GetNewNpmTags -packageName $packageName -packageVersion $packageVersion + if ($r.Tag -ne $eTag -or $r.AdditionalTag -ne $eAdditional) + { + Write-Error "Failed test case." + Write-Host "Extected tag: '$($eTag)'' Actual tag: '$($r.Tag)'" + Write-Host "Extected tag: '$($eAdditional)' Actual tag: '$($r.AdditionalTag)'" + } + else{ + Write-Host "Succeeded test case for $packageName version $packageVersion" + } +} + +function TestNewTags() +{ + # test cases are based on currently available package version on npm + CreateTestCase -packageVersion "1.1.0" -eTag "latest" -eAdditional "" -packageName "@azure/template" + CreateTestCase -packageName "@azure/template" -packageVersion "1.1.0-preview.1" -eTag "latest" -eAdditional "next" + CreateTestCase -packageName "@azure/template" -packageVersion "1.0.9" -eTag "latest" -eAdditional "" + CreateTestCase -packageName "@azure/template" -packageVersion "1.0.8" -eTag "" -eAdditional "" + CreateTestCase -packageName "@azure/core-http" -packageVersion "1.2.5" -eTag "latest" -eAdditional "" + CreateTestCase -packageName "@azure/core-http" -packageVersion "1.3.0-preview.1" -eTag "next" -eAdditional "" + CreateTestCase -packageName "@azure/core-http" -packageVersion "1.2.2" -eTag "" -eAdditional "" + CreateTestCase -packageName "@azure/core-http" -packageVersion "1.2.5-preview.1" -eTag "next" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.5.1" -eTag "latest" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.6.0-beta.2" -eTag "next" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.6.0" -eTag "latest" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.6.0-alpha.20210525.2" -eTag "" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.4.1" -eTag "" -eAdditional "" + CreateTestCase -packageName "@azure/storage-blob" -packageVersion "12.4.1-preview.1" -eTag "" -eAdditional "" + CreateTestCase -packageName "@azure/dummy-new-package" -packageVersion "1.0.0" -eTag "latest" -eAdditional "" + CreateTestCase -packageName "@azure/dummy-new-package" -packageVersion "1.0.0-preview.1" -eTag "latest" -eAdditional "next" +}