Files
Hua.Todo/publish-windows.ps1
T
ShaoHua 7a4c516a20 feat: 引入 CloudSync 核心能力并新增 Avalonia 桌面端与发布脚本
- 后端:新增 CloudSync 认证/权限/端点/服务与 DTO
- 数据:新增用户/会话/安全策略实体与 EF Core migrations
- 前端:新增云同步设置 UI、客户端与本地存储;Vite 支持 maui 构建输出到 wwwroot
- 桌面端:新增 Avalonia 项目、内置 WebServer、托盘与 Windows 全局热键
- 发布/构建:新增 Windows/Linux 发布脚本与统一入口;调整 MAUI 资源与安装包配置
- 文档:同步更新 README/docs 与协作规则
2026-04-07 03:34:34 +08:00

326 lines
11 KiB
PowerShell

param(
[string]$TargetFramework = "net10.0-windows10.0.19041.0",
[string]$RuntimeIdentifier = "win-x64",
[string]$Configuration = "Release",
[switch]$SkipInnoSetup,
[switch]$SkipVersionBump,
[string]$ArtifactsRoot = (Join-Path $PSScriptRoot ("artifacts\windows\{0}" -f $RuntimeIdentifier))
)
$ErrorActionPreference = "Stop"
$ScriptPath = $PSScriptRoot
$ProjectDir = Join-Path $ScriptPath "src\Hua.Todo.Maui"
$ProjectFile = Join-Path $ProjectDir "Hua.Todo.Maui.csproj"
$SetupScript = Join-Path $ProjectDir "setup.iss"
$ProjectBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ProjectFile)
function Read-ProjectVersion {
param(
[Parameter(Mandatory = $true)]
[string]$ProjectFile
)
$currentVersion = "0.0.0"
[xml]$csproj = Get-Content $ProjectFile -Raw
$versionNode = $csproj.SelectSingleNode("//Version")
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
return $versionNode.InnerText.Trim()
}
$versionNode = $csproj.SelectSingleNode("//ApplicationDisplayVersion")
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
return $versionNode.InnerText.Trim()
}
return $currentVersion
}
function Read-InnoSetupAppName {
param(
[Parameter(Mandatory = $true)]
[string]$SetupScript
)
if (!(Test-Path $SetupScript)) {
return $null
}
$content = Get-Content $SetupScript -Raw
$match = [regex]::Match($content, '#define\s+MyAppName\s+"([^"]+)"')
if ($match.Success) {
return $match.Groups[1].Value.Trim()
}
return $null
}
# Read OutputBaseFilename from setup.iss so we can determine the exact installer file path after ISCC runs.
# The value may contain Inno preprocessor macros like {#MyAppName}/{#MyAppVersion}.
function Read-InnoSetupOutputBaseFilename {
param(
[Parameter(Mandatory = $true)]
[string]$SetupScript
)
if (!(Test-Path $SetupScript)) {
return $null
}
$content = Get-Content $SetupScript -Raw
$match = [regex]::Match($content, '^\s*OutputBaseFilename\s*=\s*(.+?)\s*$', [System.Text.RegularExpressions.RegexOptions]::Multiline)
if ($match.Success) {
return $match.Groups[1].Value.Trim().Trim('"')
}
return $null
}
# Resolve the subset of Inno preprocessor macros used by this repo to a concrete file base name.
function Resolve-InnoSetupOutputBaseFilename {
param(
[Parameter(Mandatory = $true)]
[string]$Template,
[string]$AppName,
[string]$Version
)
if ([string]::IsNullOrWhiteSpace($Template)) {
return $null
}
$resolved = $Template
if (![string]::IsNullOrWhiteSpace($AppName)) {
$resolved = $resolved.Replace("{#MyAppName}", $AppName)
}
if (![string]::IsNullOrWhiteSpace($Version)) {
$resolved = $resolved.Replace("{#MyAppVersion}", $Version)
}
return $resolved.Trim()
}
function Update-InnoSetupVersion {
param(
[Parameter(Mandatory = $true)]
[string]$SetupScript,
[Parameter(Mandatory = $true)]
[string]$Version
)
if (!(Test-Path $SetupScript)) {
return
}
$issContent = Get-Content $SetupScript
$versionFound = $false
for ($i = 0; $i -lt $issContent.Count; $i++) {
if ($issContent[$i] -like '#define MyAppVersion *') {
$issContent[$i] = '#define MyAppVersion "' + $Version + '"'
$versionFound = $true
break
}
}
if ($versionFound) {
Set-Content $SetupScript -Value $issContent -Encoding UTF8
}
}
function Copy-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Source,
[Parameter(Mandatory = $true)]
[string]$Destination
)
if (Test-Path $Destination) {
Remove-Item -Recurse -Force $Destination
}
New-Item -ItemType Directory -Path $Destination | Out-Null
Copy-Item -Path (Join-Path $Source "*") -Destination $Destination -Recurse -Force
}
$currentVersion = Read-ProjectVersion -ProjectFile $ProjectFile
Update-InnoSetupVersion -SetupScript $SetupScript -Version $currentVersion
Write-Host "Publishing Hua.Todo.Maui (TFM=$TargetFramework, RID=$RuntimeIdentifier, Config=$Configuration)..." -ForegroundColor Cyan
# UseMonoRuntime 在 csproj 内按 TargetFramework 做了条件配置:仅 Android 启用,其它目标关闭。
dotnet publish $ProjectFile -f $TargetFramework -c $Configuration -r $RuntimeIdentifier --self-contained false
if ($LASTEXITCODE -ne 0) {
Write-Error "MAUI build failed"
exit 1
}
$publishDir = Join-Path $ProjectDir ("bin\{0}\{1}\{2}\publish" -f $Configuration, $TargetFramework, $RuntimeIdentifier)
if (!(Test-Path $publishDir)) {
$publishDir = (Get-ChildItem -Path (Join-Path $ProjectDir "bin\$Configuration") -Recurse -Directory -Filter publish -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName)
}
if (!(Test-Path $publishDir)) {
Write-Error "Publish directory not found"
exit 1
}
$publishAppSettings = Join-Path $publishDir "appsettings.json"
if (Test-Path $publishAppSettings) {
$appSettingsObj = Get-Content $publishAppSettings -Raw | ConvertFrom-Json
if ($null -eq $appSettingsObj.WebServer) {
$appSettingsObj | Add-Member -MemberType NoteProperty -Name "WebServer" -Value ([pscustomobject]@{})
}
$appSettingsObj.WebServer.IsUsingStatic = $true
$appSettingsObj | ConvertTo-Json -Depth 32 | Set-Content -Path $publishAppSettings -Encoding UTF8
} else {
Write-Error "Publish appsettings.json not found: $publishAppSettings"
exit 1
}
$desiredExeName = "$ProjectBaseName.exe"
$desiredExePath = Join-Path $publishDir $desiredExeName
if (!(Test-Path $desiredExePath)) {
$candidateExe = $null
$preferredCandidateNames = @(
"Hua.Todo.Maui.exe",
"Hua.Todo.exe"
)
foreach ($name in $preferredCandidateNames) {
$candidatePath = Join-Path $publishDir $name
if (Test-Path $candidatePath) {
$candidateExe = Get-Item -Path $candidatePath
break
}
}
if ($null -eq $candidateExe) {
$exeFiles = Get-ChildItem -Path $publishDir -Filter "*.exe" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike "*Setup*" }
$candidateExe = $exeFiles | Where-Object { Test-Path (Join-Path $publishDir ($_.BaseName + ".dll")) } | Select-Object -First 1
if ($null -eq $candidateExe) {
$candidateExe = $exeFiles | Sort-Object Length -Descending | Select-Object -First 1
}
}
if ($null -ne $candidateExe) {
Rename-Item -Path $candidateExe.FullName -NewName $desiredExeName -Force
}
}
if (!(Test-Path $desiredExePath)) {
Write-Error "Publish main executable not found: $desiredExeName"
exit 1
}
$mauiWwwrootDir = Join-Path $ProjectDir "wwwroot"
$mauiIndexPath = Join-Path $mauiWwwrootDir "index.html"
if (!(Test-Path $mauiIndexPath)) {
Write-Error "MAUI wwwroot not found: $mauiIndexPath"
exit 1
}
$publishWwwroot = Join-Path $publishDir "wwwroot"
if (Test-Path $publishWwwroot) {
Remove-Item -Recurse -Force $publishWwwroot
}
New-Item -ItemType Directory -Path $publishWwwroot | Out-Null
Copy-Item -Path (Join-Path $mauiWwwrootDir "*") -Destination $publishWwwroot -Recurse -Force
$installerPath = $null
if (!$SkipInnoSetup.IsPresent) {
$ISCC = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
if (Test-Path $ISCC) {
$innoAppName = Read-InnoSetupAppName -SetupScript $SetupScript
$outputDir = Join-Path $ProjectDir "Output"
$outputBaseTemplate = Read-InnoSetupOutputBaseFilename -SetupScript $SetupScript
$resolvedOutputBase = Resolve-InnoSetupOutputBaseFilename -Template $outputBaseTemplate -AppName $innoAppName -Version $currentVersion
$expectedInstallerPath = $null
if (![string]::IsNullOrWhiteSpace($resolvedOutputBase)) {
$expectedInstallerPath = Join-Path $outputDir ("{0}.exe" -f $resolvedOutputBase)
}
if (![string]::IsNullOrWhiteSpace($innoAppName)) {
# Prevent stale unversioned installer (Output\Hua.Todo.exe) from confusing local release artifacts.
$oldInstallerPath = Join-Path $outputDir ("{0}.exe" -f $innoAppName)
if (Test-Path $oldInstallerPath) {
Remove-Item -Path $oldInstallerPath -Force
}
}
# ISCC sometimes fails with a transient file sharing violation (e.g. antivirus/indexer holding the script/output briefly).
$maxAttempts = 3
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
$isccOutput = (& $ISCC $SetupScript 2>&1 | Out-String)
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
break
}
Write-Host $isccOutput
if ($attempt -lt $maxAttempts -and ($isccOutput -match 'Compile aborted|being used by another process|Sharing violation|process cannot access')) {
Start-Sleep -Seconds 2
continue
}
Write-Error "Packaging failed"
exit 1
}
if ($null -ne $expectedInstallerPath -and (Test-Path $expectedInstallerPath)) {
$installerPath = $expectedInstallerPath
}
if ($null -eq $installerPath) {
if (Test-Path $outputDir) {
$installerPath = (Get-ChildItem -Path $outputDir -Filter "*.exe" -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName)
}
}
Write-Host "Setup package created successfully!" -ForegroundColor Green
if ($null -ne $installerPath -and (Test-Path $installerPath)) {
Write-Host ("Output: {0}" -f $installerPath) -ForegroundColor Green
}
} else {
Write-Error "Inno Setup compiler not found"
exit 1
}
}
if (![string]::IsNullOrWhiteSpace($ArtifactsRoot)) {
$publishArtifactDir = Join-Path $ArtifactsRoot "publish"
$installerArtifactDir = Join-Path $ArtifactsRoot "installer"
Copy-Directory -Source $publishDir -Destination $publishArtifactDir
if ($null -ne $installerPath -and (Test-Path $installerPath)) {
if (Test-Path $installerArtifactDir) {
Remove-Item -Recurse -Force $installerArtifactDir
}
New-Item -ItemType Directory -Path $installerArtifactDir | Out-Null
$installerName = "hua.todo-$currentVersion-$RuntimeIdentifier-setup.exe"
Copy-Item -Path $installerPath -Destination (Join-Path $installerArtifactDir $installerName) -Force
}
}
if (!$SkipVersionBump.IsPresent) {
$versionMatch = [regex]::Match($currentVersion, '^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$')
if ($versionMatch.Success) {
$newVersion = "{0}.{1}.{2}" -f $versionMatch.Groups["major"].Value, $versionMatch.Groups["minor"].Value, ([int]$versionMatch.Groups["patch"].Value + 1)
$content = Get-Content $ProjectFile -Raw
if ($content -match "<Version>[^<]*</Version>") {
$content = $content -replace "<Version>[^<]*</Version>", "<Version>$newVersion</Version>"
Set-Content $ProjectFile -Value $content -Encoding UTF8
} else {
Write-Host "Skip version bump: <Version> node not found in csproj." -ForegroundColor Yellow
}
} else {
Write-Host "Skip version bump: version is not MAJOR.MINOR.PATCH -> $currentVersion" -ForegroundColor Yellow
}
}