ek-ui 0.9.30

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
# package-installer.ps1 -- build the standalone desktop installer (2026-10-28 wave).
#
# Product ruling (user, 2026-10-28): installer only (no green zip); desktop connects
# by hand click; product name "ek-station 桌面"; headless daemon auto-connects flagged
# rows; headless write-once = `ekapp add-account` (password via stdin).
#
# What this does, in order:
#   1. preflight: ISCC present, dumpbin present, scaffolding patch OFF (the installer
#      must ship the *published* chain face -- with .cargo\config.toml present the
#      build resolves local directories, which is not what an installed user gets);
#   2. release builds:
#        ek-ui   --release --features ctp-native,bundled-font --bin ekapp
#        ek-station --release --features ctp-native --bin ek-station
#   3. staging: exes + the two CTP SDK DLLs (runtime import requirement of ekapp.exe,
#      from ek-ctp/native/sdk/win64) + license texts + first-run doc;
#   4. dependency audit: dumpbin /dependents ekapp.exe must list exactly the two CTP
#      DLLs (plus system DLLs) -- any path outside the staging dir is a STOP;
#   5. ISCC compiles packaging\ek-station-desktop.iss -> dist\ek-station-app-setup-<v>.exe;
#   6. SHA256 of the setup exe -> dist\SHA256.txt.
#
# Usage:
#   powershell -ExecutionPolicy Bypass -File scripts\package-installer.ps1
#   powershell -ExecutionPolicy Bypass -File scripts\package-installer.ps1 -SkipBuild
#   powershell -ExecutionPolicy Bypass -File scripts\package-installer.ps1 -Version 0.9.0
# NOTE: ASCII-only on purpose (Windows PowerShell 5.1 reads BOM-less .ps1 as ANSI).
[CmdletBinding()]
param(
    [switch]$SkipBuild,
    [string]$Version
)
$ErrorActionPreference = 'Stop'
# scripts -> ek-ui -> fuke (repo root). Two levels up; a third level lands on C:\dev.
$root = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$ui = Join-Path $root 'ek-ui'
$st = Join-Path $root 'ek-station'
$ctp = Join-Path $root 'ek-ctp'
$pack = Join-Path $ui 'packaging'
$stage = Join-Path $pack 'staging'
$dist = Join-Path $ui 'dist'
if (-not (Test-Path $dist)) { New-Item -ItemType Directory -Path $dist | Out-Null }

if (-not $Version) {
    $toml = Get-Content (Join-Path $ui 'Cargo.toml') -Raw -Encoding UTF8
    if ($toml -match '(?m)^version\s*=\s*"([^"]+)"') { $Version = $Matches[1] } else {
        Write-Host 'STOP: cannot read ek-ui version' -ForegroundColor Red; exit 2
    }
}
Write-Host ("== ek-station desktop packaging, version " + $Version + " ==") -ForegroundColor Cyan

# ---- 1. preflight ----
$iscc = @(
    'C:\Program Files (x86)\Inno Setup 6\ISCC.exe',
    'C:\Program Files\Inno Setup 6\ISCC.exe'
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $iscc) { Write-Host 'STOP: ISCC.exe not found (install Inno Setup 6)' -ForegroundColor Red; exit 2 }
$dumpbin = Get-ChildItem 'C:\Program Files\Microsoft Visual Studio','C:\Program Files (x86)\Microsoft Visual Studio' -Filter dumpbin.exe -Recurse -ErrorAction SilentlyContinue |
    Where-Object { $_.FullName -match 'Hostx64\\x64' } | Select-Object -First 1
if (-not $dumpbin) { Write-Host 'STOP: dumpbin.exe not found (MSVC Build Tools needed for the dependency audit)' -ForegroundColor Red; exit 2 }
$patch = Join-Path $root '.cargo\config.toml'
if (Test-Path $patch) {
    Write-Host ('STOP: local scaffolding patch is present: ' + $patch) -ForegroundColor Red
    Write-Host '       the installer must ship the published chain face. Move it away first:' -ForegroundColor Red
    Write-Host '         Rename-Item .cargo\config.toml config.toml.off' -ForegroundColor Yellow
    exit 2
}
Write-Host 'preflight OK (ISCC + dumpbin present, scaffolding patch off)' -ForegroundColor DarkGray

# ---- 2. release builds ----
if (-not $SkipBuild) {
    Push-Location $ui
    Write-Host 'build: ekapp (release, ctp-native + bundled-font)' -ForegroundColor Cyan
    & cargo build --release --offline --features ctp-native,bundled-font --bin ekapp
    if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Host 'STOP: ekapp build failed' -ForegroundColor Red; exit 1 }
    Pop-Location
    Push-Location $st
    Write-Host 'build: ek-station daemon (release, ctp-native)' -ForegroundColor Cyan
    & cargo build --release --offline --features ctp-native --bin ek-station
    if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Host 'STOP: ek-station daemon build failed' -ForegroundColor Red; exit 1 }
    Pop-Location
}

