feat: v1.2.0 开发进度更新
### 新增功能 - **Linux 官方支持**:新增 Hua.Todo.Avalonia 项目,正式适配 Linux 平台,同时支持 Windows 和 macOS - **Avalonia 桌面交互**:增加托盘菜单(显示/退出)、关闭隐藏到托盘、Windows 全局热键唤起主窗口、热键配置本地持久化 - **SQLite DateTime 兼容修复**:新增 LenientUtcDateTimeStringConverter,解决历史遗留的 DateTime 脏数据解析问题 - **用户文档完善**:新增 docs/manual/新手指南.md 和 docs/manual/用户指南.md - **部署文档**:新增 docs/manual/部署文档.md,详细说明多平台发布流程 ### 优化与修复 - **发布脚本整理**:拆分/对齐各平台发布入口,新增 publish.ps1 作为统一入口 - **Windows WebView2 优化**:数据目录调整到 %LocalAppData%\Hua.Todo\WebView2,修复 Runtime 误判问题 - **MAUI 多平台构建**:在 Windows 开发机上默认仅构建 Android + Windows 目标 - **SPA 路由回落**:修复 Release 模式下 /swagger 路径的 404 问题 - **Swagger 输出**:补齐 Dynamic API 端点,避免接口缺失 ### 文档更新 - **版本记录**:更新 v1.2.0 开发进度和功能列表 - **技术设计文档**:添加 Avalonia 项目架构和模块设计 - **项目结构**:更新 README.md 中的项目结构说明 ### 其他变更 - 新增 Directory.Build.props 和更新 Directory.Build.targets - 调整 src/Hua.Todo.Avalonia 项目配置和资源文件 - 更新 src/Hua.Todo.Web 前端资源文件 - 修复 src/Hua.Todo.Maui 相关配置和打包脚本
This commit is contained in:
+108
-11
@@ -14,14 +14,24 @@ param(
|
||||
[ValidateSet("linux-x64", "linux-arm64")]
|
||||
[string]$RuntimeIdentifier = "linux-x64",
|
||||
|
||||
[string]$TargetFramework = "net10.0",
|
||||
|
||||
[switch]$SelfContained,
|
||||
|
||||
[string]$Configuration = "Release"
|
||||
[switch]$SkipProcessStop,
|
||||
|
||||
[switch]$SkipRestore,
|
||||
|
||||
[ValidateSet("Release", "Debug")]
|
||||
[string]$Configuration = "Release",
|
||||
|
||||
[string]$BaseIntermediateOutputPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$ScriptPath = $PSScriptRoot
|
||||
$DirectoryBuildProps = Join-Path $ScriptPath "Directory.Build.props"
|
||||
|
||||
function Get-FirstExistingFilePath {
|
||||
param(
|
||||
@@ -41,10 +51,23 @@ function Get-FirstExistingFilePath {
|
||||
function Read-ProjectVersion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ProjectFile
|
||||
[string]$ProjectFile,
|
||||
|
||||
[string]$DirectoryBuildProps
|
||||
)
|
||||
|
||||
$currentVersion = "0.0.0"
|
||||
|
||||
# 1. Try Directory.Build.props first
|
||||
if ($null -ne $DirectoryBuildProps -and (Test-Path $DirectoryBuildProps)) {
|
||||
[xml]$props = Get-Content $DirectoryBuildProps -Raw
|
||||
$versionNode = $props.SelectSingleNode("//Version")
|
||||
if ($null -ne $versionNode -and -not [string]::IsNullOrWhiteSpace($versionNode.InnerText)) {
|
||||
return $versionNode.InnerText.Trim()
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Try .csproj
|
||||
[xml]$csproj = Get-Content $ProjectFile -Raw
|
||||
|
||||
$versionNode = $csproj.SelectSingleNode("//Version")
|
||||
@@ -60,6 +83,38 @@ function Read-ProjectVersion {
|
||||
return $currentVersion
|
||||
}
|
||||
|
||||
function Stop-ProjectProcesses {
|
||||
Write-Host "Shutting down dotnet build servers..." -ForegroundColor Yellow
|
||||
dotnet build-server shutdown | Out-Null
|
||||
|
||||
Write-Host "Checking for running processes to prevent file locks..." -ForegroundColor Yellow
|
||||
|
||||
# Aggressively look for anything related to the project or MSBuild/dotnet background tasks
|
||||
$processesToKill = Get-Process | Where-Object {
|
||||
$_.ProcessName -like "*Hua.Todo*" -or
|
||||
$_.ProcessName -eq "MSBuild" -or
|
||||
($_.ProcessName -eq "dotnet" -and ($_.CommandLine -like "*Hua.Todo*" -or $_.CommandLine -like "*msbuild*"))
|
||||
}
|
||||
|
||||
if ($processesToKill) {
|
||||
Write-Host "Stopping $($processesToKill.Count) running processes..." -ForegroundColor Yellow
|
||||
foreach ($p in $processesToKill) {
|
||||
try {
|
||||
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
Write-Warning "Failed to stop process $($p.ProcessName) (ID: $($p.Id))"
|
||||
}
|
||||
}
|
||||
Start-Sleep -Seconds 2 # Give OS more time to release file handles
|
||||
} else {
|
||||
Write-Host "No conflicting processes found." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
if (!$SkipProcessStop.IsPresent) {
|
||||
Stop-ProjectProcesses
|
||||
}
|
||||
|
||||
$candidateProjects = @(
|
||||
(Join-Path $ScriptPath "src\Hua.Todo.Avalonia\Hua.Todo.Avalonia.csproj"),
|
||||
(Join-Path $ScriptPath "src\Hua.Todo.Desktop.Avalonia\Hua.Todo.Desktop.Avalonia.csproj")
|
||||
@@ -77,7 +132,8 @@ $candidatesText
|
||||
exit 1
|
||||
}
|
||||
|
||||
$version = Read-ProjectVersion -ProjectFile $ProjectFile
|
||||
$version = Read-ProjectVersion -ProjectFile $ProjectFile -DirectoryBuildProps $DirectoryBuildProps
|
||||
$ProjectBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ProjectFile)
|
||||
$artifactRoot = Join-Path $ScriptPath "artifacts\linux\$RuntimeIdentifier"
|
||||
$publishDir = Join-Path $artifactRoot "publish"
|
||||
|
||||
@@ -88,16 +144,57 @@ New-Item -ItemType Directory -Path $publishDir | Out-Null
|
||||
|
||||
$selfContainedValue = if ($SelfContained.IsPresent) { "true" } else { "false" }
|
||||
|
||||
Write-Host "Publishing (RID=$RuntimeIdentifier, SelfContained=$selfContainedValue)..." -ForegroundColor Cyan
|
||||
if ($Configuration -ne "Release") {
|
||||
Write-Host "⚠️ WARNING: You are publishing in $Configuration configuration!" -ForegroundColor Yellow
|
||||
Write-Host " Typically, production artifacts MUST be in Release configuration." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
dotnet publish $ProjectFile `
|
||||
-c $Configuration `
|
||||
-r $RuntimeIdentifier `
|
||||
--self-contained $selfContainedValue `
|
||||
-o $publishDir
|
||||
if (!$SkipRestore.IsPresent) {
|
||||
Write-Host "Restoring $ProjectBaseName for $RuntimeIdentifier ($TargetFramework)..." -ForegroundColor Yellow
|
||||
$restoreArgs = @("restore", $ProjectFile, "-r", $RuntimeIdentifier, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "--verbosity", "minimal")
|
||||
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
|
||||
# Ensure trailing slash and avoid backslash escaping the quote in CLI
|
||||
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
|
||||
$restoreArgs += "-p:BaseIntermediateOutputPath=$path"
|
||||
}
|
||||
& dotnet @restoreArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "dotnet publish failed"
|
||||
Write-Host "Cleaning $ProjectBaseName ($TargetFramework)..." -ForegroundColor Yellow
|
||||
$cleanArgs = @("clean", $ProjectFile, "-c", $Configuration, "-r", $RuntimeIdentifier, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "--verbosity", "minimal")
|
||||
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
|
||||
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
|
||||
$cleanArgs += "-p:BaseIntermediateOutputPath=$path"
|
||||
}
|
||||
& dotnet @cleanArgs
|
||||
}
|
||||
|
||||
$maxAttempts = 3
|
||||
$publishSuccess = $false
|
||||
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
|
||||
Write-Host "Publishing (RID=$RuntimeIdentifier, TFM=$TargetFramework, SelfContained=$selfContainedValue, Config=$Configuration, Attempt $attempt/$maxAttempts)..." -ForegroundColor Cyan
|
||||
$publishArgs = @("publish", $ProjectFile, "-c", $Configuration, "-r", $RuntimeIdentifier, "--framework", $TargetFramework, "--self-contained", $selfContainedValue, "-p:IsDesktopBuild=true", "-p:SkipWebBuild=true", "-o", $publishDir)
|
||||
if (![string]::IsNullOrWhiteSpace($BaseIntermediateOutputPath)) {
|
||||
$path = $BaseIntermediateOutputPath.TrimEnd('\') + '\'
|
||||
$publishArgs += "-p:BaseIntermediateOutputPath=$path"
|
||||
}
|
||||
$publishOutput = (& dotnet @publishArgs 2>&1 | Out-String)
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
$publishSuccess = $true
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host $publishOutput
|
||||
if ($attempt -lt $maxAttempts -and ($publishOutput -match 'Access to the path .* is denied|being used by another process|Sharing violation')) {
|
||||
Write-Host "⚠️ Build failed due to file lock. Retrying in 3 seconds..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 3
|
||||
Stop-ProjectProcesses # Try killing processes again before retry
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Error "dotnet publish failed after $attempt attempts."
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user