param( [string]$Remote = "git@gitea.itworx.tech:Jens/ForgeFlow.git", [string]$Branch = "main", [string]$ExpectedVersion = "0.9.1", [string]$UserDataPath = "" ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $temp = Join-Path ([IO.Path]::GetTempPath()) ("forgeflow-binary-release-" + [guid]::NewGuid().ToString("N")) $clone = Join-Path $temp "ForgeFlow" function Invoke-CheckedCommand { param( [Parameter(Mandatory = $true)][string]$Title, [Parameter(Mandatory = $true)][scriptblock]$Action ) Write-Host "`n$Title" -ForegroundColor Cyan & $Action if ($LASTEXITCODE -ne 0) { throw "$Title failed with exit code $LASTEXITCODE." } } try { foreach ($command in @("git", "node", "npm")) { if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { throw "Required command '$command' was not found on PATH." } } New-Item -ItemType Directory -Force -Path $temp | Out-Null Invoke-CheckedCommand "Cloning the exact published ForgeFlow source..." { & git clone --branch $Branch --single-branch $Remote $clone } $manifestPath = Join-Path $clone "package.json" if (-not (Test-Path -LiteralPath $manifestPath)) { throw "The cloned repository does not contain package.json." } $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json if ($manifest.name -ne "forgeflow") { throw "The cloned repository is not ForgeFlow." } $version = [string]$manifest.version if ($ExpectedVersion -and $version -ne $ExpectedVersion) { throw "Gitea branch '$Branch' contains ForgeFlow $version, not the expected $ExpectedVersion." } $localCommit = (& git -C $clone rev-parse HEAD).Trim() $remoteLines = @(& git -C $clone ls-remote origin "refs/heads/$Branch") if ($LASTEXITCODE -ne 0 -or $remoteLines.Count -lt 1) { throw "Could not verify origin/$Branch." } $remoteCommit = ($remoteLines[0] -split "`t")[0].Trim() if ($localCommit -ne $remoteCommit) { throw "The temporary checkout is not the current origin/$Branch commit." } Push-Location $clone try { Invoke-CheckedCommand "Installing exact dependencies..." { & cmd.exe /d /s /c "npm ci --no-audit --no-fund" } Invoke-CheckedCommand "Running the complete ForgeFlow quality gate..." { & cmd.exe /d /s /c "npm run check" } Invoke-CheckedCommand "Building installer and portable Windows assets..." { & cmd.exe /d /s /c "npm run dist:win" } $expectedAssets = @( "ForgeFlow-Setup-$version-win-x64.exe", "ForgeFlow-Setup-$version-win-x64.exe.sha256", "ForgeFlow-Portable-$version-win-x64.exe", "ForgeFlow-Portable-$version-win-x64.exe.sha256" ) foreach ($assetName in $expectedAssets) { $assetPath = Join-Path $clone "dist\$assetName" if (-not (Test-Path -LiteralPath $assetPath)) { throw "The build did not produce $assetName." } } $resolvedUserData = if ($UserDataPath) { $UserDataPath } else { Join-Path $env:APPDATA "forgeflow" } $configPath = Join-Path $resolvedUserData "forgeflow-config.json" if (-not (Test-Path -LiteralPath $configPath)) { throw "ForgeFlow configuration was not found at $configPath. Open ForgeFlow and sign in to Gitea once, then run this script again." } $previousUserData = $env:FORGEFLOW_USER_DATA $previousBranch = $env:FORGEFLOW_RELEASE_BRANCH try { $env:FORGEFLOW_USER_DATA = $resolvedUserData $env:FORGEFLOW_RELEASE_BRANCH = $Branch Invoke-CheckedCommand "Creating the Gitea release and uploading all four assets..." { & cmd.exe /d /s /c "npm run release:binary" } } finally { $env:FORGEFLOW_USER_DATA = $previousUserData $env:FORGEFLOW_RELEASE_BRANCH = $previousBranch } } finally { Pop-Location } Write-Host "`nForgeFlow $version now has a published binary release for commit $($localCommit.Substring(0,7))." -ForegroundColor Green Write-Host "Return to ForgeFlow $ExpectedVersion's predecessor and choose Check now -> Download update -> Apply & restart." -ForegroundColor Green } finally { Remove-Item -LiteralPath $temp -Recurse -Force -ErrorAction SilentlyContinue }