mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Add a script to publish a folder of binaries as a GH release (#13629)
I thought, "what if I could just have a script make all the releases, tags and names and upload all the assets to the right place?" So, here's that script.
This commit is contained in:
parent
4ff38c260f
commit
abf5d9423a
308
tools/ReleaseEngineering/Draft-TerminalReleases.ps1
Normal file
308
tools/ReleaseEngineering/Draft-TerminalReleases.ps1
Normal file
@ -0,0 +1,308 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
#################################
|
||||
# Draft-TerminalReleases takes a directory full of Terminal build outputs:
|
||||
# - zip files
|
||||
# - preinstallation kits
|
||||
# - MSIX bundles
|
||||
# and produces tagged releases with hashes and assets on GitHub.
|
||||
# It automatically cracks files to get their version numbers, arranges them into branding categories,
|
||||
# and publishes drafts based on their contents.
|
||||
|
||||
#Requires -Version 7.0
|
||||
#Requires -Modules PSGitHub
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
Param(
|
||||
[string[]]$Directory,
|
||||
[string]$Owner = "microsoft",
|
||||
[string]$RepositoryName = "terminal",
|
||||
[switch]$DumpOutput = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$PSDefaultParameterValues['*GitHub*:Owner'] = $Owner
|
||||
$PSDefaultParameterValues['*GitHub*:RepositoryName'] = $RepositoryName
|
||||
|
||||
Enum AssetType {
|
||||
Unknown
|
||||
ApplicationBundle
|
||||
PreinstallKit
|
||||
Zip
|
||||
}
|
||||
|
||||
Enum Branding {
|
||||
Unknown
|
||||
Release
|
||||
Preview
|
||||
Canary
|
||||
Dev
|
||||
}
|
||||
|
||||
$script:tar = "tar"
|
||||
|
||||
Class Asset {
|
||||
[string]$Name
|
||||
[Version]$Version
|
||||
[string]$ExpandedVersion
|
||||
|
||||
[AssetType]$Type
|
||||
[Branding]$Branding
|
||||
|
||||
[string]$Architecture
|
||||
|
||||
[string]$Path
|
||||
|
||||
[string]$VersionMoniker
|
||||
|
||||
Asset([string]$Path) {
|
||||
$this.Path = $Path;
|
||||
}
|
||||
|
||||
[void]Load() {
|
||||
$local:bundlePath = $this.Path
|
||||
|
||||
$local:sentinelFile = New-TemporaryFile -Confirm:$false -WhatIf:$false
|
||||
$local:directory = New-Item -Type Directory "$($local:sentinelFile.FullName)_Package" -Confirm:$false -WhatIf:$false
|
||||
Remove-Item $local:sentinelFile -Force -EA:Ignore -Confirm:$false -WhatIf:$false
|
||||
|
||||
$local:ext = [IO.Path]::GetExtension($this.Path)
|
||||
$local:filename = [IO.Path]::GetFileName($this.Path)
|
||||
If (".zip" -eq $local:ext -and $local:filename -like '*Preinstall*') {
|
||||
Write-Verbose "Cracking Preinstall Kit $($this.Path)"
|
||||
$local:licenseFile = & $script:tar -t -f $this.Path | Select-String "_License1.xml"
|
||||
If (-Not $local:licenseFile) {
|
||||
Throw ("$($this.Path) does not appear to be a preinstall kit")
|
||||
}
|
||||
$local:bundleName = ($local:licenseFile -Split "_")[0] + ".msixbundle"
|
||||
Write-Verbose "Found inner bundle $($local:bundleName)"
|
||||
& $script:tar -x -f $this.Path -C $local:directory $local:bundleName
|
||||
|
||||
$local:bundlePath = Join-Path $local:directory $local:bundleName
|
||||
$this.Type = [AssetType]::PreinstallKit
|
||||
$this.Architecture = "all"
|
||||
} ElseIf (".zip" -eq $local:ext) {
|
||||
$this.Type = [AssetType]::Zip
|
||||
} ElseIf (".msixbundle" -eq $local:ext) {
|
||||
$this.Type = [AssetType]::ApplicationBundle
|
||||
$this.Architecture = "all"
|
||||
}
|
||||
|
||||
If ($this.Type -Ne [AssetType]::Zip) {
|
||||
Write-Verbose "Cracking bundle $($local:bundlePath)"
|
||||
$local:firstMsixName = & $script:tar -t -f $local:bundlePath |
|
||||
Select-String 'Cascadia.*\.msix' |
|
||||
Select-Object -First 1 -Expand Line
|
||||
& $script:tar -x -f $local:bundlePath -C $local:directory $local:firstMsixName
|
||||
|
||||
Write-Verbose "Found inner msix $($local:firstMsixName)"
|
||||
$local:msixPath = Join-Path $local:directory $local:firstMsixName
|
||||
& $script:tar -x -v -f $local:msixPath -C $local:directory AppxManifest.xml
|
||||
|
||||
Write-Verbose "Parsing AppxManifest.xml"
|
||||
$local:Manifest = [xml](Get-Content (Join-Path $local:directory AppxManifest.xml))
|
||||
$this.ParseManifest($local:Manifest)
|
||||
} Else {
|
||||
& $script:tar -x -f $this.Path -C $local:directory --strip-components=1 '*/wt.exe'
|
||||
$this.ExpandedVersion = (Get-Item (Join-Path $local:directory wt.exe)).VersionInfo.ProductVersion
|
||||
|
||||
# Zip files just encode everything in their filename. Not great, but workable.
|
||||
$this.ParseFilename($local:filename)
|
||||
}
|
||||
|
||||
$v = [version]$this.Version
|
||||
$this.VersionMoniker = "{0}.{1}" -f ($v.Major, $v.Minor)
|
||||
|
||||
$this.Branding = Switch($this.Name) {
|
||||
"Microsoft.WindowsTerminal" { [Branding]::Release }
|
||||
"Microsoft.WindowsTerminalPreview" { [Branding]::Preview }
|
||||
"Microsoft.WindowsTerminalCanary" { [Branding]::Canary }
|
||||
"WindowsTerminalDev" { [Branding]::Dev }
|
||||
Default { [Branding]::Unknown }
|
||||
}
|
||||
}
|
||||
|
||||
[void]ParseManifest([xml]$Manifest) {
|
||||
$this.Name = $Manifest.Package.Identity.Name
|
||||
$this.Version = $Manifest.Package.Identity.Version
|
||||
}
|
||||
|
||||
[void]ParseFilename([string]$filename) {
|
||||
$parts = [IO.Path]::GetFileNameWithoutExtension($filename).Split("_")
|
||||
$this.Name = $parts[0]
|
||||
$this.Version = $parts[1]
|
||||
$this.Architecture = $parts[2]
|
||||
}
|
||||
|
||||
[string]IdealFilename() {
|
||||
$local:bundleName = "{0}_{1}_8wekyb3d8bbwe.msixbundle" -f ($this.Name, $this.Version)
|
||||
|
||||
$local:filename = Switch($this.Type) {
|
||||
PreinstallKit {
|
||||
"{0}_Windows10_PreinstallKit.zip" -f $local:bundleName
|
||||
}
|
||||
ApplicationBundle {
|
||||
$local:bundleName
|
||||
}
|
||||
Zip {
|
||||
"{0}_{1}_{2}.zip" -f ($this.Name, $this.Version, $this.Architecture)
|
||||
}
|
||||
Default {
|
||||
Throw "Unknown type $($_.Type)"
|
||||
}
|
||||
}
|
||||
return $local:filename
|
||||
}
|
||||
|
||||
static [Asset] CreateFromFile([string]$Path) {
|
||||
$a = [Asset]::new($Path)
|
||||
$a.Load()
|
||||
return $a
|
||||
}
|
||||
}
|
||||
|
||||
class Release {
|
||||
[string]$Name
|
||||
[Branding]$Branding
|
||||
[Version]$TagVersion
|
||||
[Version]$DisplayVersion
|
||||
[string]$Branch
|
||||
|
||||
[Asset[]]$Assets
|
||||
|
||||
Release([Asset[]]$a) {
|
||||
$this.Assets = $a
|
||||
$this.Branding = $a[0].Branding
|
||||
$this.Name = Switch($this.Branding) {
|
||||
Release { "Windows Terminal" }
|
||||
Preview { "Windows Terminal Preview" }
|
||||
Default { throw "Unknown Branding for release publication $_" }
|
||||
}
|
||||
$local:minVer = $a.Version | Measure -Minimum | Select-Object -Expand Minimum
|
||||
$this.TagVersion = $local:minVer
|
||||
$this.DisplayVersion = $local:minVer
|
||||
$this.Branch = "release-{0}.{1}" -f ($local:minVer.Major, $local:minVer.Minor)
|
||||
}
|
||||
|
||||
[string]TagName() {
|
||||
return "v{0}" -f $this.TagVersion
|
||||
}
|
||||
}
|
||||
|
||||
enum TaggingType {
|
||||
TagAlreadyExists
|
||||
TagBranchLatest
|
||||
TagCommitSha1
|
||||
}
|
||||
|
||||
class ReleaseConfig {
|
||||
[Release]$Release
|
||||
[TaggingType]$TaggingType
|
||||
[string]$TagTarget # may be null
|
||||
}
|
||||
|
||||
Function Read-ReleaseConfigFromHost([Release]$Release) {
|
||||
$choices = @(
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("No New &Tag", "Tag already exists, do not create a new tag"),
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("Tag &Branch"),
|
||||
[System.Management.Automation.Host.ChoiceDescription]::new("Tag &Commit")
|
||||
)
|
||||
|
||||
$existingTagSha1 = & git rev-parse --verify $Release.TagName() 2>$null
|
||||
If($existingTagSha1) {
|
||||
### If there is already a tag, trust the release engineer.
|
||||
Write-Verbose "Found existing tag $($Release.TagName())"
|
||||
return [ReleaseConfig]@{
|
||||
Release = $Release;
|
||||
TaggingType = [TaggingType]::TagAlreadyExists;
|
||||
TagTarget = $null;
|
||||
}
|
||||
}
|
||||
|
||||
$choice = $Host.UI.PromptForChoice("How should I tag $("{0} v{1}" -f ($Release.Name,$Release.DisplayVersion))?", $null, $choices, 0)
|
||||
$target = $null
|
||||
Switch($choice) {
|
||||
0 { }
|
||||
1 { $target = $Release.Branch }
|
||||
2 { $target = Read-Host "What commit corresponds to $($Release.DisplayVersion)?" }
|
||||
}
|
||||
|
||||
Return [ReleaseConfig]@{
|
||||
Release = $Release;
|
||||
TaggingType = [TaggingType]$choice;
|
||||
TagTarget = $target;
|
||||
}
|
||||
}
|
||||
|
||||
Function New-ReleaseBody([Release]$Release) {
|
||||
$zipAssetVersion = $Release.Assets.ExpandedVersion | ? { $_.Length -Gt 0 } | Select -First 1
|
||||
$body = "---`n`n"
|
||||
If (-Not [String]::IsNullOrEmpty($zipAssetVersion)) {
|
||||
$body += "_Binary files inside the unpackaged distribution archive bear the version number ``$zipAssetVersion``._`n`n"
|
||||
}
|
||||
$body += "### Asset Hashes`n`n";
|
||||
ForEach($a in $Release.Assets) {
|
||||
$body += "- {0}`n - SHA256 ``{1}```n" -f ($a.IdealFilename(), (Get-FileHash $a.Path -Algorithm SHA256 | Select-Object -Expand Hash))
|
||||
}
|
||||
Return $body
|
||||
}
|
||||
|
||||
# Collect Assets from $Directory, figure out what those assets are
|
||||
$Assets = Get-ChildItem $Directory -Recurse -Include *.msixbundle, *.zip | ForEach-Object {
|
||||
[Asset]::CreateFromFile($_.FullName)
|
||||
}
|
||||
|
||||
$Releases = $Assets | Group VersionMoniker | ForEach-Object {
|
||||
[Release]::new($_.Group)
|
||||
}
|
||||
|
||||
$ReleaseConfigs = $Releases | % { Read-ReleaseConfigFromHost $_ }
|
||||
|
||||
If($DumpOutput) {
|
||||
$ReleaseConfigs
|
||||
Return
|
||||
}
|
||||
|
||||
$sentinelFile = New-TemporaryFile -Confirm:$false -WhatIf:$false
|
||||
$directory = New-Item -Type Directory "$($sentinelFile.FullName)_PackageUploads" -Confirm:$false -WhatIf:$false
|
||||
Remove-Item $sentinelFile -Force -EA:Ignore -Confirm:$false -WhatIf:$false
|
||||
|
||||
ForEach($c in $ReleaseConfigs) {
|
||||
$releaseName = "{0} v{1}" -f ($c.Release.Name, $c.Release.DisplayVersion)
|
||||
if($PSCmdlet.ShouldProcess("Release $releaseName", "Publish")) {
|
||||
Write-Verbose "Preparing release $releaseName"
|
||||
|
||||
$releaseParams = @{
|
||||
TagName = $c.Release.TagName();
|
||||
Name = $releaseName;
|
||||
}
|
||||
|
||||
If($c.TagTarget) {
|
||||
Switch($c.TaggingType) {
|
||||
[TagCommitSha1] { $releaseParams.CommitSHA = $c.TagTarget }
|
||||
[TagBranchLatest] { $releaseParams.Branch = $c.Release.Branch }
|
||||
}
|
||||
Write-Verbose " - Will create tag $($c.Release.TagName()) to point to $($c.TagTarget)"
|
||||
} Else {
|
||||
Write-Verbose " - Tag already exists"
|
||||
}
|
||||
|
||||
$releaseParams.PreRelease = Switch($c.Release.Branding) {
|
||||
Release { $false }
|
||||
Preview { $true }
|
||||
}
|
||||
|
||||
$releaseParams.Body = New-ReleaseBody $c.Release
|
||||
|
||||
$GitHubRelease = New-GitHubRelease @releaseParams -Draft:$true
|
||||
|
||||
ForEach($a in $c.Release.Assets) {
|
||||
$idealPath = (Join-Path $directory $a.IdealFilename())
|
||||
Copy-Item $a.Path $idealPath -Confirm:$false
|
||||
Write-Verbose "Uploading $idealPath to Release $($GitHubRelease.id)"
|
||||
New-GitHubReleaseAsset -ReleaseId $GitHubRelease.id -Path $idealPath
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user