Skip to content

fix(gameinput): 更新 InputWeave 修正版套件 #93

fix(gameinput): 更新 InputWeave 修正版套件

fix(gameinput): 更新 InputWeave 修正版套件 #93

Workflow file for this run

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