param([string]$Version, [switch]$RebuildWeb) $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'build-lock.ps1') $buildLock = Enter-LinkPortBuildLock $previousPath = $env:PATH try { $totalTimer = [Diagnostics.Stopwatch]::StartNew() $phaseTimer = [Diagnostics.Stopwatch]::StartNew() function Write-PhaseTime([string]$Name) { Write-Host ('{0}: {1:N1}s' -f $Name, $phaseTimer.Elapsed.TotalSeconds) $phaseTimer.Restart() } . (Join-Path $PSScriptRoot 'version.ps1') $buildInfo = Get-linkportBuildInfo -Version $Version $Version = $buildInfo.Version $repo = Split-Path $PSScriptRoot -Parent . (Join-Path $PSScriptRoot 'go-toolchain.ps1') $goExecutable = Get-LinkPortGoExecutable -ProjectRoot $repo . (Join-Path $PSScriptRoot 'node-toolchain.ps1') $nodeExecutable = Initialize-LinkPortNodeRuntime -ProjectRoot $repo $stamp = (Get-Date -Format 'yyyyMMdd-HHmmss-fff') + '-' + [Guid]::NewGuid().ToString('N').Substring(0, 8) $releaseRoot = Join-Path $repo "bin/releases/$stamp" $stage = [IO.Path]::GetFullPath((Join-Path $repo "bin/build-cache/package-$stamp")) $binary = Join-Path $stage 'linkport.exe' $releaseBinary = Join-Path $releaseRoot 'linkport.exe' $previous = @{} foreach ($key in @('GOTOOLCHAIN','GO111MODULE','GOOS','GOARCH','CGO_ENABLED','LINKPORT_NO_BROWSER')) { $previous[$key] = [Environment]::GetEnvironmentVariable($key, 'Process') } $keepPath = Join-Path $repo 'web/dist/.gitkeep' $hadKeep = Test-Path -LiteralPath $keepPath $keepBytes = [byte[]]@() if ($hadKeep) { $keepBytes = [IO.File]::ReadAllBytes($keepPath) } Push-Location -LiteralPath $repo try { $env:GOTOOLCHAIN = 'go1.27.1' $env:GO111MODULE = 'on' $env:LINKPORT_NO_BROWSER = '1' $env:GOOS = 'windows' $env:GOARCH = 'amd64' $env:CGO_ENABLED = '0' $webArguments = @((Join-Path $PSScriptRoot 'build-web.mjs')) if ($RebuildWeb) { $webArguments += '--force' } & $nodeExecutable @webArguments if ($LASTEXITCODE -ne 0) { throw 'Frontend build failed' } Write-PhaseTime 'Frontend and dependencies' New-Item -ItemType Directory -Path $stage | Out-Null & $goExecutable build -trimpath -ldflags $buildInfo.LDFlags -o $binary ./cmd/linkport if ($LASTEXITCODE -ne 0) { throw 'linkport release build failed' } Write-PhaseTime 'Go build' & $nodeExecutable (Join-Path $PSScriptRoot 'verify-package.mjs') $stage if ($LASTEXITCODE -ne 0) { throw 'Package verification failed' } Write-PhaseTime 'Executable verification' # Publish only after verification. The generated paths must stay in this repository. $cacheRoot = [IO.Path]::GetFullPath((Join-Path $repo 'bin/build-cache')) + [IO.Path]::DirectorySeparatorChar $releasesRoot = [IO.Path]::GetFullPath((Join-Path $repo 'bin/releases')) $releaseRoot = [IO.Path]::GetFullPath($releaseRoot) if (!$stage.StartsWith($cacheRoot, [StringComparison]::OrdinalIgnoreCase) -or !(($releaseRoot + [IO.Path]::DirectorySeparatorChar).StartsWith($releasesRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase))) { throw 'Unexpected package output path' } $digest = (Get-FileHash -LiteralPath $binary -Algorithm SHA256).Hash.ToLowerInvariant() New-Item -ItemType Directory -Path $releasesRoot -Force | Out-Null if (Test-Path -LiteralPath $releaseRoot) { throw 'Release directory already exists' } Move-Item -LiteralPath $stage -Destination $releaseRoot Write-Output $releaseBinary Write-Output "SHA256: $digest" Write-PhaseTime 'Single EXE output' Write-Host ('Total: {0:N1}s' -f $totalTimer.Elapsed.TotalSeconds) } finally { if ($hadKeep) { New-Item -ItemType Directory -Path (Split-Path $keepPath -Parent) -Force | Out-Null [IO.File]::WriteAllBytes($keepPath, [byte[]]$keepBytes) } foreach ($key in $previous.Keys) { [Environment]::SetEnvironmentVariable($key, $previous[$key], 'Process') } Pop-Location } } finally { $env:PATH = $previousPath $buildLock.Dispose() }