# ESP32-S3 Firmware Release Script param( [string]$Tag = "", [switch]$SkipBuild, [switch]$NoPush ) $ErrorActionPreference = "Stop" $ProjectRoot = Split-Path -Parent $PSScriptRoot $EimExe = "C:\Users\micha\AppData\Local\Microsoft\WinGet\Packages\Espressif.EIM-CLI_Microsoft.Winget.Source_8wekyb3d8bbwe\eim.exe" $BinName = "esp32s3_audio.bin" $BuildBin = Join-Path $ProjectRoot "build\$BinName" $ReleasesDir = Join-Path $ProjectRoot "releases" $CatalogFile = Join-Path $ReleasesDir "firmware-catalog.json" # -- Helpers (Using pure ASCII to prevent encoding parser issues) -- function Write-Step([string]$msg) { Write-Host "`n>> $msg" -ForegroundColor Cyan } function Write-Ok([string]$msg) { Write-Host " [OK] $msg" -ForegroundColor Green } function Write-Err([string]$msg) { Write-Host " [ERROR] $msg" -ForegroundColor Red exit 1 } # -- Step 1: Determine version -- Write-Step "Resolving version..." if ($Tag -eq "") { # Try exact tag on current commit first $exactTag = & { $oldEA = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $t = git tag --points-at HEAD 2>$null $ErrorActionPreference = $oldEA $t } | Select-Object -First 1 if ($exactTag) { $Tag = $exactTag.Trim() Write-Ok "Using exact commit tag: $Tag" } else { # Fall back to git describe (nearest tag + distance) $described = & { $oldEA = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $d = git describe --tags --long 2>$null $ErrorActionPreference = $oldEA $d } if ($described) { # If on a tag, described looks like "v1.0.0-0-gabcdef" -> strip -0-g... if ($described -match '^(.+)-0-g[0-9a-f]+$') { $Tag = $Matches[1] Write-Ok "On tagged commit: $Tag" } else { # Not on a clean tag - build a dev version $Tag = $described -replace '-(\d+)-g([0-9a-f]+)$', '+$1.$2' Write-Ok "Dev build version: $Tag" } } else { # No tags at all - generate from commit count $commitCount = & { $oldEA = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $c = git rev-list HEAD --count $ErrorActionPreference = $oldEA $c } if ($commitCount) { $commitCount = $commitCount.Trim() } else { $commitCount = "0" } $shortHash = & { $oldEA = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $h = git rev-parse --short HEAD $ErrorActionPreference = $oldEA $h } if ($shortHash) { $shortHash = $shortHash.Trim() } else { $shortHash = "unknown" } $Tag = "v0.0.$commitCount-$shortHash" Write-Ok "Auto-generated version (no tags): $Tag" } } } Write-Ok "Release version: $Tag" # -- Step 2: Build and Package DEV-1 and PROTO-1 -- # Detect original model from device-config file to restore later $deviceConfigFile = Join-Path $ProjectRoot "device-config" $originalModel = "DEV-1" if (Test-Path $deviceConfigFile) { $configLine = Get-Content $deviceConfigFile | Where-Object { $_ -match '^MODEL=' } if ($configLine) { $originalModel = ($configLine -split '=')[1].Trim() } } $SafeTag = $Tag -replace '[+/]', '-' if (-not (Test-Path $ReleasesDir)) { New-Item -ItemType Directory -Path $ReleasesDir | Out-Null } $DevBinSize = 0 $ProtoBinSize = 0 if (-not $SkipBuild) { # 1. Build and copy DEV-1 Write-Step "Building firmware for DEV-1 ($Tag)..." "MODEL=DEV-1" | Set-Content $deviceConfigFile Push-Location $ProjectRoot if (Test-Path "build") { Remove-Item -Path "build" -Force -Recurse -ErrorAction SilentlyContinue } & $EimExe run "idf.py build" if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Err "Build failed for DEV-1 (exit code $LASTEXITCODE)" } Pop-Location if (-not (Test-Path $BuildBin)) { Write-Err "Binary not found: $BuildBin" } $DevBinSize = (Get-Item $BuildBin).Length $DestDevBin = Join-Path $ReleasesDir "firmware-$SafeTag-dev1.bin" Copy-Item $BuildBin $DestDevBin -Force Write-Ok "DEV-1 release packaged -> releases/firmware-$SafeTag-dev1.bin" # 2. Build and copy PROTO-1 Write-Step "Building firmware for PROTO-1 ($Tag)..." "MODEL=PROTO-1" | Set-Content $deviceConfigFile Push-Location $ProjectRoot if (Test-Path "build") { Remove-Item -Path "build" -Force -Recurse -ErrorAction SilentlyContinue } & $EimExe run "idf.py build" if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Err "Build failed for PROTO-1 (exit code $LASTEXITCODE)" } Pop-Location if (-not (Test-Path $BuildBin)) { Write-Err "Binary not found: $BuildBin" } $ProtoBinSize = (Get-Item $BuildBin).Length $DestProtoBin = Join-Path $ReleasesDir "firmware-$SafeTag-proto1.bin" Copy-Item $BuildBin $DestProtoBin -Force Write-Ok "PROTO-1 release packaged -> releases/firmware-$SafeTag-proto1.bin" # Restore original configuration "MODEL=$originalModel" | Set-Content $deviceConfigFile Push-Location $ProjectRoot if (Test-Path "build") { Remove-Item -Path "build" -Force -Recurse -ErrorAction SilentlyContinue } Pop-Location } else { Write-Host " (skipping build - using existing output in build/)" -ForegroundColor Yellow if (-not (Test-Path $BuildBin)) { Write-Err "Binary not found in build/: $BuildBin" } $DevBinSize = (Get-Item $BuildBin).Length $ProtoBinSize = $DevBinSize Copy-Item $BuildBin (Join-Path $ReleasesDir "firmware-$SafeTag-dev1.bin") -Force Copy-Item $BuildBin (Join-Path $ReleasesDir "firmware-$SafeTag-proto1.bin") -Force Write-Ok "Copied build/esp32s3_audio.bin to both dev1 and proto1 release outputs." } # -- Step 5: Build changelog -- Write-Step "Generating changelog..." $prevTag = & { $oldEA = $ErrorActionPreference; $ErrorActionPreference = "SilentlyContinue" $t = git describe --tags --abbrev=0 HEAD~1 2>$null $ErrorActionPreference = $oldEA $t } if ($prevTag) { $logRange = "$($prevTag.Trim())..HEAD" } else { $logRange = "HEAD" } $gitLog = git log $logRange --pretty=format:"%s" 2>$null $changelog = @() if ($gitLog) { $changelog = ($gitLog -split "`n") | Where-Object { $_ -match '\S' } | ForEach-Object { $_.Trim() } } if ($changelog.Count -eq 0) { $changelog = @("No changes logged") } Write-Ok "$($changelog.Count) changelog entries" # -- Step 6: Read / update firmware-catalog.json -- Write-Step "Updating firmware-catalog.json..." if (Test-Path $CatalogFile) { $catalog = Get-Content $CatalogFile -Raw | ConvertFrom-Json $releases = [System.Collections.Generic.List[object]]@() if ($catalog.releases) { foreach ($item in $catalog.releases) { $releases.Add($item) } } } else { $releases = [System.Collections.Generic.List[object]]@() $catalog = [PSCustomObject]@{ latest = ""; releases = @() } } # Remove existing entries for this version if re-releasing $filteredReleases = [System.Collections.Generic.List[object]]::new() foreach ($r in $releases) { if ($r.version -ne $Tag) { $filteredReleases.Add($r) } } $releases = $filteredReleases # Prepend new entries (DEV-1 first, then PROTO-1) $newDevEntry = [PSCustomObject]@{ version = $Tag filename = "firmware-$SafeTag-dev1.bin" date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ") size = $DevBinSize model = "DEV-1" commitHash = (git rev-parse HEAD).Trim() changelog = $changelog } $newProtoEntry = [PSCustomObject]@{ version = $Tag filename = "firmware-$SafeTag-proto1.bin" date = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ") size = $ProtoBinSize model = "PROTO-1" commitHash = (git rev-parse HEAD).Trim() changelog = $changelog } $releases.Insert(0, $newDevEntry) $releases.Insert(1, $newProtoEntry) # Update catalog $catalog.latest = $Tag $catalog.releases = $releases.ToArray() $catalog | ConvertTo-Json -Depth 10 | Set-Content $CatalogFile -Encoding UTF8 Write-Ok "Catalog written: $CatalogFile" Write-Ok "Latest = $Tag | Total releases = $($releases.Count)" # -- Step 7: Git tag (only for clean semver tags) -- Write-Step "Tagging..." $isCleanTag = $Tag -match '^v\d+\.\d+\.\d+$' if ($isCleanTag) { $existingTag = git tag -l $Tag if (-not $existingTag) { git tag -a $Tag -m "Release $Tag" Write-Ok "Created annotated tag: $Tag" } else { Write-Host " Tag $Tag already exists - skipping" -ForegroundColor Yellow } } else { Write-Host " Dev version ($Tag) - skipping tag creation" -ForegroundColor Yellow } # -- Step 8: Commit catalog update -- Write-Step "Committing catalog..." git add (Join-Path $ReleasesDir "firmware-catalog.json") $hasChanges = git diff --quiet HEAD -- (Join-Path $ReleasesDir "firmware-catalog.json") if ($LASTEXITCODE -ne 0) { git commit -m "release: update firmware-catalog.json for $Tag" Write-Ok "Committed catalog update" } else { Write-Host " No catalog changes to commit" -ForegroundColor Yellow } # -- Step 9: Push -- if (-not $NoPush) { Write-Step "Pushing to origin..." git push origin master if ($isCleanTag) { git push origin $Tag } Write-Ok "Pushed" } else { Write-Host " (skipping push - -NoPush flag set)" -ForegroundColor Yellow } # -- Done -- Write-Host "" Write-Host "=======================================================" -ForegroundColor Magenta Write-Host " Release complete: $Tag" -ForegroundColor Green Write-Host " DEV-1 Bin : releases/firmware-$SafeTag-dev1.bin ($([math]::Round($DevBinSize/1024,1)) KB)" -ForegroundColor White Write-Host " PROTO-1 Bin: releases/firmware-$SafeTag-proto1.bin ($([math]::Round($ProtoBinSize/1024,1)) KB)" -ForegroundColor White Write-Host " Catalog: releases/firmware-catalog.json" -ForegroundColor White Write-Host "=======================================================" -ForegroundColor Magenta Write-Host ""