fix(gameinput): 更新 InputWeave 修正版套件 #93
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: 建置與發佈 | |
| on: | |
| push: | |
| tags: | |
| # 當推送符合語意版本的標籤(如 v1.0.0)時觸發,排除非版本標籤(如 vtest)。 | |
| - 'v[0-9]*' | |
| workflow_call: | |
| secrets: | |
| VT_API_KEY: | |
| required: false | |
| concurrency: | |
| # 相同 tag 的重複發版只保留最新一次,避免重覆占用 runner 與 API 配額。 | |
| group: release-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-release: | |
| runs-on: windows-latest | |
| timeout-minutes: 25 | |
| permissions: | |
| contents: write | |
| # 定義環境變數,方便統一管理。 | |
| env: | |
| DOTNET_MAJOR_VERSION: '10' | |
| DOTNET_CLI_TELEMETRY_OPTOUT: '1' | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1' | |
| DOTNET_NOLOGO: '1' | |
| PUBLISH_DIR: "./src/InputBox/out" | |
| EXE_NAME: 'InputBox.exe' | |
| SHA256_HASH: '' | |
| GAMEINPUT_REDIST_SHA256: '' | |
| ZIP_FILENAME: '' | |
| steps: | |
| - name: 簽出原始碼(Checkout) | |
| uses: actions/checkout@v6 | |
| with: | |
| # 發版僅需目前 tag 對應提交,避免抓取完整歷史紀錄。 | |
| fetch-depth: 1 | |
| - name: 驗證版本標籤來源為 main 最新提交 | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| # 僅抓取 main 的最新遠端頭,避免不必要的歷史下載。 | |
| git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main | |
| $tagCommit = (git rev-list -n 1 "${{ github.ref_name }}").Trim() | |
| $mainHead = (git rev-parse origin/main).Trim() | |
| Write-Host "Tag commit: $tagCommit" | |
| Write-Host "main HEAD: $mainHead" | |
| if ([string]::IsNullOrWhiteSpace($tagCommit) -or [string]::IsNullOrWhiteSpace($mainHead)) { | |
| Write-Error "無法解析版本標籤或 main 分支的提交資訊。" | |
| } | |
| if ($tagCommit -ne $mainHead) { | |
| Write-Error "正式版本標籤只能從 main 分支最新提交建立;目前 tag 未指向 origin/main 的最新 commit。" | |
| } | |
| Write-Host "版本標籤來源驗證通過。" | |
| - name: 安裝 .NET 環境 | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| # 使用 GitHub Actions 語法來讀取 env 變數。 | |
| dotnet-version: '${{ env.DOTNET_MAJOR_VERSION }}.x' | |
| # Release 觸發頻率遠低於 cache TTL(7 天),不快取以避免無效寫入。 | |
| - name: 驗證本儲存庫 InputWeave.GameInput NuGet 套件 | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $packagePath = ".\eng\nuget\InputWeave.GameInput.0.0.1.nupkg" | |
| $checksumPath = ".\eng\nuget\InputWeave.GameInput.0.0.1.nupkg.sha256" | |
| if (-not (Test-Path $packagePath)) { | |
| Write-Error "找不到本儲存庫 InputWeave.GameInput NuGet 套件:$packagePath" | |
| } | |
| if (-not (Test-Path $checksumPath)) { | |
| Write-Error "找不到本儲存庫 InputWeave.GameInput 套件 SHA256 檔案:$checksumPath" | |
| } | |
| $expected = ((Get-Content -LiteralPath $checksumPath -Raw).Trim() -split '\s+')[0].ToLowerInvariant() | |
| $actual = (Get-FileHash $packagePath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| if ($actual -ne $expected) { | |
| Write-Error "InputWeave.GameInput 套件 SHA256 不符。Expected=$expected Actual=$actual" | |
| } | |
| $expectedCommit = "72697db594b8f1863bd3fbf5db75ff69b40e3364" | |
| $inspectDir = Join-Path $env:RUNNER_TEMP "inputweave-gameinput-package" | |
| if (Test-Path $inspectDir) { | |
| Remove-Item -LiteralPath $inspectDir -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Path $inspectDir | Out-Null | |
| Expand-Archive -Path $packagePath -DestinationPath $inspectDir -Force | |
| $nuspecPath = Get-ChildItem -LiteralPath $inspectDir -Filter "*.nuspec" -File | Select-Object -First 1 | |
| if ($null -eq $nuspecPath) { | |
| Write-Error "InputWeave.GameInput 套件缺少 nuspec。" | |
| } | |
| $nuspecText = Get-Content -LiteralPath $nuspecPath.FullName -Raw | |
| if ($nuspecText -notmatch "commit=`"$expectedCommit`"") { | |
| Write-Error "InputWeave.GameInput 套件封裝來源 commit 不符;必須為 $expectedCommit。" | |
| } | |
| Write-Host "InputWeave.GameInput 套件 SHA256 驗證通過:$actual" | |
| Write-Host "InputWeave.GameInput 套件封裝來源 commit 驗證通過:$expectedCommit" | |
| - name: 還原 NuGet 套件 | |
| run: dotnet restore ./src/InputBox/InputBox.csproj --force-evaluate | |
| - name: 讀取 Microsoft.GameInput 版本 | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| [xml]$project = Get-Content -LiteralPath "./src/InputBox/InputBox.csproj" -Raw | |
| $packageRefs = @($project.Project.ItemGroup.PackageReference | Where-Object { $_.Include -eq 'Microsoft.GameInput' }) | |
| if ($packageRefs.Count -ne 1) { | |
| Write-Error "InputBox.csproj 必須剛好包含一個 Microsoft.GameInput PackageReference;目前找到 $($packageRefs.Count) 個。" | |
| } | |
| $version = [string]$packageRefs[0].Version | |
| if ([string]::IsNullOrWhiteSpace($version)) { | |
| Write-Error "Microsoft.GameInput PackageReference 缺少明確 Version。" | |
| } | |
| if ($version -notmatch '^\d+\.\d+\.\d+(?:\.\d+)?$') { | |
| Write-Error "Microsoft.GameInput Version 必須是明確穩定版本,不可使用 floating/range/prerelease:$version" | |
| } | |
| "GAMEINPUT_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "Microsoft.GameInput pinned version: $version" | |
| if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_STEP_SUMMARY)) { | |
| "Microsoft.GameInput pinned version: ``$version``" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 | |
| } | |
| - name: 檢查 Microsoft.GameInput 最新穩定版本(非阻擋) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $indexUrl = "https://api.nuget.org/v3-flatcontainer/microsoft.gameinput/index.json" | |
| try { | |
| $index = Invoke-RestMethod -Uri $indexUrl -ErrorAction Stop | |
| $stableVersions = @( | |
| $index.versions | | |
| Where-Object { $_ -match '^\d+\.\d+\.\d+(?:\.\d+)?$' } | | |
| Sort-Object { [version]$_ } | |
| ) | |
| if ($stableVersions.Count -eq 0) { | |
| throw "NuGet index 未回傳可解析的 stable 版本。" | |
| } | |
| $latest = $stableVersions[-1] | |
| Write-Host "Microsoft.GameInput pinned version: $env:GAMEINPUT_VERSION" | |
| Write-Host "Microsoft.GameInput latest stable: $latest" | |
| if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_STEP_SUMMARY)) { | |
| @( | |
| '', | |
| '### Microsoft.GameInput 版本資訊', | |
| '', | |
| "- Pinned: ``$env:GAMEINPUT_VERSION``", | |
| "- Latest stable: ``$latest``" | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 | |
| } | |
| if ($env:GAMEINPUT_VERSION -ne $latest) { | |
| Write-Warning "Microsoft.GameInput pinned version ($env:GAMEINPUT_VERSION) 不是 NuGet 最新 stable ($latest);本檢查不阻擋發佈。" | |
| } | |
| } catch { | |
| Write-Warning "無法查詢 Microsoft.GameInput 最新 stable 版本;本檢查不阻擋發佈。原因:$($_.Exception.Message)" | |
| if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_STEP_SUMMARY)) { | |
| @( | |
| '', | |
| '### Microsoft.GameInput 版本資訊', | |
| '', | |
| "- Pinned: ``$env:GAMEINPUT_VERSION``", | |
| '- Latest stable: 查詢失敗(非阻擋)' | |
| ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 | |
| } | |
| } | |
| - name: 執行專案發佈(Publish) | |
| working-directory: ./src/InputBox | |
| run: > | |
| dotnet publish -c Release -r win-x64 | |
| --self-contained true | |
| -p:PublishSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:PublishReadyToRun=true | |
| -o out | |
| - name: 驗證單檔發佈輸出 | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $exePath = Join-Path "${{ env.PUBLISH_DIR }}" "${{ env.EXE_NAME }}" | |
| if (-not (Test-Path $exePath)) { | |
| Write-Error "找不到單檔發佈執行檔:$exePath" | |
| } | |
| $forbiddenNames = @( | |
| "InputBox.GameInput.Native.dll", | |
| "gameinput.dll" | |
| ) | |
| $publishFiles = Get-ChildItem -LiteralPath "${{ env.PUBLISH_DIR }}" -Recurse -File | |
| foreach ($name in $forbiddenNames) { | |
| $match = $publishFiles | Where-Object { $_.Name.Equals($name, [StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1 | |
| if ($match) { | |
| Write-Error "單檔發佈輸出不應包含可見的 GameInput sidecar:$($match.FullName)" | |
| } | |
| } | |
| Write-Host "單檔發佈輸出驗證通過:未留下 InputBox.GameInput.Native.dll 或 gameinput.dll sidecar。" | |
| - name: 自動產生第三方套件授權聲明(nuget-license) | |
| shell: pwsh | |
| run: | | |
| dotnet tool restore | |
| Write-Host "掃描專案 NuGet 套件並匯出 ThirdPartyNotices.txt……" | |
| # -i:指定專案檔路徑。 | |
| # -o:指定輸出格式為 Markdown(讓純文字檔內有整齊的表格排版)。 | |
| # -fo:指定輸出的檔案名稱(File Output)。 | |
| dotnet tool run nuget-license -i ./src/InputBox/InputBox.csproj -o Markdown -fo ThirdPartyNotices.txt -ignore Microsoft.GameInput | |
| if (Test-Path "ThirdPartyNotices.txt") { | |
| Write-Host "✅ 成功產生專案依賴套件的 ThirdPartyNotices.txt!" | |
| } else { | |
| Write-Warning "⚠️ 未產生 ThirdPartyNotices.txt,可能是因為專案目前沒有依賴任何第三方套件。" | |
| } | |
| - name: 補寫 InputWeave.GameInput 授權聲明 | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $noticePath = "ThirdPartyNotices.txt" | |
| if (-not (Test-Path $noticePath)) { | |
| New-Item -ItemType File -Path $noticePath -Force | Out-Null | |
| } | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '## InputWeave.GameInput' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 套件:InputWeave.GameInput 0.0.1' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 來源:https://github.com/rubujo/InputWeave.GameInput/tree/72697db594b8f1863bd3fbf5db75ff69b40e3364' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 作者:https://github.com/rubujo' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 貢獻者:https://github.com/rubujo/InputWeave.GameInput/graphs/contributors' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 封裝來源:https://github.com/rubujo/InputWeave.GameInput/commit/72697db594b8f1863bd3fbf5db75ff69b40e3364' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 套件檔案:eng/nuget/InputWeave.GameInput.0.0.1.nupkg' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- SHA256: 451b9d65143eafea130ec3492f12a991b29c62d53509e74b856894848f329f5e' | |
| Add-Content -Path $noticePath -Encoding utf8 -Value '- 授權:Creative Commons CC0 1.0 Universal(SPDX:CC0-1.0);詳見本 Licenses 目錄中的 InputWeave.GameInput_LICENSE.txt 與 https://github.com/rubujo/InputWeave.GameInput/blob/72697db594b8f1863bd3fbf5db75ff69b40e3364/LICENSE。' | |
| - name: 產生執行檔的雜湊值(SHA256) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $exePath = Join-Path -Path "${{ env.PUBLISH_DIR }}" -ChildPath "${{ env.EXE_NAME }}" | |
| if (Test-Path $exePath) { | |
| $hash = (Get-FileHash $exePath -Algorithm SHA256).Hash | |
| "SHA256_HASH=$hash" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "成功產生雜湊值:$hash" | |
| } else { | |
| Write-Error "找不到執行檔($exePath),請檢查發佈步驟!" | |
| } | |
| - name: 打包並壓縮發佈檔案(ZIP Archive) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $tagName = "${{ github.ref_name }}" | |
| $zipName = "InputBox-$tagName-win-x64.zip" | |
| "ZIP_FILENAME=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| # 1. 建立外層目錄(dist)、內層目錄(dist/InputBox)、授權子目錄與 redist 子目錄。 | |
| $distDir = "./dist" | |
| $packDir = Join-Path $distDir "InputBox" | |
| $licensesDir = Join-Path $packDir "Licenses" | |
| $redistDir = Join-Path $packDir "redist" | |
| if (Test-Path $distDir) { | |
| Remove-Item -Recurse -Force $distDir | |
| } | |
| New-Item -ItemType Directory -Force -Path $packDir | Out-Null | |
| New-Item -ItemType Directory -Force -Path $licensesDir | Out-Null | |
| New-Item -ItemType Directory -Force -Path $redistDir | Out-Null | |
| # 2. 複製執行檔與專案文件到內層目錄。 | |
| # - 執行檔與 README.md 放根目錄。 | |
| # - 專案授權(LICENSE)保留根目錄(業界慣例:使用者預期在根層找到它)。 | |
| # - ThirdPartyNotices.txt 放入 Licenses/ 子目錄。 | |
| Copy-Item -Path (Join-Path ${{ env.PUBLISH_DIR }} ${{ env.EXE_NAME }}) -Destination $packDir | |
| $rootDocs = @("LICENSE", "README.md") | |
| foreach ($doc in $rootDocs) { | |
| if (Test-Path $doc) { | |
| Copy-Item -Path $doc -Destination $packDir | |
| } | |
| } | |
| if (Test-Path "ThirdPartyNotices.txt") { | |
| Copy-Item -Path "ThirdPartyNotices.txt" -Destination $licensesDir | |
| } | |
| Copy-Item -Path ".\eng\nuget\InputWeave.GameInput_LICENSE.txt" -Destination (Join-Path $licensesDir "InputWeave.GameInput_LICENSE.txt") | |
| # 3. 動態抓取 .NET 官方授權檔案(含最相近 Tag/分支 Fallback)。 | |
| Write-Host "=== 開始動態下載 .NET 官方授權檔案 ===" | |
| $runtimeInfo = dotnet --list-runtimes | |
| $majorVersion = "${{ env.DOTNET_MAJOR_VERSION }}" | |
| $runtimeLine = $runtimeInfo | Where-Object { $_ -match "^Microsoft\.NETCore\.App ($majorVersion\.\d+\.\d+)" } | Select-Object -Last 1 | |
| $githubHeaders = @{ | |
| 'User-Agent' = 'InputBox-Release-Workflow' | |
| 'X-GitHub-Api-Version' = '2022-11-28' | |
| } | |
| function Get-TagStageRank { | |
| param([string]$TagName) | |
| if ($TagName -match '(?i)preview') { return 3 } | |
| if ($TagName -match '(?i)rc') { return 2 } | |
| if ($TagName -match '(?i)rtm|servicing') { return 1 } | |
| return 0 | |
| } | |
| function Get-ClosestDotnetTag { | |
| param( | |
| [string]$Repo, | |
| [version]$TargetVersion | |
| ) | |
| $tagsApiUrl = "https://api.github.com/repos/dotnet/$Repo/tags?per_page=100" | |
| try { | |
| $tagResponse = Invoke-RestMethod -Uri $tagsApiUrl -Headers $githubHeaders -ErrorAction Stop | |
| } catch { | |
| Write-Warning "⚠️ 無法查詢 dotnet/$Repo 的 Tag 清單:$($_.Exception.Message)" | |
| return $null | |
| } | |
| $candidates = foreach ($tagItem in $tagResponse) { | |
| if ($tagItem.name -match '^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)') { | |
| [pscustomobject]@{ | |
| Name = $tagItem.name | |
| Major = [int]$Matches['major'] | |
| Minor = [int]$Matches['minor'] | |
| Patch = [int]$Matches['patch'] | |
| StageRank = Get-TagStageRank -TagName $tagItem.name | |
| } | |
| } | |
| } | |
| $nearest = $candidates | | |
| Where-Object { $_.Major -eq $TargetVersion.Major -and $_.Minor -eq $TargetVersion.Minor } | | |
| Sort-Object ` | |
| @{ Expression = { [Math]::Abs($_.Patch - $TargetVersion.Build) } }, ` | |
| @{ Expression = { $_.StageRank } }, ` | |
| @{ Expression = { $_.Patch }; Descending = $true } | | |
| Select-Object -First 1 | |
| return $nearest.Name | |
| } | |
| function Save-GitHubFileWithFallback { | |
| param( | |
| [string]$Repo, | |
| [string[]]$Refs, | |
| [string]$File, | |
| [string]$OutPath | |
| ) | |
| foreach ($ref in ($Refs | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique)) { | |
| $rawUrl = "https://raw.githubusercontent.com/dotnet/$Repo/$ref/$File" | |
| $metadataUrl = "https://api.github.com/repos/dotnet/$Repo/contents/$File?ref=$([uri]::EscapeDataString($ref))" | |
| Write-Host "嘗試下載:dotnet/$Repo@$ref -> $File" | |
| try { | |
| Invoke-WebRequest -Uri $rawUrl -OutFile $OutPath -Headers $githubHeaders -ErrorAction Stop | |
| Write-Host "✅ 成功儲存為 $(Split-Path $OutPath -Leaf)(來源:$ref / raw)" | |
| return $true | |
| } catch { | |
| try { | |
| $metadata = Invoke-RestMethod -Uri $metadataUrl -Headers $githubHeaders -ErrorAction Stop | |
| if (-not [string]::IsNullOrWhiteSpace($metadata.download_url)) { | |
| Invoke-WebRequest -Uri $metadata.download_url -OutFile $OutPath -Headers $githubHeaders -ErrorAction Stop | |
| Write-Host "✅ 成功儲存為 $(Split-Path $OutPath -Leaf)(來源:$ref / api)" | |
| return $true | |
| } | |
| } catch { | |
| # Ignore and continue to the next candidate below. | |
| } | |
| Write-Warning "⚠️ 無法從 $ref 下載 $File,繼續嘗試下一個候選來源。" | |
| } | |
| } | |
| return $false | |
| } | |
| if ($runtimeLine -match "^Microsoft\.NETCore\.App (\d+\.\d+\.\d+)") { | |
| $version = $Matches[1] | |
| $tag = "v$version" | |
| $targetVersion = [version]$version | |
| $releaseBranch = "release/$($targetVersion.Major).$($targetVersion.Minor)" | |
| Write-Host "偵測到目前使用的 .NET Runtime 版本為:$version,優先使用 GitHub 標籤:$tag" | |
| $filesToDownload = @( | |
| @{ Repo = "runtime"; File = "LICENSE.TXT"; Out = "DOTNET_RUNTIME_LICENSE.txt" }, | |
| @{ Repo = "runtime"; File = "THIRD-PARTY-NOTICES.TXT"; Out = "DOTNET_RUNTIME_THIRD_PARTY_NOTICES.txt" }, | |
| @{ Repo = "winforms"; File = "LICENSE.TXT"; Out = "DOTNET_WINFORMS_LICENSE.txt" }, | |
| @{ Repo = "winforms"; File = "THIRD-PARTY-NOTICES.TXT"; Out = "DOTNET_WINFORMS_THIRD_PARTY_NOTICES.txt" } | |
| ) | |
| $repoRefCandidates = @{} | |
| foreach ($repoName in ($filesToDownload.Repo | Select-Object -Unique)) { | |
| $closestTag = Get-ClosestDotnetTag -Repo $repoName -TargetVersion $targetVersion | |
| if ($closestTag) { | |
| Write-Host "dotnet/$repoName 最相近的可用 Tag 候選:$closestTag" | |
| } else { | |
| Write-Warning "⚠️ dotnet/$repoName 找不到符合 $($targetVersion.Major).$($targetVersion.Minor).x 的 Tag,將直接退回分支。" | |
| } | |
| $repoRefCandidates[$repoName] = @($tag, $closestTag, $releaseBranch, 'main') | |
| } | |
| foreach ($item in $filesToDownload) { | |
| $outPath = Join-Path $licensesDir $item.Out | |
| $downloaded = Save-GitHubFileWithFallback -Repo $item.Repo -Refs $repoRefCandidates[$item.Repo] -File $item.File -OutPath $outPath | |
| if (-not $downloaded) { | |
| Write-Warning "⚠️ 最終仍無法取得 $($item.Out)。" | |
| } | |
| } | |
| } else { | |
| Write-Warning "❌ 無法解析 .NET Runtime 的版本號,請確認安裝步驟。" | |
| } | |
| # 4. 打包 Microsoft GameInput redist 與授權檔案。 | |
| Write-Host "=== 開始處理 Microsoft GameInput redist 與授權 ===" | |
| $gameInputPackageDir = Join-Path $env:USERPROFILE ".nuget\packages\microsoft.gameinput\$env:GAMEINPUT_VERSION" | |
| $gameInputRedistSource = Join-Path $gameInputPackageDir "redist\GameInputRedist.msi" | |
| $gameInputLicenseSource = Join-Path $gameInputPackageDir "LICENSE.txt" | |
| $gameInputNoticeSource = Join-Path $gameInputPackageDir "NOTICE.txt" | |
| if (-not (Test-Path $gameInputRedistSource)) { | |
| Write-Error "找不到 Microsoft.GameInput redist:$gameInputRedistSource" | |
| } | |
| if (-not (Test-Path $gameInputLicenseSource)) { | |
| Write-Error "找不到 Microsoft.GameInput 授權檔:$gameInputLicenseSource" | |
| } | |
| if (-not (Test-Path $gameInputNoticeSource)) { | |
| Write-Error "找不到 Microsoft.GameInput notice 檔:$gameInputNoticeSource" | |
| } | |
| Copy-Item -Path $gameInputRedistSource -Destination (Join-Path $redistDir "GameInputRedist.msi") | |
| Copy-Item -Path $gameInputLicenseSource -Destination (Join-Path $licensesDir "Microsoft.GameInput_LICENSE.txt") | |
| Copy-Item -Path $gameInputNoticeSource -Destination (Join-Path $licensesDir "Microsoft.GameInput_NOTICE.txt") | |
| $redistHash = (Get-FileHash (Join-Path $redistDir "GameInputRedist.msi") -Algorithm SHA256).Hash | |
| "GAMEINPUT_REDIST_SHA256=$redistHash" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| $redistReadme = @( | |
| 'Microsoft GameInput Redistributable', | |
| '===================================', | |
| '', | |
| "Version: $env:GAMEINPUT_VERSION", | |
| 'Included file: GameInputRedist.msi', | |
| "SHA256: $redistHash", | |
| '', | |
| 'InputBox bundles this redistributable for optional manual installation only.', | |
| 'InputBox does not execute this installer automatically and does not require administrator elevation during normal application startup.', | |
| '', | |
| 'License: see ../Licenses/Microsoft.GameInput_LICENSE.txt', | |
| 'Third-party notices: see ../Licenses/Microsoft.GameInput_NOTICE.txt' | |
| ) -join "`n" | |
| $redistReadme | Out-File -FilePath (Join-Path $redistDir "README.txt") -Encoding utf8 | |
| if (Test-Path "ThirdPartyNotices.txt") { | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '' | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '## Microsoft GameInput Redistributable' | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '' | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value "- Package: Microsoft.GameInput $env:GAMEINPUT_VERSION" | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value "- Source: https://www.nuget.org/packages/Microsoft.GameInput/$env:GAMEINPUT_VERSION" | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '- Included file: redist/GameInputRedist.msi' | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value "- SHA256: $redistHash" | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '- Install policy: bundled for optional manual installation; InputBox does not install it automatically.' | |
| Add-Content -Path "ThirdPartyNotices.txt" -Encoding utf8 -Value '- License: see Microsoft.GameInput_LICENSE.txt and Microsoft.GameInput_NOTICE.txt in this Licenses directory.' | |
| Copy-Item -Path "ThirdPartyNotices.txt" -Destination $licensesDir -Force | |
| } else { | |
| Write-Error "找不到 ThirdPartyNotices.txt,無法加入 Microsoft GameInput redist 聲明。" | |
| } | |
| Write-Host "=======================================" | |
| # 5. 執行壓縮並驗證 ZIP 內容。 | |
| Compress-Archive -Path $packDir -DestinationPath $zipName -CompressionLevel Optimal | |
| Add-Type -AssemblyName System.IO.Compression.FileSystem | |
| $zip = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path $zipName)) | |
| try { | |
| $entries = $zip.Entries | ForEach-Object { $_.FullName } | |
| $requiredEntries = @( | |
| 'InputBox/InputBox.exe', | |
| 'InputBox/LICENSE', | |
| 'InputBox/README.md', | |
| 'InputBox/Licenses/ThirdPartyNotices.txt', | |
| 'InputBox/Licenses/InputWeave.GameInput_LICENSE.txt', | |
| 'InputBox/Licenses/Microsoft.GameInput_LICENSE.txt', | |
| 'InputBox/Licenses/Microsoft.GameInput_NOTICE.txt', | |
| 'InputBox/redist/GameInputRedist.msi', | |
| 'InputBox/redist/README.txt' | |
| ) | |
| foreach ($entry in $requiredEntries) { | |
| if ($entries -notcontains $entry) { | |
| Write-Error "ZIP 缺少必要項目:$entry" | |
| } | |
| } | |
| $forbiddenPatterns = @( | |
| 'GameInputNet_LICENSE.txt', | |
| 'UsbVendorsLibrary_LICENSE.txt', | |
| 'usb_ids_LICENSE.txt', | |
| 'gameinput.dll', | |
| 'InputBox.GameInput.Native.dll' | |
| ) | |
| foreach ($pattern in $forbiddenPatterns) { | |
| if ($entries | Where-Object { $_ -like "*$pattern" }) { | |
| Write-Error "ZIP 包含禁止項目:$pattern" | |
| } | |
| } | |
| $thirdParty = $zip.GetEntry('InputBox/Licenses/ThirdPartyNotices.txt') | |
| if ($null -eq $thirdParty) { | |
| Write-Error "ZIP 缺少 ThirdPartyNotices.txt,無法驗證 InputWeave.GameInput 聲明。" | |
| } | |
| $reader = [System.IO.StreamReader]::new($thirdParty.Open()) | |
| try { | |
| $thirdPartyText = $reader.ReadToEnd() | |
| if ($thirdPartyText -notmatch 'InputWeave\.GameInput') { | |
| Write-Error "ThirdPartyNotices.txt 缺少 InputWeave.GameInput 聲明。" | |
| } | |
| } finally { | |
| $reader.Dispose() | |
| } | |
| } finally { | |
| $zip.Dispose() | |
| } | |
| - name: 檢查 VirusTotal API Key 設定 | |
| id: vt-config | |
| shell: pwsh | |
| env: | |
| VT_API_KEY: ${{ secrets.VT_API_KEY }} | |
| run: | | |
| if ([string]::IsNullOrWhiteSpace($env:VT_API_KEY)) { | |
| "enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Warning "未設定 VT_API_KEY,略過 VirusTotal 掃描。" | |
| } else { | |
| "enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "已偵測到 VT_API_KEY,將進行 VirusTotal 掃描。" | |
| } | |
| - name: 建立 GitHub Release | |
| id: release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| files: ${{ env.ZIP_FILENAME }} | |
| # 根據 Commit 與 PR 自動生成版本說明(Changelog)。 | |
| generate_release_notes: true | |
| body: | | |
| ## 📦 輸入框發佈摘要 | |
| - **版本號碼**:`${{ github.ref_name }}` | |
| - **目標平台**:Windows x64 | |
| - **執行環境**:.NET 10(已隨附 Runtime,無需額外安裝) | |
| - **執行檔案的雜湊值(SHA256)**:`${{ env.SHA256_HASH }}` | |
| - **GameInput Redist(選用手動安裝)SHA256**:`${{ env.GAMEINPUT_REDIST_SHA256 }}` | |
| --- | |
| > [!NOTE] | |
| > 壓縮檔內含 Microsoft GameInput Redistributable (`redist/GameInputRedist.msi`) 供需要者手動安裝;InputBox 不會自動執行該安裝程式,GameInput 不可用時會退避至 XInput。 | |
| > [!NOTE] | |
| > 本版本由 GitHub Actions 自動建置並發佈。詳細變更內容請參考下方的自動產生說明。 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 使用 VirusTotal 掃描並更新 Release | |
| if: ${{ steps.vt-config.outputs.enabled == 'true' }} | |
| continue-on-error: true | |
| uses: cssnr/virustotal-action@v2 | |
| with: | |
| vt_api_key: ${{ secrets.VT_API_KEY }} | |
| release_id: ${{ steps.release.outputs.id }} | |
| rate_limit: 4 | |
| sha256: true | |
| update_release: true | |
| release_heading: "🛡️ **VirusTotal 掃描結果:**" | |
| summary: true |