178 lines
10 KiB
PowerShell
178 lines
10 KiB
PowerShell
param(
|
|
[string]$Remote = "git@gitea.itworx.tech:Jens/ForgeFlow.git",
|
|
[string]$Branch = "main",
|
|
[string]$InstalledSource = "C:\Projects\ForgeFlow",
|
|
[string]$UserDataPath = "",
|
|
[switch]$SkipBinaryRelease
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$source = $PSScriptRoot
|
|
$manifestPath = Join-Path $source "package.json"
|
|
if (-not (Test-Path -LiteralPath $manifestPath)) { throw "package.json was not found beside the publishing script." }
|
|
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
|
if ($manifest.name -ne "forgeflow") { throw "Run this script from an extracted ForgeFlow source release." }
|
|
$version = [string]$manifest.version
|
|
$temp = Join-Path ([IO.Path]::GetTempPath()) ("forgeflow-publish-" + [guid]::NewGuid().ToString("N"))
|
|
$clone = Join-Path $temp "ForgeFlow"
|
|
$publishedCommit = $null
|
|
|
|
try {
|
|
Write-Host "Validating ForgeFlow $version before publishing..." -ForegroundColor Cyan
|
|
Push-Location $source
|
|
try {
|
|
& cmd.exe /d /s /c "npm install --no-audit --no-fund"
|
|
if ($LASTEXITCODE -ne 0) { throw "npm install failed." }
|
|
if (-not (Test-Path -LiteralPath (Join-Path $source "package-lock.json"))) { throw "npm install did not create package-lock.json; publication was stopped to avoid a non-reproducible update." }
|
|
& cmd.exe /d /s /c "npm run check"
|
|
if ($LASTEXITCODE -ne 0) { throw "ForgeFlow quality gate failed." }
|
|
} finally { Pop-Location }
|
|
|
|
New-Item -ItemType Directory -Force -Path $temp | Out-Null
|
|
Write-Host "Cloning $Remote..." -ForegroundColor Cyan
|
|
& git clone --branch $Branch --single-branch $Remote $clone
|
|
if ($LASTEXITCODE -ne 0) { throw "Could not clone the ForgeFlow update repository." }
|
|
|
|
& robocopy.exe $source $clone /MIR /R:2 /W:1 /NFL /NDL /NJH /NJS /NP /XD .git node_modules dist /XF *.zip *.sha256
|
|
if ($LASTEXITCODE -gt 7) { throw "Robocopy failed with exit code $LASTEXITCODE." }
|
|
|
|
Push-Location $clone
|
|
try {
|
|
& git add -A
|
|
if ($LASTEXITCODE -ne 0) { throw "Could not stage the release source." }
|
|
$changes = @(& git status --porcelain)
|
|
if ($changes.Count -gt 0) {
|
|
& git commit -m "Release ForgeFlow $version"
|
|
if ($LASTEXITCODE -ne 0) { throw "Could not create the release commit." }
|
|
& git push origin $Branch
|
|
if ($LASTEXITCODE -ne 0) { throw "Could not push ForgeFlow $version to Gitea." }
|
|
} else {
|
|
Write-Host "Gitea already contains the ForgeFlow $version source; verifying the branch head." -ForegroundColor Yellow
|
|
}
|
|
|
|
$localCommit = (& git rev-parse HEAD).Trim()
|
|
if ($LASTEXITCODE -ne 0 -or $localCommit -notmatch '^[0-9a-f]{40}$') { throw "Could not read the local release commit." }
|
|
$remoteLines = @(& git ls-remote origin "refs/heads/$Branch")
|
|
if ($LASTEXITCODE -ne 0 -or $remoteLines.Count -lt 1) { throw "Could not verify the Gitea release branch." }
|
|
$publishedCommit = ($remoteLines[0] -split "`t")[0].Trim()
|
|
if ($publishedCommit -ne $localCommit) { throw "Gitea did not report the exact release commit after publication." }
|
|
} finally { Pop-Location }
|
|
|
|
Write-Host "ForgeFlow $version is available on Gitea at commit $($publishedCommit.Substring(0,7))." -ForegroundColor Green
|
|
|
|
if (-not $SkipBinaryRelease) {
|
|
Write-Host "Building and publishing the matching Windows installer and portable release..." -ForegroundColor Cyan
|
|
Push-Location $clone
|
|
try {
|
|
& cmd.exe /d /s /c "npm ci --no-audit --no-fund"
|
|
if ($LASTEXITCODE -ne 0) { throw "npm ci failed in the exact published checkout." }
|
|
& cmd.exe /d /s /c "npm run check"
|
|
if ($LASTEXITCODE -ne 0) { throw "The exact published checkout failed the release quality gate." }
|
|
& cmd.exe /d /s /c "npm run dist:win"
|
|
if ($LASTEXITCODE -ne 0) { throw "The Windows release build failed." }
|
|
|
|
$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) {
|
|
if (-not (Test-Path -LiteralPath (Join-Path $clone "dist\$assetName"))) {
|
|
throw "The Windows build did not produce $assetName."
|
|
}
|
|
}
|
|
|
|
$resolvedUserData = if ($UserDataPath) { $UserDataPath } else { Join-Path $env:APPDATA "forgeflow" }
|
|
$previousUserData = $env:FORGEFLOW_USER_DATA
|
|
$previousBranch = $env:FORGEFLOW_RELEASE_BRANCH
|
|
try {
|
|
$env:FORGEFLOW_USER_DATA = $resolvedUserData
|
|
$env:FORGEFLOW_RELEASE_BRANCH = $Branch
|
|
& cmd.exe /d /s /c "npm run release:binary"
|
|
if ($LASTEXITCODE -ne 0) { throw "The Gitea binary release publisher failed." }
|
|
} finally {
|
|
$env:FORGEFLOW_USER_DATA = $previousUserData
|
|
$env:FORGEFLOW_RELEASE_BRANCH = $previousBranch
|
|
}
|
|
} finally { Pop-Location }
|
|
Write-Host "ForgeFlow $version source and Windows release assets are both published." -ForegroundColor Green
|
|
} else {
|
|
Write-Host "Binary publication was skipped explicitly. Packaged ForgeFlow installations cannot auto-update until the release assets are published." -ForegroundColor Yellow
|
|
}
|
|
|
|
$installedManifestPath = Join-Path $InstalledSource "package.json"
|
|
if (Test-Path -LiteralPath $installedManifestPath) {
|
|
try {
|
|
$installedManifest = Get-Content -LiteralPath $installedManifestPath -Raw | ConvertFrom-Json
|
|
if ($installedManifest.name -eq "forgeflow" -and [string]$installedManifest.version -ne $version) {
|
|
$helperSource = Join-Path $source "scripts\apply-source-update.ps1"
|
|
$helperTarget = Join-Path $InstalledSource "scripts\apply-source-update.ps1"
|
|
$serviceSource = Join-Path $source "src\main\update-service.cjs"
|
|
$serviceTarget = Join-Path $InstalledSource "src\main\update-service.cjs"
|
|
|
|
$helperText = Get-Content -LiteralPath $helperSource -Raw
|
|
$serviceText = Get-Content -LiteralPath $serviceSource -Raw
|
|
if ($helperText.TrimStart() -notmatch '^param\(') { throw "The validated updater helper does not start with param(." }
|
|
if ($helperText -notmatch 'System\.IO\.File\]::Replace' -or $helperText -notmatch 'HandshakeOnly') { throw "The validated updater helper is missing the Windows status replacement fix." }
|
|
if ($serviceText -notmatch 'expectedUpdateId' -or $serviceText -notmatch 'readLogTail') { throw "The validated update service is missing the confirmed-handshake diagnostics." }
|
|
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $helperTarget) | Out-Null
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $serviceTarget) | Out-Null
|
|
Copy-Item -LiteralPath $helperSource -Destination $helperTarget -Force
|
|
Copy-Item -LiteralPath $serviceSource -Destination $serviceTarget -Force
|
|
|
|
$copiedHelper = Get-Content -LiteralPath $helperTarget -Raw
|
|
$copiedService = Get-Content -LiteralPath $serviceTarget -Raw
|
|
if ($copiedHelper.TrimStart() -notmatch '^param\(' -or $copiedHelper -notmatch 'System\.IO\.File\]::Replace') { throw "The updater helper bootstrap copy failed validation." }
|
|
if ($copiedService -notmatch 'expectedUpdateId' -or $copiedService -notmatch 'readLogTail') { throw "The update-service bootstrap copy failed validation." }
|
|
|
|
# Prove the exact STARTED handshake on this Windows PowerShell version before
|
|
# asking the installed ForgeFlow to close itself for a real update.
|
|
$handshakeRoot = Join-Path ([IO.Path]::GetTempPath()) ("forgeflow-handshake-" + [guid]::NewGuid().ToString("N"))
|
|
New-Item -ItemType Directory -Force -Path $handshakeRoot | Out-Null
|
|
try {
|
|
$handshakeId = "publisher-" + [guid]::NewGuid().ToString("N")
|
|
$handshakeStatus = Join-Path $handshakeRoot "status.json"
|
|
$handshakeLog = Join-Path $handshakeRoot "helper.log"
|
|
$dummyArchive = Join-Path $handshakeRoot "unused.zip"
|
|
$launching = @{ schemaVersion = 1; updateId = $handshakeId; state = "launching" } | ConvertTo-Json
|
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
[System.IO.File]::WriteAllText($handshakeStatus, $launching, $utf8NoBom)
|
|
[System.IO.File]::WriteAllBytes($dummyArchive, [byte[]](0x50,0x4b,0x03,0x04))
|
|
|
|
$windowsRoot = if ($env:SystemRoot) { $env:SystemRoot } else { $env:WINDIR }
|
|
$powershellExe = if ($windowsRoot) { Join-Path $windowsRoot "System32\WindowsPowerShell\v1.0\powershell.exe" } else { "powershell.exe" }
|
|
& $powershellExe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $helperTarget `
|
|
-SourcePath $InstalledSource -ArchivePath $dummyArchive -ExpectedVersion $version `
|
|
-ExpectedSha256 ("0" * 64) -ParentPid 2147483647 -LogPath $handshakeLog `
|
|
-StatusPath $handshakeStatus -UpdateId $handshakeId -HandshakeOnly
|
|
if ($LASTEXITCODE -ne 0) { throw "The installed update helper failed its Windows handshake self-test with exit code $LASTEXITCODE." }
|
|
$handshakeResult = Get-Content -LiteralPath $handshakeStatus -Raw | ConvertFrom-Json
|
|
if ($handshakeResult.state -ne "started" -or $handshakeResult.updateId -ne $handshakeId) {
|
|
$tail = if (Test-Path -LiteralPath $handshakeLog) { Get-Content -LiteralPath $handshakeLog -Tail 20 | Out-String } else { "No helper log was created." }
|
|
throw "The installed update helper did not replace the launching state with the expected STARTED marker. $tail"
|
|
}
|
|
} finally {
|
|
Remove-Item -LiteralPath $handshakeRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Write-Host "Prepared and Windows-tested the installed ForgeFlow $($installedManifest.version) updater bootstrap without changing its version." -ForegroundColor Green
|
|
}
|
|
} catch {
|
|
throw "Release was published, but the installed updater bootstrap could not be prepared: $($_.Exception.Message)"
|
|
}
|
|
} else {
|
|
Write-Host "Installed source was not found at $InstalledSource; publication itself succeeded." -ForegroundColor Yellow
|
|
}
|
|
|
|
if ($SkipBinaryRelease) {
|
|
Write-Host "Source publication completed. Run Publish-Missing-Binary-Release.ps1 before using the updater from an installed EXE." -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host "Open the installed older ForgeFlow and use Settings -> ForgeFlow updates -> Check now." -ForegroundColor Cyan
|
|
}
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $temp -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|