完善在线工具、插件运行时与安装卸载流程
This commit is contained in:
+143
-5
@@ -41,6 +41,7 @@ $ServerPublicRoot = Join-Path $Root 'server\update\public'
|
||||
$ServerDownloadRoot = Join-Path $ServerPublicRoot 'downloads'
|
||||
$ToolStateRoot = Join-Path $Root '.cache\tool_state'
|
||||
$NuGetRoot = Join-Path $Root '.cache\nuget'
|
||||
$BuildTempRoot = Join-Path $Root '.cache\build-temp'
|
||||
$TauriHostRoot = Join-Path $Root 'src\YMhut.Box.PluginTauriHost\src-tauri'
|
||||
$RepositoryCargoHome = Join-Path $Root '.cache\rust-toolchain\cargo'
|
||||
$RepositoryRustupHome = Join-Path $Root '.cache\rust-toolchain\rustup'
|
||||
@@ -324,7 +325,10 @@ function Ensure-TauriPluginHost {
|
||||
return $executable
|
||||
}
|
||||
|
||||
Write-Warning 'Cargo was not found, so the optional Tauri plugin host will not be included. Install the Rust stable MSVC toolchain to build ExternalRuntime support.'
|
||||
if ($Configuration -ieq 'Release') {
|
||||
throw 'Cargo was not found and no previously built Release Tauri host is available. Release packages require the bundled plugin host.'
|
||||
}
|
||||
Write-Warning 'Cargo was not found, so the optional Debug Tauri plugin host will not be included.'
|
||||
return $null
|
||||
}
|
||||
|
||||
@@ -516,6 +520,12 @@ function Save-ScaledLogo {
|
||||
}
|
||||
|
||||
function Ensure-MsixAssets {
|
||||
$iconScript = Join-Path $Root 'scripts\generate-app-icons.ps1'
|
||||
& $iconScript -Root $Root
|
||||
if (-not $?) {
|
||||
throw 'Application icon generation failed.'
|
||||
}
|
||||
|
||||
$sourceIcon = Join-Path $Root 'assets\icons\app_icon.png'
|
||||
if (-not (Test-Path -LiteralPath $sourceIcon)) {
|
||||
throw "MSIX source icon was not found: $sourceIcon"
|
||||
@@ -527,6 +537,18 @@ function Ensure-MsixAssets {
|
||||
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'Square150x150Logo.png') 150 150 16 '#00FFFFFF'
|
||||
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'LockScreenLogo.png') 70 70 8 '#00FFFFFF'
|
||||
Save-ScaledLogo $sourceIcon (Join-Path $ProjectAssets 'Wide310x150Logo.png') 310 150 24 '#F5F6F7' -Wordmark
|
||||
|
||||
$iconPath = Join-Path $ProjectAssets 'app_icon.ico'
|
||||
$bytes = [IO.File]::ReadAllBytes($iconPath)
|
||||
$count = [BitConverter]::ToUInt16($bytes, 4)
|
||||
$sizes = for ($index = 0; $index -lt $count; $index++) {
|
||||
$value = $bytes[6 + ($index * 16)]
|
||||
if ($value -eq 0) { 256 } else { [int] $value }
|
||||
}
|
||||
$missing = @(16, 20, 24, 32, 40, 48, 64, 96, 128, 256) | Where-Object { $_ -notin $sizes }
|
||||
if ($missing.Count -gt 0) {
|
||||
throw "Application icon is missing DPI frames: $($missing -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-LocalDeveloperCertificate {
|
||||
@@ -712,7 +734,13 @@ function Write-LayoutManifests([string] $Directory) {
|
||||
$configDir = Join-Path $Directory 'config'
|
||||
New-Directory $configDir
|
||||
|
||||
$required = @($files | Where-Object { $_ -in @('YMhutBox.exe', 'YMhutBox.dll', 'WebView2Loader.dll') })
|
||||
$required = @($files | Where-Object { $_ -in @(
|
||||
'YMhutBox.exe',
|
||||
'YMhutBox.dll',
|
||||
'WebView2Loader.dll',
|
||||
'Assets/runtime-assets.dat',
|
||||
'Assets/data/ymhut-data.ybin'
|
||||
) })
|
||||
if ($required.Count -eq 0) {
|
||||
$required = @('YMhutBox.exe')
|
||||
}
|
||||
@@ -720,6 +748,7 @@ function Write-LayoutManifests([string] $Directory) {
|
||||
$manifest = [System.Collections.Generic.List[string]]::new()
|
||||
if ($versionInfo) {
|
||||
$manifest.Add('[Release]')
|
||||
$manifest.Add('ManifestVersion=2')
|
||||
$manifest.Add("Version=$($versionInfo.Version)")
|
||||
$manifest.Add("Build=$($versionInfo.Build)")
|
||||
$manifest.Add("Channel=$($versionInfo.Channel)")
|
||||
@@ -1097,6 +1126,7 @@ function Normalize-PublishLayout([string] $Directory) {
|
||||
Remove-RootHelperDuplicates $Directory
|
||||
Assert-UnpackagedLanguageResourcesInLang $Directory
|
||||
Assert-NoDevelopmentArtifacts $Directory
|
||||
Assert-AuthenticatedAssetLayout $Directory
|
||||
}
|
||||
|
||||
function Assert-UnpackagedLanguageResourcesInLang([string] $Directory) {
|
||||
@@ -1274,8 +1304,14 @@ function Assert-ToolUiPayload([string] $Directory) {
|
||||
}
|
||||
|
||||
$files = @(Get-ChildItem -LiteralPath $folder -Filter '*.dat' -File -ErrorAction SilentlyContinue)
|
||||
if ($files.Count -lt 202) {
|
||||
throw "Release tool UI payload is incomplete in '$Directory': expected at least 202 .dat definitions, found $($files.Count)"
|
||||
$expectedFolder = Join-Path $Root 'assets\data\tool-ui'
|
||||
$expectedNames = @(Get-ChildItem -LiteralPath $expectedFolder -Filter '*.dat' -File -ErrorAction SilentlyContinue |
|
||||
ForEach-Object Name | Sort-Object)
|
||||
$actualNames = @($files | ForEach-Object Name | Sort-Object)
|
||||
$difference = @(Compare-Object -ReferenceObject $expectedNames -DifferenceObject $actualNames)
|
||||
if ($expectedNames.Count -eq 0 -or $difference.Count -gt 0) {
|
||||
$details = $difference | ForEach-Object { "$($_.SideIndicator) $($_.InputObject)" }
|
||||
throw "Release tool UI payload does not exactly match the repository definitions in '$Directory': $($details -join ', ')"
|
||||
}
|
||||
|
||||
foreach ($file in $files) {
|
||||
@@ -1286,6 +1322,47 @@ function Assert-ToolUiPayload([string] $Directory) {
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-AuthenticatedAssetLayout([string] $Directory, [switch] $AllowMsixLogos) {
|
||||
$assetsRoot = Join-Path $Directory 'Assets'
|
||||
$runtimePackage = Join-Path $assetsRoot 'runtime-assets.dat'
|
||||
$referencePackage = Join-Path $assetsRoot 'data\ymhut-data.ybin'
|
||||
foreach ($package in @($runtimePackage, $referencePackage)) {
|
||||
if (-not (Test-Path -LiteralPath $package -PathType Leaf)) {
|
||||
throw "Authenticated asset package is missing from '$Directory': $package"
|
||||
}
|
||||
|
||||
$bytes = [IO.File]::ReadAllBytes($package)
|
||||
if ($bytes.Length -lt 97 -or [Text.Encoding]::ASCII.GetString($bytes, 0, 8) -ne 'YMHUTAS2' -or $bytes[8] -ne 2) {
|
||||
throw "Authenticated asset package has an invalid header: $package"
|
||||
}
|
||||
}
|
||||
|
||||
$allowedPng = if ($AllowMsixLogos) {
|
||||
@(
|
||||
'Assets/StoreLogo.png',
|
||||
'Assets/Square44x44Logo.png',
|
||||
'Assets/Square150x150Logo.png',
|
||||
'Assets/Wide310x150Logo.png'
|
||||
)
|
||||
} else { @() }
|
||||
$blockedExtensions = @(
|
||||
'.bmp', '.css', '.csv', '.gif', '.glb', '.gltf', '.htm', '.html', '.ico',
|
||||
'.jpeg', '.jpg', '.js', '.json', '.mjs', '.otf', '.svg', '.ttf', '.wasm',
|
||||
'.webp', '.woff', '.woff2', '.xls', '.xlsx'
|
||||
)
|
||||
$blocked = @(Get-ChildItem -LiteralPath $assetsRoot -Recurse -File -ErrorAction SilentlyContinue | Where-Object {
|
||||
$relative = 'Assets/' + ($_.FullName.Substring($assetsRoot.Length).TrimStart('\', '/') -replace '\\', '/')
|
||||
($blockedExtensions -contains $_.Extension.ToLowerInvariant()) -or
|
||||
($_.Extension -ieq '.png' -and $allowedPng -notcontains $relative)
|
||||
})
|
||||
if ($blocked.Count -gt 0) {
|
||||
$relativeList = $blocked | ForEach-Object {
|
||||
'Assets/' + ($_.FullName.Substring($assetsRoot.Length).TrimStart('\', '/') -replace '\\', '/')
|
||||
}
|
||||
throw "Publish Assets contains replaceable plaintext static files: $($relativeList -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
function Copy-PublishToLatest {
|
||||
Stop-ProcessesFromDirectory $LatestRoot
|
||||
Reset-DirectoryInsideRepo $LatestRoot
|
||||
@@ -1293,6 +1370,7 @@ function Copy-PublishToLatest {
|
||||
Normalize-PublishLayout $LatestRoot
|
||||
Assert-MusicApiPayload $LatestRoot
|
||||
Assert-ToolUiPayload $LatestRoot
|
||||
Assert-AuthenticatedAssetLayout $LatestRoot
|
||||
Write-LayoutManifests $LatestRoot
|
||||
}
|
||||
|
||||
@@ -1305,6 +1383,7 @@ function Publish-UnpackagedApp([object] $VersionInfo) {
|
||||
'--self-contained', 'true',
|
||||
'--no-restore',
|
||||
'-p:WindowsPackageType=None',
|
||||
'-p:GenerateAppxPackageOnBuild=false',
|
||||
'-p:PublishSingleFile=false',
|
||||
"-p:AssemblyVersion=$($VersionInfo.PackageVersion)",
|
||||
"-p:FileVersion=$($VersionInfo.PackageVersion)",
|
||||
@@ -1314,6 +1393,7 @@ function Publish-UnpackagedApp([object] $VersionInfo) {
|
||||
Normalize-PublishLayout $PublishRoot
|
||||
Assert-MusicApiPayload $PublishRoot
|
||||
Assert-ToolUiPayload $PublishRoot
|
||||
Assert-AuthenticatedAssetLayout $PublishRoot
|
||||
Assert-PerMonitorV2Manifest (Join-Path $PublishRoot 'YMhutBox.exe')
|
||||
Write-LayoutManifests $PublishRoot
|
||||
Copy-PublishToLatest
|
||||
@@ -1387,6 +1467,39 @@ function Build-InstallerBootstrap([object] $VersionInfo, [string] $SignTool) {
|
||||
Invoke-ToolQuiet $setupPath @('/WINDOWSELFTEST') 'WinUI installer window navigation self-test failed' " Installer window self-test: $setupPath"
|
||||
}
|
||||
|
||||
function Build-UninstallerBootstrap([object] $VersionInfo, [string] $SignTool) {
|
||||
$bootstrapRoot = Join-Path $Root 'build\winui\uninstaller-bootstrap'
|
||||
Reset-DirectoryInsideRepo $bootstrapRoot
|
||||
Invoke-DotNet @(
|
||||
'publish', $InstallerBootstrapProject,
|
||||
'-c', $Configuration,
|
||||
'-r', 'win-x64',
|
||||
'--self-contained', 'true',
|
||||
'--no-restore',
|
||||
'-p:BootstrapMode=Uninstall',
|
||||
'-p:AssemblyName=unins000',
|
||||
"-p:Version=$($VersionInfo.PackageVersion)",
|
||||
"-p:FileVersion=$($VersionInfo.PackageVersion)",
|
||||
"-p:InformationalVersion=$($VersionInfo.PackageVersion)",
|
||||
'-o', $bootstrapRoot
|
||||
)
|
||||
|
||||
$bootstrap = Join-Path $bootstrapRoot 'unins000.exe'
|
||||
if (-not (Test-Path -LiteralPath $bootstrap -PathType Leaf)) {
|
||||
throw "WinUI uninstaller bootstrap was not produced: $bootstrap"
|
||||
}
|
||||
if ($SignTool) {
|
||||
Sign-Artifact $SignTool $bootstrap
|
||||
}
|
||||
Assert-PerMonitorV2Manifest $bootstrap
|
||||
|
||||
foreach ($payloadRoot in @($PublishRoot, $LatestRoot)) {
|
||||
Copy-Item -LiteralPath $bootstrap -Destination (Join-Path $payloadRoot 'unins000.exe') -Force
|
||||
Write-LayoutManifests $payloadRoot
|
||||
}
|
||||
Write-Host " Uninstaller bootstrap: $(Join-Path $LatestRoot 'unins000.exe')"
|
||||
}
|
||||
|
||||
function Wait-FileWritable([string] $Path, [int] $Attempts = 20, [int] $DelayMilliseconds = 250) {
|
||||
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
||||
$stream = $null
|
||||
@@ -1501,8 +1614,11 @@ function Build-MsixPackage([object] $VersionInfo, [string] $MakeAppx, [string] $
|
||||
|
||||
Reset-DirectoryInsideRepo $MsixStageRoot
|
||||
Copy-Item -Path (Join-Path $PublishRoot '*') -Destination $MsixStageRoot -Recurse -Force
|
||||
Remove-Item -LiteralPath (Join-Path $MsixStageRoot 'unins000.exe') -Force -ErrorAction SilentlyContinue
|
||||
Write-LayoutManifests $MsixStageRoot
|
||||
Assert-MusicApiPayload $MsixStageRoot
|
||||
Assert-ToolUiPayload $MsixStageRoot
|
||||
Assert-AuthenticatedAssetLayout $MsixStageRoot
|
||||
Assert-PerMonitorV2Manifest (Join-Path $MsixStageRoot 'YMhutBox.exe')
|
||||
Expand-SatelliteResourcePackages $MsixStageRoot -RemovePackages
|
||||
Restore-LangResourceLayoutForMsix $MsixStageRoot
|
||||
@@ -1516,7 +1632,10 @@ function Build-MsixPackage([object] $VersionInfo, [string] $MakeAppx, [string] $
|
||||
|
||||
$stageAssets = Join-Path $MsixStageRoot 'Assets'
|
||||
New-Directory $stageAssets
|
||||
Copy-Item -Path (Join-Path $ProjectAssets '*') -Destination $stageAssets -Recurse -Force
|
||||
foreach ($logo in @('StoreLogo.png', 'Square44x44Logo.png', 'Square150x150Logo.png', 'Wide310x150Logo.png')) {
|
||||
Copy-Item -LiteralPath (Join-Path $ProjectAssets $logo) -Destination (Join-Path $stageAssets $logo) -Force
|
||||
}
|
||||
Assert-AuthenticatedAssetLayout $MsixStageRoot -AllowMsixLogos
|
||||
|
||||
Repair-MsixStageThirdPartyPayloads
|
||||
Invoke-ToolQuiet $MakeAppx @('pack', '/d', $MsixStageRoot, '/p', $msixPath, '/o') 'MakeAppx packaging failed' " MSIX package: $msixPath"
|
||||
@@ -1599,10 +1718,15 @@ pause
|
||||
|
||||
New-Directory $NuGetRoot
|
||||
New-Directory $OutputRoot
|
||||
New-Directory $BuildTempRoot
|
||||
$env:DOTNET_CLI_HOME = Join-Path $ToolStateRoot 'dotnet'
|
||||
$env:NUGET_PACKAGES = $NuGetRoot
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = '1'
|
||||
$env:TEMP = $BuildTempRoot
|
||||
$env:TMP = $BuildTempRoot
|
||||
$env:DOTNET_BUNDLE_EXTRACT_BASE_DIR = Join-Path $BuildTempRoot 'dotnet-bundle'
|
||||
New-Directory $env:DOTNET_CLI_HOME
|
||||
New-Directory $env:DOTNET_BUNDLE_EXTRACT_BASE_DIR
|
||||
|
||||
$versionInfo = Get-VersionInfo
|
||||
$makeAppx = Find-WindowsSdkTool 'MakeAppx.exe'
|
||||
@@ -1637,12 +1761,26 @@ foreach ($ridProject in @($Project, $InstallerBootstrapProject)) {
|
||||
}
|
||||
|
||||
Publish-UnpackagedApp $versionInfo
|
||||
Build-UninstallerBootstrap $versionInfo $signTool
|
||||
if ($tauriHostExecutable) {
|
||||
$publishedTauriHost = Join-Path $PublishRoot 'tauri-host\ymhut-box-plugin-tauri-host.exe'
|
||||
if (-not (Test-Path -LiteralPath $publishedTauriHost)) {
|
||||
throw "The Tauri plugin host was built but was not included in the publish payload: $publishedTauriHost"
|
||||
}
|
||||
}
|
||||
$tauriRuntimeFiles = @(
|
||||
'tauri-host\runtime.json',
|
||||
'tauri-host\template.ymtemplate',
|
||||
'tauri-host\LICENSES.txt',
|
||||
'tauri-host\template\dist\index.html',
|
||||
'tauri-host\template\README.md'
|
||||
)
|
||||
foreach ($relative in $tauriRuntimeFiles) {
|
||||
$runtimeFile = Join-Path $PublishRoot $relative
|
||||
if (-not (Test-Path -LiteralPath $runtimeFile -PathType Leaf)) {
|
||||
throw "The bundled Tauri runtime dependency is missing from the publish payload: $runtimeFile"
|
||||
}
|
||||
}
|
||||
|
||||
if (($Target -in @('exe', 'both')) -and -not $SkipExe) {
|
||||
$exeBuilt = Build-InnoInstaller $versionInfo $signTool -AllowMissingCompiler:($Target -eq 'both')
|
||||
|
||||
Reference in New Issue
Block a user