#requires -Version 5.1 [CmdletBinding()] param( [Parameter(Mandatory)] [ValidatePattern('^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$')] [string]$Version, [string]$Notes = "Decky $Version", [string]$ForgejoBaseUrl = 'https://git.elijahkuntz.com', [string]$Repository = 'Elijah/Decky', [string]$SigningKeyPath = "$env:USERPROFILE\.tauri\decky.key", [string]$SigningKeyPasswordPath = "$env:USERPROFILE\.tauri\decky.key.password", [switch]$SkipValidation, [switch]$SkipBuild ) $ErrorActionPreference = 'Stop' $repositoryRoot = Split-Path -Parent $PSScriptRoot $appRoot = Join-Path $repositoryRoot 'app' $tauriConfigPath = Join-Path $appRoot 'src-tauri\tauri.conf.json' $cargoManifestPath = Join-Path $appRoot 'src-tauri\Cargo.toml' $packagePath = Join-Path $appRoot 'package.json' $releaseDirectory = Join-Path $repositoryRoot "artifacts\release\v$Version" $packageVersion = (Get-Content -Raw -LiteralPath $packagePath | ConvertFrom-Json).version $tauriVersion = (Get-Content -Raw -LiteralPath $tauriConfigPath | ConvertFrom-Json).version $cargoVersion = [regex]::Match((Get-Content -Raw -LiteralPath $cargoManifestPath), '(?m)^version\s*=\s*"([^"]+)"').Groups[1].Value if (@($packageVersion, $tauriVersion, $cargoVersion) | Where-Object { $_ -ne $Version }) { throw "Version mismatch. package.json=$packageVersion, tauri.conf.json=$tauriVersion, Cargo.toml=$cargoVersion; expected $Version." } if (-not $env:TAURI_SIGNING_PRIVATE_KEY) { if (-not (Test-Path -LiteralPath $SigningKeyPath)) { throw "Updater signing key not found at $SigningKeyPath. Do not generate a replacement after shipping a release." } $env:TAURI_SIGNING_PRIVATE_KEY = $SigningKeyPath } if (-not $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD -and (Test-Path -LiteralPath $SigningKeyPasswordPath)) { $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = (Get-Content -Raw -LiteralPath $SigningKeyPasswordPath) } if (-not $SkipBuild) { Push-Location $appRoot try { if (-not $SkipValidation) { & npm.cmd ci if ($LASTEXITCODE) { throw 'npm ci failed.' } & npm.cmd run check if ($LASTEXITCODE) { throw 'Svelte validation failed.' } & npm.cmd test if ($LASTEXITCODE) { throw 'Tests failed.' } } & npm.cmd run tauri build if ($LASTEXITCODE) { throw 'Tauri release build failed.' } } finally { Pop-Location } } $nsisDirectory = Join-Path $appRoot 'src-tauri\target\release\bundle\nsis' $installer = Get-ChildItem -LiteralPath $nsisDirectory -Filter '*-setup.exe' | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1 if (-not $installer) { throw "No NSIS installer was found under $nsisDirectory." } $signaturePath = "$($installer.FullName).sig" if (-not (Test-Path -LiteralPath $signaturePath)) { throw "Updater signature was not generated: $signaturePath" } New-Item -ItemType Directory -Path $releaseDirectory -Force | Out-Null $publishedInstaller = Join-Path $releaseDirectory $installer.Name $publishedSignature = "$publishedInstaller.sig" Copy-Item -LiteralPath $installer.FullName -Destination $publishedInstaller -Force Copy-Item -LiteralPath $signaturePath -Destination $publishedSignature -Force $tag = "v$Version" $escapedInstallerName = [Uri]::EscapeDataString($installer.Name) $downloadUrl = "$($ForgejoBaseUrl.TrimEnd('/'))/$Repository/releases/download/$tag/$escapedInstallerName" $metadata = [ordered]@{ version = $Version notes = $Notes pub_date = [DateTimeOffset]::UtcNow.ToString('o') platforms = [ordered]@{ 'windows-x86_64' = [ordered]@{ signature = (Get-Content -Raw -LiteralPath $signaturePath).Trim() url = $downloadUrl } } } $latestJsonPath = Join-Path $releaseDirectory 'latest.json' $metadataJson = $metadata | ConvertTo-Json -Depth 6 [IO.File]::WriteAllText($latestJsonPath, $metadataJson, (New-Object Text.UTF8Encoding($false))) $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $publishedInstaller).Hash Write-Host "Release prepared: $releaseDirectory" Write-Host "Installer: $publishedInstaller" Write-Host "SHA256: $hash" Write-Host "Updater metadata: $latestJsonPath"