Files
Palash DebnathandClaude Sonnet 5.5 97ee973c61 feat(ci): package Windows on ARM64 (native shell, x64 Python runtime)
Adds a windows-11-arm leg to the packaging rehearsal and the release matrix
(experimental: a failed leg does not block the four established targets; the
release-asset check verifies the arm64 feed in full whenever any trace of it is
published). install.ps1 installs the native ARM64 build and falls back to x64
under emulation. README and the Windows/script docs gain a hardware table, CPU-only
and Windows-on-ARM guidance, and the external-drive recipe for #2436.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 19:06:04 +05:30

145 lines
8.7 KiB
PowerShell

# VoiceStudio Electron: latest, a release version, or build main.
param([string]$Version = $env:VOICESTUDIO_VERSION, [switch]$Main, [switch]$Source, [switch]$Uninstall, [switch]$Help, [switch]$Silent)
$ErrorActionPreference = 'Stop'
$vsVersion = $Version
if ($Help) {
Write-Output @'
VoiceStudio Electron installer (Windows x64 and ARM64)
irm https://voicestudio.sh/install | iex
$env:VOICESTUDIO_VERSION='X.Y.Z'; irm https://voicestudio.sh/install | iex
$env:VOICESTUDIO_INSTALL_MODE='main'; irm https://voicestudio.sh/install | iex
File usage: .\install.ps1 [-Version X.Y.Z] [-Main | -Source] [-Silent]
Uninstall: .\install.ps1 -Uninstall (preserves user data)
$env:VOICESTUDIO_INSTALL_MODE='uninstall'; irm https://voicestudio.sh/install.ps1 | iex
Main requires Git, Node.js 22+, Bun, Rust/Cargo, and MSVC build tools.
Only Electron releases are supported. Quit the app first; user data is preserved.
'@
return
}
if ([Environment]::OSVersion.Platform -ne 'Win32NT') { throw 'Use install.sh on macOS or Linux.' }
# ARM64 hardware (Snapdragon X etc.) reports ARM64 natively, or through
# PROCESSOR_ARCHITEW6432 when this PowerShell is itself an emulated x64 process.
$onArm = $env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64'
if (-not $onArm -and $env:PROCESSOR_ARCHITECTURE -ne 'AMD64' -and $env:PROCESSOR_ARCHITEW6432 -ne 'AMD64') { throw 'Windows x64 or ARM64 is required.' }
$mode = if ($Main -or $Source) { 'main' } elseif ($env:VOICESTUDIO_INSTALL_MODE) { $env:VOICESTUDIO_INSTALL_MODE } else { 'binary' }
if ($mode -eq 'source') { $mode = 'main' }
if ($Uninstall) { $mode = 'uninstall' }
if ($mode -eq 'uninstall') {
if ($Main -or $Source -or $vsVersion) { throw 'Uninstall cannot be combined with installation options; clear VOICESTUDIO_VERSION first.' }
$keys = @('HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
$entries = @(Get-ItemProperty $keys -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match '^VoiceStudio(?: \d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?)?$' })
foreach ($entry in $entries) {
$command = [string]$entry.UninstallString
if ($command -notmatch '^"([^"]+\.exe)"(?:\s+(/currentuser|/allusers))?\s*$') { continue }
$exe = $Matches[1]
$scope = $Matches[2]
if ((Split-Path $exe -Leaf) -ne 'Uninstall VoiceStudio.exe' -or -not (Test-Path -LiteralPath $exe -PathType Leaf)) { continue }
$launch = @{ FilePath = $exe; Wait = $true; PassThru = $true }
$arguments = @()
if ($scope) { $arguments += $scope }
if ($Silent) { $arguments += '/S' }
if ($arguments.Count) { $launch.ArgumentList = $arguments }
$process = Start-Process @launch
if ($process.ExitCode -notin @(0, 3010)) { throw "Uninstall failed or cancelled (exit $($process.ExitCode))." }
break
}
Write-Output 'Uninstall complete (or app was not installed). User data is preserved.'
return
}
if ($mode -notin @('binary', 'main')) { throw 'Install mode must be binary, main, source, or uninstall.' }
if ($mode -eq 'main' -and $vsVersion) { throw 'Main cannot be combined with Version.' }
function Assert-Version([string]$Value) {
if ($Value -notmatch '^\d+\.\d+\.\d+(-[A-Za-z0-9]+([.-][A-Za-z0-9]+)*)?$') { throw "Invalid version: $Value" }
}
function Run-Checked([string]$Command, [string[]]$Arguments) {
& $Command @Arguments
if ($LASTEXITCODE -ne 0) { throw "$Command failed (exit $LASTEXITCODE)." }
}
if ($vsVersion) { $vsVersion = $vsVersion -replace '^v', ''; Assert-Version $vsVersion }
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$work = Join-Path ([IO.Path]::GetTempPath()) ('voicestudio-install-' + [Guid]::NewGuid())
New-Item -ItemType Directory -Path $work | Out-Null
try {
if ($mode -eq 'main') {
foreach ($tool in @('git', 'node', 'bun', 'cargo')) {
if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "Main builds require $tool; see docs/install/script.md." }
}
Run-Checked node @('-e', 'if (Number(process.versions.node.split(".")[0]) < 22) process.exit(1)')
$checkout = Join-Path $work 'source'
Run-Checked git @('clone', '--depth', '1', '--branch', 'main', '--single-branch', 'https://github.com/debpalash/VoiceStudio.git', $checkout)
Push-Location $checkout
try {
Run-Checked git @('rev-parse', 'HEAD')
Run-Checked bun @('install', '--frozen-lockfile')
Push-Location 'electron'
try {
Run-Checked bun @('run', 'build')
Run-Checked bun @('run', 'build:web')
Run-Checked node @('tests/packaging-contract.mjs')
Run-Checked bun @('run', 'electron-builder', '--config', 'electron-builder.config.mjs', '--publish', 'never', '--win', $(if ($onArm) { '--arm64' } else { '--x64' }))
Run-Checked node @('tests/update-package-contract.mjs')
} finally { Pop-Location }
} finally { Pop-Location }
$vsVersion = (Get-Content (Join-Path $checkout 'package.json') -Raw | ConvertFrom-Json).version
Assert-Version $vsVersion
$packageDir = Join-Path $checkout 'electron/release'
} else {
if (-not $vsVersion) {
try {
$release = Invoke-RestMethod 'https://api.github.com/repos/debpalash/VoiceStudio/releases/latest'
$vsVersion = $release.tag_name -replace '^v', ''
} catch {
$response = Invoke-WebRequest -UseBasicParsing 'https://github.com/debpalash/VoiceStudio/releases/latest'
# HttpWebResponse on Windows PowerShell 5.1, HttpResponseMessage on 7.
$uri = if ($response.BaseResponse.ResponseUri) { $response.BaseResponse.ResponseUri } else { $response.BaseResponse.RequestMessage.RequestUri }
if ([string]$uri -notmatch '^https://github\.com/debpalash/VoiceStudio/releases/tag/v([^/?#]+)$') { throw 'Could not resolve the latest release tag.' }
$vsVersion = $Matches[1]
}
Assert-Version $vsVersion
}
$packageDir = $work
}
$asset = "VoiceStudio-Electron-$vsVersion-win-x64.exe"
if ($onArm -and $mode -eq 'main') { $asset = "VoiceStudio-Electron-$vsVersion-win-arm64.exe" }
$package = Join-Path $packageDir $asset
if ($mode -eq 'binary') {
$base = "https://github.com/debpalash/VoiceStudio/releases/download/v$vsVersion"
$downloaded = $false
if ($onArm) {
# Prefer the native ARM64 shell; releases that predate it (or whose
# ARM64 leg did not publish) fall back to the x64 build, which runs
# under Windows' emulation layer.
$armAsset = "VoiceStudio-Electron-$vsVersion-win-arm64.exe"
$armPackage = Join-Path $packageDir $armAsset
try {
Write-Output "Downloading $armAsset"
Invoke-WebRequest -UseBasicParsing "$base/$armAsset" -OutFile $armPackage
$asset = $armAsset; $package = $armPackage; $downloaded = $true
} catch {
Write-Output 'No native ARM64 build for this release; using the x64 build under emulation.'
}
}
if (-not $downloaded) {
Write-Output "Downloading $asset"
Invoke-WebRequest -UseBasicParsing "$base/$asset" -OutFile $package
}
$sums = Join-Path $work 'SHA256SUMS.txt'
Invoke-WebRequest -UseBasicParsing "$base/SHA256SUMS.txt" -OutFile $sums
$pattern = '^([A-Fa-f0-9]{64})\s+' + [regex]::Escape($asset) + '$'
$entries = @(Get-Content $sums | Where-Object { $_ -match $pattern })
if ($entries.Count -ne 1) { throw 'Missing, duplicate, or invalid checksum entry; refusing to install.' }
$expected = ($entries[0] -split '\s+')[0]
if ((Get-FileHash $package -Algorithm SHA256).Hash -ne $expected) { throw 'Checksum mismatch; refusing to install.' }
Write-Output 'Checksum verified.'
}
if (-not (Test-Path $package -PathType Leaf)) { throw "Expected Electron package missing: $package" }
Write-Output 'Opening Electron setup. Existing settings and models are preserved.'
$launch = @{ FilePath = $package; Wait = $true; PassThru = $true }
if ($Silent) { $launch.ArgumentList = @('/S') }
$process = Start-Process @launch
if ($process.ExitCode -notin @(0, 3010)) { throw "Setup failed or was cancelled (exit $($process.ExitCode))." }
Write-Output 'Electron installed. Open VoiceStudio to configure the backend and choose models.'
} finally {
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue
}