param([switch]$Finalized) $ErrorActionPreference = 'Stop' if (-not $Finalized) { throw 'Only run after root finalizes the patient release.' } $taskArtifactRoot = $PSScriptRoot $taskPlan = Get-Content -Raw -LiteralPath (Join-Path $taskArtifactRoot 'patient-release-plan.json') | ConvertFrom-Json if (-not $taskPlan.finalized -or $taskPlan.version -ne '1.4.25') { throw 'Finalized release plan required' } $taskRoot = [IO.Path]::GetFullPath($taskPlan.windowsRoot) if ($taskRoot.TrimEnd('\') -ne 'C:\wechat_rpa') { throw 'Validated cached builder requires C:\wechat_rpa' } foreach ($taskModule in $taskPlan.windowsTestModules) { if (-not (Test-Path -LiteralPath (Join-Path $taskRoot ($taskModule + '.py')))) { throw ('Sync finalized test module first: ' + $taskModule) } } $taskPython = Join-Path $taskRoot '.build-venv\Scripts\python.exe' $taskOld = Join-Path $taskRoot 'releases\甄AI客服-安装包-v1.4.24.exe' $taskPreviousHash = '28ba845b9910b29521968f70877f05cb99dcccd6e9b0d382652800100780b5d6' if ((Get-FileHash -LiteralPath $taskOld -Algorithm SHA256).Hash.ToLowerInvariant() -ne $taskPreviousHash) { throw 'Previous release changed' } if (Test-Path -LiteralPath (Join-Path $taskRoot 'dist\ZhenAI-WeCom-Assistant-v1.4.25')) { throw 'Refuse to overwrite existing app build' } if (Test-Path -LiteralPath (Join-Path $taskRoot 'dist\installer\甄AI客服-安装包-v1.4.25.exe')) { throw 'Refuse to overwrite existing installer' } if (-not (Select-String -LiteralPath (Join-Path $taskRoot 'app_version.py') -Pattern '^APP_VERSION = "1.4.25"$')) { throw 'Root must set APP_VERSION 1.4.25 before build' } & $taskPython -B -c 'import sys,ssl,PyInstaller,PySide6; assert sys.version_info[:2] == (3,12)' if ($LASTEXITCODE -ne 0) { throw 'Existing Python 3.12 build environment required; no automatic installs' } $taskManifest = [ordered]@{} $taskInputs = @(Get-ChildItem -LiteralPath $taskRoot -File | Where-Object { $_.Extension -in @('.py','.ps1','.spec') -or $_.Name -eq 'requirements.txt' }) + @(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'assets') -Recurse -File) foreach ($taskFile in $taskInputs) { $taskRelative = $taskFile.FullName.Substring($taskRoot.Length + 1).Replace('\','/'); $taskManifest[$taskRelative] = (Get-FileHash -LiteralPath $taskFile.FullName -Algorithm SHA256).Hash.ToLowerInvariant() } $taskManifestPath = Join-Path $taskArtifactRoot 'build-source-manifest.json' if (Test-Path -LiteralPath $taskManifestPath) { throw 'Build manifest already exists' } [IO.File]::WriteAllText($taskManifestPath, ($taskManifest | ConvertTo-Json), [Text.UTF8Encoding]::new($false)) $env:LOCALAPPDATA = Join-Path $taskArtifactRoot 'build-profile\Local' $env:APPDATA = Join-Path $taskArtifactRoot 'build-profile\Roaming' New-Item -ItemType Directory -Path $env:LOCALAPPDATA,$env:APPDATA -Force | Out-Null Push-Location $taskRoot try { & (Join-Path $taskArtifactRoot 'build_windows_cached_patient.ps1') -PythonPath $taskPython *> (Join-Path $taskArtifactRoot 'patient-app-build.log') if ($LASTEXITCODE -ne 0) { throw 'Application build failed' } foreach ($taskEntry in $taskManifest.GetEnumerator()) { if ((Get-FileHash -LiteralPath (Join-Path $taskRoot $taskEntry.Key) -Algorithm SHA256).Hash.ToLowerInvariant() -ne $taskEntry.Value) { throw ('Source changed during build: '+$taskEntry.Key) } } & (Join-Path $taskRoot 'build_installer.ps1') -SkipAppBuild *> (Join-Path $taskArtifactRoot 'patient-installer-build.log') if ($LASTEXITCODE -ne 0) { throw 'Installer build failed' } if ((Get-FileHash -LiteralPath $taskOld -Algorithm SHA256).Hash.ToLowerInvariant() -ne $taskPreviousHash) { throw 'Previous release changed' } } finally { Pop-Location } Write-Output 'Built v1.4.25 locally. Validation required; no install/upload/publication performed.'