From 19a6d2dd349e3c15e042fbeedb11ae90e84d0381 Mon Sep 17 00:00:00 2001 From: paisley <8197966+su8su@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:39:46 +0800 Subject: [PATCH] v0.3.1 (#674) --- .github/workflows/release.yml | 260 +++++++++++++++++----------------- package.json | 4 +- 2 files changed, 132 insertions(+), 132 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82e728c6b..bb80ec1cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,147 +83,147 @@ jobs: if: matrix.platform == 'win' run: pnpm run package:win - # # Detect release channel from tag to skip code signing for alpha/beta builds - # - name: Detect Windows release channel - # if: matrix.platform == 'win' - # id: win-channel - # shell: bash - # run: | - # if [[ "${{ github.ref }}" == refs/tags/v* ]]; then - # TAG="${GITHUB_REF#refs/tags/v}" - # else - # TAG="${{ github.event.inputs.version }}" - # fi - # if [[ "$TAG" =~ (alpha|beta) ]]; then - # echo "is_stable=false" >> $GITHUB_OUTPUT - # echo "Channel: prerelease ($TAG) — skipping code signing" - # else - # echo "is_stable=true" >> $GITHUB_OUTPUT - # echo "Channel: stable ($TAG) — will sign" - # fi + # Detect release channel from tag to skip code signing for alpha/beta builds + - name: Detect Windows release channel + if: matrix.platform == 'win' + id: win-channel + shell: bash + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG="${{ github.event.inputs.version }}" + fi + if [[ "$TAG" =~ (alpha|beta) ]]; then + echo "is_stable=false" >> $GITHUB_OUTPUT + echo "Channel: prerelease ($TAG) — skipping code signing" + else + echo "is_stable=true" >> $GITHUB_OUTPUT + echo "Channel: stable ($TAG) — will sign" + fi - # - name: Validate unsigned Windows artifacts before SignPath - # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' - # shell: pwsh - # run: | - # $unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File - # if (-not $unsignedExeFiles) { - # throw "No unsigned .exe files found in release/ before SignPath upload" - # } - # $unsignedCount = $unsignedExeFiles.Count - # "UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append - # Write-Host "Found $unsignedCount unsigned .exe file(s):" - # $unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" } + - name: Validate unsigned Windows artifacts before SignPath + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + shell: pwsh + run: | + $unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File + if (-not $unsignedExeFiles) { + throw "No unsigned .exe files found in release/ before SignPath upload" + } + $unsignedCount = $unsignedExeFiles.Count + "UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append + Write-Host "Found $unsignedCount unsigned .exe file(s):" + $unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" } - # - name: Upload unsigned Windows artifacts for SignPath - # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' - # id: upload-unsigned-windows-artifact - # uses: actions/upload-artifact@v4 - # with: - # name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }} - # path: release/*.exe - # retention-days: 1 + - name: Upload unsigned Windows artifacts for SignPath + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + id: upload-unsigned-windows-artifact + uses: actions/upload-artifact@v4 + with: + name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }} + path: release/*.exe + retention-days: 1 - # - name: Sign Windows artifacts via SignPath - # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' - # id: signpath-sign-windows - # uses: signpath/github-action-submit-signing-request@v2 - # with: - # api-token: ${{ secrets.SIGNPATH_API_TOKEN }} - # organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3" - # project-slug: "ValueCell" - # signing-policy-slug: "ValueCell-sign" - # github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} - # wait-for-completion: true - # output-artifact-directory: release/signed + - name: Sign Windows artifacts via SignPath + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + id: signpath-sign-windows + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3" + project-slug: "ValueCell" + signing-policy-slug: "ValueCell-sign" + github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: release/signed - # - name: Replace unsigned executables with signed ones - # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' - # shell: pwsh - # run: | - # Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}" - # $signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse - # if (-not $signedExeFiles) { - # throw "No signed .exe files found in release/signed" - # } - # $signedCount = $signedExeFiles.Count - # if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) { - # throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" - # } - # foreach ($file in $signedExeFiles) { - # Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force - # } - # $finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File - # if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) { - # throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" - # } - # Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))" + - name: Replace unsigned executables with signed ones + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + shell: pwsh + run: | + Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}" + $signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse + if (-not $signedExeFiles) { + throw "No signed .exe files found in release/signed" + } + $signedCount = $signedExeFiles.Count + if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) { + throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" + } + foreach ($file in $signedExeFiles) { + Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force + } + $finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File + if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) { + throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" + } + Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))" - # # Code signing changes the .exe binary, invalidating the sha512 hash that - # # electron-builder wrote into latest.yml during the initial build. - # # Recalculate the hash for each signed .exe and patch the yml files so - # # electron-updater can verify the download successfully. - # # - # # Actual latest.yml structure (from electron-builder NSIS): - # # files: - # # - url: ClawX-0.2.4-win-x64.exe ← files[] entries have url/sha512/size - # # sha512: - # # size: 430775882 - # # path: ClawX-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!) - # # sha512: - # # releaseDate: '...' - # - name: Update latest.yml sha512 after code signing - # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' - # shell: pwsh - # run: | - # $ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" } - # $exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File + # Code signing changes the .exe binary, invalidating the sha512 hash that + # electron-builder wrote into latest.yml during the initial build. + # Recalculate the hash for each signed .exe and patch the yml files so + # electron-updater can verify the download successfully. + # + # Actual latest.yml structure (from electron-builder NSIS): + # files: + # - url: ClawX-0.2.4-win-x64.exe ← files[] entries have url/sha512/size + # sha512: + # size: 430775882 + # path: ClawX-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!) + # sha512: + # releaseDate: '...' + - name: Update latest.yml sha512 after code signing + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + shell: pwsh + run: | + $ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" } + $exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File - # foreach ($yml in $ymlFiles) { - # $content = Get-Content $yml.FullName -Raw - # $modified = $false + foreach ($yml in $ymlFiles) { + $content = Get-Content $yml.FullName -Raw + $modified = $false - # foreach ($exe in $exeFiles) { - # # Compute new sha512 (base64) for the signed exe - # $hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512 - # $hashBytes = [byte[]]::new($hash.Hash.Length / 2) - # for ($i = 0; $i -lt $hashBytes.Length; $i++) { - # $hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16) - # } - # $newSha512 = [Convert]::ToBase64String($hashBytes) - # $newSize = (Get-Item $exe.FullName).Length - # $escapedName = [Regex]::Escape($exe.Name) + foreach ($exe in $exeFiles) { + # Compute new sha512 (base64) for the signed exe + $hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512 + $hashBytes = [byte[]]::new($hash.Hash.Length / 2) + for ($i = 0; $i -lt $hashBytes.Length; $i++) { + $hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16) + } + $newSha512 = [Convert]::ToBase64String($hashBytes) + $newSize = (Get-Item $exe.FullName).Length + $escapedName = [Regex]::Escape($exe.Name) - # # 1) files[] entries: url: \n sha512: \n size: - # $urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)" - # if ($content -match $urlPattern) { - # $content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}" - # $modified = $true - # Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize" - # } + # 1) files[] entries: url: \n sha512: \n size: + $urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)" + if ($content -match $urlPattern) { + $content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}" + $modified = $true + Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize" + } - # # 2) Top-level entry: path: \nsha512: \n (no size field) - # $pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+" - # if ($content -match $pathPattern) { - # $content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}" - # $modified = $true - # Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512" - # } - # } + # 2) Top-level entry: path: \nsha512: \n (no size field) + $pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+" + if ($content -match $pathPattern) { + $content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}" + $modified = $true + Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512" + } + } - # if ($modified) { - # Set-Content -Path $yml.FullName -Value $content -NoNewline - # Write-Host "Saved updated $($yml.Name)" - # } - # } + if ($modified) { + Set-Content -Path $yml.FullName -Value $content -NoNewline + Write-Host "Saved updated $($yml.Name)" + } + } - # Write-Host "" - # Write-Host "=== Final yml contents ===" - # foreach ($yml in $ymlFiles) { - # Write-Host "--- $($yml.Name) ---" - # Get-Content $yml.FullName - # Write-Host "" - # } + Write-Host "" + Write-Host "=== Final yml contents ===" + foreach ($yml in $ymlFiles) { + Write-Host "--- $($yml.Name) ---" + Get-Content $yml.FullName + Write-Host "" + } # Linux specific steps - name: Build Linux diff --git a/package.json b/package.json index 1417f89d9..f8b82f764 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clawx", - "version": "0.3.1-alpha.0", + "version": "0.3.1", "pnpm": { "onlyBuiltDependencies": [ "@discordjs/opus", @@ -141,4 +141,4 @@ "zx": "^8.8.5" }, "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268" -} +} \ No newline at end of file