mirror of
https://mirror.skon.top/github.com/ILoveBingLu/CipherTalk
synced 2026-04-21 05:40:19 +08:00
546 lines
18 KiB
YAML
546 lines
18 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
jobs:
|
|
prepare-meta:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
AI_API_URL: ${{ vars.AI_API_URL }}
|
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }}
|
|
FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }}
|
|
FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }}
|
|
FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }}
|
|
FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }}
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
tag: ${{ steps.version.outputs.tag }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Read package version
|
|
id: version
|
|
shell: pwsh
|
|
run: |
|
|
$pkg = Get-Content package.json -Raw | ConvertFrom-Json
|
|
"version=$($pkg.version)" >> $env:GITHUB_OUTPUT
|
|
"tag=${env:GITHUB_REF_NAME}" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Validate tag matches package version
|
|
shell: pwsh
|
|
run: |
|
|
$expectedTag = "v${{ steps.version.outputs.version }}"
|
|
$actualTag = "${{ steps.version.outputs.tag }}"
|
|
if ($actualTag -ne $expectedTag) {
|
|
Write-Error "Tag $actualTag does not match package.json version $expectedTag"
|
|
exit 1
|
|
}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Generate force update manifest
|
|
run: npm run build:force-update-manifest
|
|
|
|
- name: Generate release context
|
|
env:
|
|
RELEASE_TAG: ${{ steps.version.outputs.tag }}
|
|
run: npm run build:release-context
|
|
|
|
- name: Upload release metadata
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: |
|
|
release/force-update.json
|
|
release/release-context.json
|
|
if-no-files-found: error
|
|
|
|
build-windows:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
needs: prepare-meta
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
AI_API_KEY: ${{ secrets.AI_API_KEY }}
|
|
AI_API_URL: ${{ vars.AI_API_URL }}
|
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }}
|
|
FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }}
|
|
FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }}
|
|
FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }}
|
|
FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Rebuild native modules
|
|
run: npx electron-rebuild
|
|
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Generate embedded release body
|
|
run: npm run build:release-body
|
|
|
|
- name: Build app
|
|
run: npm run build:win
|
|
|
|
- name: Validate build artifacts
|
|
shell: pwsh
|
|
run: |
|
|
$version = "${{ needs.prepare-meta.outputs.version }}"
|
|
$installer = "release/CipherTalk-$version-Setup.exe"
|
|
if (-not (Test-Path $installer)) {
|
|
Write-Error "Installer not found: $installer"
|
|
exit 1
|
|
}
|
|
|
|
- name: Upload release binaries
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-binaries-windows
|
|
path: |
|
|
release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.exe
|
|
release/latest.yml
|
|
if-no-files-found: error
|
|
|
|
build-macos:
|
|
runs-on: macos-latest
|
|
environment: 软件发布
|
|
needs: prepare-meta
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
AI_API_KEY: ${{ secrets.AI_API_KEY }}
|
|
AI_API_URL: ${{ vars.AI_API_URL }}
|
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }}
|
|
FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }}
|
|
FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }}
|
|
FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }}
|
|
FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Rebuild native modules
|
|
run: npx electron-rebuild
|
|
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Generate embedded release body
|
|
run: npm run build:release-body
|
|
|
|
- name: Build mac app
|
|
run: npm run build:mac
|
|
|
|
- name: Validate build artifacts
|
|
run: |
|
|
version="${{ needs.prepare-meta.outputs.version }}"
|
|
dmg="release/CipherTalk-${version}-Setup.dmg"
|
|
if [ ! -f "$dmg" ]; then
|
|
echo "DMG not found: $dmg" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Upload release binaries
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-binaries-macos
|
|
path: |
|
|
release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.dmg
|
|
release/latest-mac.yml
|
|
if-no-files-found: error
|
|
|
|
generate-release-body:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
needs: prepare-meta
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
AI_API_KEY: ${{ secrets.AI_API_KEY }}
|
|
AI_API_URL: ${{ vars.AI_API_URL }}
|
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }}
|
|
FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }}
|
|
FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }}
|
|
FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }}
|
|
FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Generate AI release body
|
|
run: npm run build:release-body
|
|
|
|
- name: Validate release body
|
|
shell: pwsh
|
|
run: |
|
|
if (-not (Test-Path "release/release-body.md")) {
|
|
Write-Error "release-body.md not found"
|
|
exit 1
|
|
}
|
|
|
|
- name: Upload release body
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-body
|
|
path: release/release-body.md
|
|
if-no-files-found: error
|
|
|
|
publish-github-release:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
needs:
|
|
- prepare-meta
|
|
- build-windows
|
|
- build-macos
|
|
- generate-release-body
|
|
steps:
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Download release binaries
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-binaries-windows
|
|
path: release
|
|
|
|
- name: Download macOS release binaries
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-binaries-macos
|
|
path: release
|
|
|
|
- name: Download release body
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-body
|
|
path: release
|
|
|
|
- name: Validate release package
|
|
shell: pwsh
|
|
run: |
|
|
$version = "${{ needs.prepare-meta.outputs.version }}"
|
|
$installer = "release/CipherTalk-$version-Setup.exe"
|
|
$macInstaller = "release/CipherTalk-$version-Setup.dmg"
|
|
if (-not (Test-Path $installer)) {
|
|
Write-Error "Installer not found: $installer"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path $macInstaller)) {
|
|
Write-Error "macOS installer not found: $macInstaller"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path "release/latest.yml")) {
|
|
Write-Error "latest.yml not found"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path "release/latest-mac.yml")) {
|
|
Write-Error "latest-mac.yml not found"
|
|
exit 1
|
|
}
|
|
$sizeLines = @(Select-String -Path "release/latest.yml" -Pattern '^\s+size:\s+\d+\s*$')
|
|
if ($sizeLines.Count -ne 1) {
|
|
Write-Error "latest.yml should contain exactly one size entry, found $($sizeLines.Count)"
|
|
exit 1
|
|
}
|
|
$macSizeLines = @(Select-String -Path "release/latest-mac.yml" -Pattern '^\s+size:\s+\d+\s*$')
|
|
if ($macSizeLines.Count -ne 1) {
|
|
Write-Error "latest-mac.yml should contain exactly one size entry, found $($macSizeLines.Count)"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path "release/force-update.json")) {
|
|
Write-Error "force-update.json not found"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path "release/release-body.md")) {
|
|
Write-Error "release-body.md not found"
|
|
exit 1
|
|
}
|
|
|
|
$latestYml = Get-Content "release/latest.yml" -Raw
|
|
$shaMatch = [regex]::Match($latestYml, '(?m)^sha512:\s*(.+)$')
|
|
if (-not $shaMatch.Success) {
|
|
Write-Error "sha512 not found in latest.yml"
|
|
exit 1
|
|
}
|
|
|
|
$hashHex = (Get-FileHash -Algorithm SHA512 $installer).Hash
|
|
$hashBytes = [byte[]]::new($hashHex.Length / 2)
|
|
for ($i = 0; $i -lt $hashHex.Length; $i += 2) {
|
|
$hashBytes[$i / 2] = [Convert]::ToByte($hashHex.Substring($i, 2), 16)
|
|
}
|
|
$actualSha512 = [Convert]::ToBase64String($hashBytes)
|
|
$expectedSha512 = $shaMatch.Groups[1].Value.Trim()
|
|
|
|
if ($actualSha512 -ne $expectedSha512) {
|
|
Write-Error "latest.yml sha512 does not match installer"
|
|
exit 1
|
|
}
|
|
|
|
$latestMacYml = Get-Content "release/latest-mac.yml" -Raw
|
|
$macShaMatch = [regex]::Match($latestMacYml, '(?m)^sha512:\s*(.+)$')
|
|
if (-not $macShaMatch.Success) {
|
|
Write-Error "sha512 not found in latest-mac.yml"
|
|
exit 1
|
|
}
|
|
|
|
$macHashHex = (Get-FileHash -Algorithm SHA512 $macInstaller).Hash
|
|
$macHashBytes = [byte[]]::new($macHashHex.Length / 2)
|
|
for ($i = 0; $i -lt $macHashHex.Length; $i += 2) {
|
|
$macHashBytes[$i / 2] = [Convert]::ToByte($macHashHex.Substring($i, 2), 16)
|
|
}
|
|
$actualMacSha512 = [Convert]::ToBase64String($macHashBytes)
|
|
$expectedMacSha512 = $macShaMatch.Groups[1].Value.Trim()
|
|
|
|
if ($actualMacSha512 -ne $expectedMacSha512) {
|
|
Write-Error "latest-mac.yml sha512 does not match dmg"
|
|
exit 1
|
|
}
|
|
|
|
- name: Create or update GitHub Release
|
|
uses: softprops/action-gh-release@v2.5.0
|
|
with:
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
tag_name: ${{ needs.prepare-meta.outputs.tag }}
|
|
name: CipherTalk v${{ needs.prepare-meta.outputs.version }}
|
|
body_path: release/release-body.md
|
|
fail_on_unmatched_files: false
|
|
overwrite_files: false
|
|
files: |
|
|
release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.exe
|
|
release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.dmg
|
|
release/latest.yml
|
|
release/latest-mac.yml
|
|
release/force-update.json
|
|
|
|
mirror-r2:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
needs:
|
|
- prepare-meta
|
|
- build-windows
|
|
- build-macos
|
|
env:
|
|
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
|
|
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
|
|
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
|
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
|
steps:
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Download release binaries
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-binaries-windows
|
|
path: release
|
|
|
|
- name: Download macOS release binaries
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-binaries-macos
|
|
path: release
|
|
|
|
- name: Ensure AWS CLI
|
|
shell: pwsh
|
|
run: |
|
|
if (-not (Get-Command aws -ErrorAction SilentlyContinue)) {
|
|
choco install awscli -y
|
|
}
|
|
aws --version
|
|
|
|
- name: Upload mirrored files to R2
|
|
shell: pwsh
|
|
run: |
|
|
if (-not $env:R2_ACCOUNT_ID -or -not $env:R2_BUCKET_NAME -or -not $env:R2_ACCESS_KEY_ID -or -not $env:R2_SECRET_ACCESS_KEY) {
|
|
Write-Error "R2 secrets are required"
|
|
exit 1
|
|
}
|
|
|
|
$env:AWS_ACCESS_KEY_ID = $env:R2_ACCESS_KEY_ID
|
|
$env:AWS_SECRET_ACCESS_KEY = $env:R2_SECRET_ACCESS_KEY
|
|
$env:AWS_DEFAULT_REGION = "auto"
|
|
$endpoint = "https://$($env:R2_ACCOUNT_ID).r2.cloudflarestorage.com"
|
|
$bucket = "s3://$($env:R2_BUCKET_NAME)"
|
|
$version = "${{ needs.prepare-meta.outputs.version }}"
|
|
$currentInstaller = "CipherTalk-$version-Setup.exe"
|
|
$currentMacInstaller = "CipherTalk-$version-Setup.dmg"
|
|
|
|
$existingInstallers = aws s3 ls $bucket --endpoint-url $endpoint | ForEach-Object {
|
|
$line = $_.ToString().Trim()
|
|
if ($line -match 'CipherTalk-.*-Setup\.exe$') {
|
|
($line -split '\s+')[-1]
|
|
}
|
|
} | Where-Object { $_ }
|
|
|
|
$existingMacInstallers = aws s3 ls $bucket --endpoint-url $endpoint | ForEach-Object {
|
|
$line = $_.ToString().Trim()
|
|
if ($line -match 'CipherTalk-.*-Setup\.dmg$') {
|
|
($line -split '\s+')[-1]
|
|
}
|
|
} | Where-Object { $_ }
|
|
|
|
$existingBlockmaps = aws s3 ls $bucket --endpoint-url $endpoint | ForEach-Object {
|
|
$line = $_.ToString().Trim()
|
|
if ($line -match '\.blockmap$') {
|
|
($line -split '\s+')[-1]
|
|
}
|
|
} | Where-Object { $_ }
|
|
|
|
foreach ($installer in $existingInstallers) {
|
|
if ($installer -ne $currentInstaller) {
|
|
aws s3 rm "$bucket/$installer" --endpoint-url $endpoint
|
|
}
|
|
}
|
|
|
|
foreach ($installer in $existingMacInstallers) {
|
|
if ($installer -ne $currentMacInstaller) {
|
|
aws s3 rm "$bucket/$installer" --endpoint-url $endpoint
|
|
}
|
|
}
|
|
|
|
foreach ($blockmap in $existingBlockmaps) {
|
|
aws s3 rm "$bucket/$blockmap" --endpoint-url $endpoint
|
|
}
|
|
|
|
aws s3 cp "release/$currentInstaller" "$bucket/$currentInstaller" --endpoint-url $endpoint
|
|
aws s3 cp "release/$currentMacInstaller" "$bucket/$currentMacInstaller" --endpoint-url $endpoint
|
|
aws s3 cp "release/latest.yml" "$bucket/latest.yml" --endpoint-url $endpoint
|
|
aws s3 cp "release/latest-mac.yml" "$bucket/latest-mac.yml" --endpoint-url $endpoint
|
|
aws s3 cp "release/force-update.json" "$bucket/force-update.json" --endpoint-url $endpoint
|
|
|
|
notify-telegram-success:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
needs:
|
|
- prepare-meta
|
|
- generate-release-body
|
|
- publish-github-release
|
|
env:
|
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
TELEGRAM_CHAT_IDS: ${{ vars.TELEGRAM_CHAT_IDS }}
|
|
TELEGRAM_RELEASE_COVER_URL: ${{ vars.TELEGRAM_RELEASE_COVER_URL }}
|
|
RELEASE_VERSION: ${{ needs.prepare-meta.outputs.version }}
|
|
TELEGRAM_NOTIFY_MODE: success
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Download release metadata
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-meta
|
|
path: release
|
|
|
|
- name: Download release body
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: release-body
|
|
path: release
|
|
|
|
- name: Notify Telegram success
|
|
run: npm run notify:telegram
|
|
continue-on-error: true
|
|
|
|
notify-failure:
|
|
runs-on: windows-latest
|
|
environment: 软件发布
|
|
if: failure()
|
|
env:
|
|
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
TELEGRAM_CHAT_IDS: ${{ vars.TELEGRAM_CHAT_IDS }}
|
|
TELEGRAM_RELEASE_COVER_URL: ${{ vars.TELEGRAM_RELEASE_COVER_URL }}
|
|
RELEASE_VERSION: ${{ github.ref_name }}
|
|
TELEGRAM_NOTIFY_MODE: failure
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 22.12.0
|
|
cache: npm
|
|
|
|
- name: Notify Telegram failure
|
|
run: npm run notify:telegram
|
|
continue-on-error: true
|