# ---- 3. staging ----
if (Test-Path $stage) { Remove-Item $stage -Recurse -Force }
New-Item -ItemType Directory -Path $stage | Out-Null
Copy-Item (Join-Path $ui 'target\release\ekapp.exe') $stage
Copy-Item (Join-Path $st 'target\release\ek-station.exe') $stage
Copy-Item (Join-Path $ctp 'native\sdk\win64\thosttraderapi_se.dll') $stage
Copy-Item (Join-Path $ctp 'native\sdk\win64\thostmduserapi_se.dll') $stage
Copy-Item (Join-Path $ui 'assets\fonts\OFL-NotoSansSC-LICENSE.txt') $stage
Copy-Item (Join-Path $ctp 'NOTICE') (Join-Path $stage 'NOTICE-ek-ctp.txt')
Copy-Item (Join-Path $pack 'first-run.md') $stage
# combined license screen: MIT (our crates; the text lives in ek-core/LICENSE.txt,
# every ek-* crate declares the same license = "MIT") + OFL (bundled font) + CTP notice
$mitPath = Join-Path $ui 'LICENSE.txt'
if (-not (Test-Path $mitPath)) { $mitPath = Join-Path $root 'ek-core\LICENSE.txt' }
if (-not (Test-Path $mitPath)) { Write-Host 'STOP: no MIT license text found (ek-ui/ek-core)' -ForegroundColor Red; exit 2 }
$mit = Get-Content $mitPath -Raw -Encoding UTF8
$ofl = Get-Content (Join-Path $stage 'OFL-NotoSansSC-LICENSE.txt') -Raw -Encoding UTF8
$ntc = Get-Content (Join-Path $stage 'NOTICE-ek-ctp.txt') -Raw -Encoding UTF8
$combined = "=== ek-ui / ek-station (MIT) ===`r`n`r`n" + $mit + "`r`n`r`n=== bundled font (SIL OFL 1.1) ===`r`n`r`n" + $ofl + "`r`n`r`n=== CTP SDK notice (CTP API binaries) ===`r`n`r`n" + $ntc
[System.IO.File]::WriteAllText((Join-Path $stage 'LICENSE-combined.txt'), $combined, (New-Object System.Text.UTF8Encoding($true)))
Write-Host 'staging ready' -ForegroundColor DarkGray

# ---- 4. dependency audit ----
$depLog = & $dumpbin.FullName /NOLOGO /dependents (Join-Path $stage 'ekapp.exe')
$deps = @($depLog | Where-Object { $_ -match '^\s+\S+\.(dll|DLL)' } | ForEach-Object { $_.Trim() })
Write-Host 'ekapp.exe direct imports:' -ForegroundColor Cyan
$deps | ForEach-Object { Write-Host ('   ' + $_) }
$want = @('thosttraderapi_se.dll', 'thostmduserapi_se.dll')
foreach ($w in $want) {
    if (-not ($deps | Where-Object { $_ -ieq $w })) {
        Write-Host ('STOP: expected import ' + $w + ' missing from ekapp.exe') -ForegroundColor Red
        exit 1
    }
}
$bads = @($deps | Where-Object { $_ -match '\\dev\\|\\target\\|thost.*\.lib$' })
if ($bads.Count -gt 0) {
    Write-Host ('STOP: import escapes the staging face: ' + ($bads -join ', ')) -ForegroundColor Red
    exit 1
}
$depLog | Set-Content (Join-Path $dist 'dumpbin-ekapp.txt') -Encoding UTF8 -ErrorAction SilentlyContinue
Write-Host 'dependency audit OK (both CTP DLLs imported; shipped next to the exe)' -ForegroundColor DarkGray

# ---- 5. ISCC ----
$env:EK_APP_VERSION = $Version
# ISCC writes chatter to stderr; under Stop, PowerShell 5.1 turns native stderr into a
# terminating error (the same trap check-package.ps1 documents). Toggle, judge by exit code.
$ErrorActionPreference = 'Continue'
& $iscc (Join-Path $pack 'ek-station-desktop.iss')
$isccCode = $LASTEXITCODE
$ErrorActionPreference = 'Stop'
if ($isccCode -ne 0) { Write-Host ('STOP: ISCC failed (exit ' + $isccCode + ')') -ForegroundColor Red; exit 1 }

# ---- 6. SHA256 ----
$setup = Join-Path $dist ("ek-station-app-setup-" + $Version + ".exe")
if (-not (Test-Path $setup)) { Write-Host ('STOP: setup exe not found: ' + $setup) -ForegroundColor Red; exit 1 }
$h = (Get-FileHash $setup -Algorithm SHA256).Hash.ToLower()
$sha = $h + '  ' + (Split-Path -Leaf $setup) + "`r`n"
Set-Content -Path (Join-Path $dist 'SHA256.txt') -Value $sha -Encoding UTF8
Write-Host ''
Write-Host ('INSTALLER  ' + $setup) -ForegroundColor Green
Write-Host ('SHA256     ' + $h) -ForegroundColor Green
exit 0