Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ jobs:
$key = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($env:APPCAST_SIGNATURE_KEY_BASE64))
Set-Content -Path $keyPath -Value $key

# Download the old appcast from GCS.
$oldAppCastPath = Join-Path $env:TEMP "appcast.old.xml"
& gsutil cp $env:APPCAST_GCS_URI $oldAppCastPath
if ($LASTEXITCODE -ne 0) { throw "Failed to download appcast" }

# Generate the new appcast and signature.
$newAppCastPath = Join-Path $env:TEMP "appcast.new.xml"
$newAppCastSignaturePath = $newAppCastPath + ".signature"
Expand All @@ -154,7 +149,6 @@ jobs:
-x64Path "${{ steps.release.outputs.X64_OUTPUT_PATH }}" `
-arm64Path "${{ steps.release.outputs.ARM64_OUTPUT_PATH }}" `
-keyPath $keyPath `
-inputAppCastPath $oldAppCastPath `
-outputAppCastPath $newAppCastPath `
-outputAppCastSignaturePath $newAppCastSignaturePath
if ($LASTEXITCODE -ne 0) { throw "Failed to generate new appcast" }
Expand Down
140 changes: 61 additions & 79 deletions scripts/Update-AppCast.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Updates appcast.xml and appcast.xml.signature for a given release.
# Generates appcast.xml and appcast.xml.signature for a given release.
#
# Requires openssl.exe. You can install it via winget:
# winget install ShiningLight.OpenSSL.Light
Expand All @@ -9,7 +9,6 @@
# -x64Path <path>
# -arm64Path <path>
# -keyPath <path>
# -inputAppCastPath <path>
# -outputAppCastPath <path>
# -outputAppCastSignaturePath <path>
param (
Expand Down Expand Up @@ -37,10 +36,6 @@ param (
[ValidateScript({ Test-Path $_ })]
[string] $keyPath,

[Parameter(Mandatory = $false)]
[ValidateScript({ Test-Path $_ })]
[string] $inputAppCastPath = "appcast.xml",

[Parameter(Mandatory = $false)]
[string] $outputAppCastPath = "appcast.xml",

Expand Down Expand Up @@ -104,86 +99,73 @@ Get-Content $releaseNotesHtmlPath
Write-Output "---- End of Release Notes HTML ----"
Write-Output ""

[xml] $appCast = Get-Content $inputAppCastPath

# Set up namespace manager for sparkle: prefix
$nsManager = New-Object System.Xml.XmlNamespaceManager($appCast.NameTable)
$nsManager.AddNamespace("sparkle", "http://www.andymatuschak.org/xml-namespaces/sparkle")
$sparkleNs = "http://www.andymatuschak.org/xml-namespaces/sparkle"
$releaseNotesHtml = [System.IO.File]::ReadAllText($releaseNotesHtmlPath)
Comment on lines -107 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just now noticing this...


# Find the matching channel item
$channelItem = $appCast.SelectSingleNode("//item[sparkle:channel='$channel']", $nsManager)
if ($null -eq $channelItem) {
throw "Could not find channel item for channel: $channel"
}

# Update the item properties
$channelItem.title = $tag
$channelItem.pubDate = $pubDate
$channelItem.SelectSingleNode("sparkle:version", $nsManager).InnerText = $version
$channelItem.SelectSingleNode("sparkle:shortVersionString", $nsManager).InnerText = $version
$channelItem.SelectSingleNode("sparkle:fullReleaseNotesLink", $nsManager).InnerText = "https://github.com/$repo/releases"

# Set description with proper line breaks
$descriptionNode = $channelItem.SelectSingleNode("description")
$descriptionNode.InnerXml = "" # Clear existing content
$cdata = $appCast.CreateCDataSection([System.IO.File]::ReadAllText($releaseNotesHtmlPath))
$descriptionNode.AppendChild($cdata) | Out-Null

# Remove existing enclosures
$existingEnclosures = $channelItem.SelectNodes("enclosure")
foreach ($enclosure in $existingEnclosures) {
$channelItem.RemoveChild($enclosure) | Out-Null
}

# Add new enclosures
$enclosures = @(
@{
path = $x64Path
os = "win-x64"
},
@{
path = $arm64Path
os = "win-arm64"
}
# NetSparkle only reads the first enclosure of each item, so every
# architecture gets its own item with a single enclosure.
$installers = @(
@{ path = $x64Path; os = "win-x64" },
@{ path = $arm64Path; os = "win-arm64" }
)
foreach ($enclosure in $enclosures) {
$fileName = Split-Path $enclosure.path -Leaf
$url = "https://github.com/$repo/releases/download/$tag/$fileName"
$fileSize = (Get-Item $enclosure.path).Length
$signature = Get-Ed25519Signature $enclosure.path

$newEnclosure = $appCast.CreateElement("enclosure")
$newEnclosure.SetAttribute("url", $url)
$newEnclosure.SetAttribute("type", "application/x-msdos-program")
$newEnclosure.SetAttribute("length", $fileSize)

# Set namespaced attributes
$sparkleNs = $nsManager.LookupNamespace("sparkle")
$attrs = @{
"os" = $enclosure.os
"version" = $version
"shortVersionString" = $version
"criticalUpdate" = "false"
"edSignature" = $signature # NetSparkle prefers edSignature over signature
}
foreach ($key in $attrs.Keys) {
$attr = $appCast.CreateAttribute("sparkle", $key, $sparkleNs)
$attr.Value = $attrs[$key]
$newEnclosure.Attributes.Append($attr) | Out-Null

# The appcast only lists the latest release, so it is generated from scratch.
# NetSparkle fails to parse CRLF line endings, so use LF.
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Indent = $true
$settings.IndentChars = " "
$settings.NewLineChars = "`n"
$settings.Encoding = New-Object System.Text.UTF8Encoding($false)
$writer = [System.Xml.XmlWriter]::Create($outputAppCastPath, $settings)
try {
$writer.WriteStartDocument($true)
$writer.WriteStartElement("rss")
$writer.WriteAttributeString("xmlns", "sparkle", $null, $sparkleNs)
$writer.WriteAttributeString("version", "2.0")
$writer.WriteStartElement("channel")
$writer.WriteElementString("title", "Coder Desktop")
$writer.WriteElementString("language", "en")

foreach ($installer in $installers) {
$fileName = Split-Path $installer.path -Leaf

$writer.WriteStartElement("item")
$writer.WriteElementString("title", $tag)
$writer.WriteStartElement("description")
$writer.WriteCData($releaseNotesHtml)
$writer.WriteEndElement()
$writer.WriteElementString("pubDate", $pubDate)
$writer.WriteElementString("channel", $sparkleNs, $channel)
$writer.WriteElementString("version", $sparkleNs, $version)
$writer.WriteElementString("shortVersionString", $sparkleNs, $version)
$writer.WriteElementString("fullReleaseNotesLink", $sparkleNs, "https://github.com/$repo/releases")

$writer.WriteStartElement("enclosure")
$writer.WriteAttributeString("url", "https://github.com/$repo/releases/download/$tag/$fileName")
$writer.WriteAttributeString("type", "application/x-msdos-program")
$writer.WriteAttributeString("length", (Get-Item $installer.path).Length)
$writer.WriteAttributeString("os", $sparkleNs, $installer.os)
$writer.WriteAttributeString("version", $sparkleNs, $version)
$writer.WriteAttributeString("shortVersionString", $sparkleNs, $version)
$writer.WriteAttributeString("criticalUpdate", $sparkleNs, "false")
# NetSparkle prefers edSignature over signature
$writer.WriteAttributeString("edSignature", $sparkleNs, (Get-Ed25519Signature $installer.path))
$writer.WriteEndElement()

$writer.WriteEndElement()
}

$channelItem.AppendChild($newEnclosure) | Out-Null
$writer.WriteEndElement()
$writer.WriteEndElement()
$writer.WriteEndDocument()
}
finally {
$writer.Close()
}

# Save the updated XML. Convert CRLF to LF since CRLF seems to break NetSparkle
$appCast.Save($outputAppCastPath)
$content = [System.IO.File]::ReadAllText($outputAppCastPath)
$content = $content -replace "`r`n", "`n"
[System.IO.File]::WriteAllText($outputAppCastPath, $content)

Write-Output "---- Updated appcast -----"
Write-Output "---- Generated appcast -----"
Get-Content $outputAppCastPath
Write-Output "---- End of updated appcast ----"
Write-Output "---- End of generated appcast ----"
Write-Output ""

# Generate the signature for the appcast itself
Expand Down
Loading