mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
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.
309 lines
9.3 KiB
PowerShell
309 lines
9.3 KiB
PowerShell
# 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
|
|
}
|
|
}
|
|
}
|