Repository navigation
Release Tauri App #36
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: Release Electron App | |
| # 由 semantic-release 创建的 v* tag 触发:构建 Electron 安装包并附加到对应 Release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| # 兜底触发:semantic-release 若以内置 GITHUB_TOKEN 推 tag, | |
| # GitHub 防递归机制不会派发 push 事件,导致本工作流从不运行、 | |
| # Release 资产为空(latest.yml 404)。Release 发布事件可正常触发补跑。 | |
| release: | |
| types: [published] | |
| # 手动兜底:在 UI 选择对应 tag 运行,用于补发历史版本资产 | |
| workflow_dispatch: | |
| # 上传构建产物到 GitHub Release 需要写权限 | |
| permissions: | |
| contents: write | |
| # 同一 tag 的多个触发源(push/release)串行执行,避免重复上传竞争 | |
| concurrency: | |
| group: release-electron-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| lint-test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| cache-dependency-path: client/package-lock.json | |
| - run: cd client && npm ci | |
| - run: cd client && npm run lint | |
| - run: cd client && npm run test -- --run | |
| build: | |
| needs: lint-test | |
| # 本产品为 Windows-only(electron-builder.yml 仅定义 win target)。 | |
| # macOS 无有效构建目标必然报 ERR_ELECTRON_BUILDER_CANNOT_EXECUTE, | |
| # 且默认 fail-fast 会连带取消 Windows 构建,导致 release/同步步骤永不执行。 | |
| # 如未来支持 mac,需补回带签名的 mac target 后再加入 matrix。 | |
| strategy: | |
| matrix: | |
| os: [windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| # Electron 打包 + 上传 + CDN 同步:上限 90 分钟 | |
| timeout-minutes: 90 | |
| steps: | |
| # lfs: true 必需:app-icon.png 等 PNG 由 Git LFS 托管(.gitattributes: | |
| # *.png filter=lfs),不拉取则仅为 131 字节指针文件, | |
| # electron-builder 图标转换(PNG→ICO)解码失败, | |
| # 报 ERR_ELECTRON_BUILDER_CANNOT_EXECUTE。 | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| cache-dependency-path: client/package-lock.json | |
| - run: cd client && npm ci | |
| # 进程环回原生模块(ADR-001):windows-latest 自带 VS 2022。 | |
| # 必须走 native:build 脚本(本地与 CI 同一入口):直接用 client 下的 | |
| # electron-rebuild 会命中旧版 node-gyp,在 CI 报 | |
| # "Could not find any Visual Studio installation"(它仍在找 VS 2013/2015)。 | |
| # continue-on-error:它是可选增强(运行时未加载到则降级为端点环回), | |
| # 不应因其编译失败而阻断整条发布流水线 | |
| - name: Build native process-audio module | |
| continue-on-error: true | |
| run: | | |
| cd client | |
| npm run native:install | |
| npm run native:build | |
| - name: Check native module artifact | |
| shell: bash | |
| run: | | |
| cd client | |
| if [ -f native/process-audio/build/Release/process_audio.node ]; then | |
| echo "进程环回原生模块已就绪" | |
| else | |
| echo "::warning::进程环回原生模块未构建成功,本包仅支持端点环回采集" | |
| fi | |
| # --publish never:发布由下方 release job 统一负责,避免 electron-builder | |
| # 因检测到 tag 而自行尝试发布(github provider 无 GH_TOKEN 会报错) | |
| # | |
| # 更新源(CDN)已直接固化在 electron-builder.yml 的 generic provider 中, | |
| # 本地打包与 CI 打包产物的 app-update.yml 完全一致,不再用 | |
| # DOWNLOAD_BASE_URL sed 改写(行锚定模式在 yml 变动后会静默失效,已移除) | |
| - name: Build Electron app | |
| shell: bash | |
| run: cd client && npm run electron:build -- --publish never | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-${{ matrix.os }} | |
| # latest*.yml 与 *.blockmap 是 electron-updater 自动更新的必需文件 | |
| path: | | |
| client/release/*.exe | |
| client/release/*.blockmap | |
| client/release/latest*.yml | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # secrets 上下文**不可用于 if 条件**(仅限 env/with/run),否则整个 | |
| # workflow 文件解析失败、所有触发立即 0 秒报错。 | |
| # 故先将条件所需值注入 job 级 env,再在 step 的 if 中用 env.* 引用。 | |
| env: | |
| DOWNLOAD_BASE_URL: ${{ secrets.DOWNLOAD_BASE_URL }} | |
| OSS_BUCKET: ${{ secrets.OSS_BUCKET }} | |
| # 仅作存在性判定的布尔标志,避免将密钥本身提到 job 级 env | |
| HAS_ALIYUN_AK: ${{ secrets.ALIYUN_ACCESS_KEY_ID != '' }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: release-* | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| files: release-*/* | |
| # --------------------------------------------------------------- | |
| # 同步 Windows 安装包至自建服务器(国内主下载源 + 自动更新源) | |
| # latest.json 供官网下载页动态展示版本/直链;latest.yml 供 electron-updater | |
| # --------------------------------------------------------------- | |
| - name: Generate latest.json for website | |
| id: meta | |
| run: | | |
| YML=release-windows-latest/latest.yml | |
| VERSION=$(grep -m1 '^version:' "$YML" | awk '{print $2}') | |
| FILE=$(grep -m1 '^path:' "$YML" | sed 's/^path: //') | |
| SIZE=$(stat -c%s "release-windows-latest/$FILE") | |
| SHA256=$(sha256sum "release-windows-latest/$FILE" | awk '{print $1}') | |
| DATE=$(date -u +%F) | |
| printf '{"version":"%s","fileName":"%s","size":%s,"sha256":"%s","releaseDate":"%s"}\n' \ | |
| "$VERSION" "$FILE" "$SIZE" "$SHA256" "$DATE" > release-windows-latest/latest.json | |
| cat release-windows-latest/latest.json | |
| # 供下方 CDN 预热步骤拼接 URL | |
| echo "file=$FILE" >> "$GITHUB_OUTPUT" | |
| - name: Ensure downloads directory on server | |
| uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| script: mkdir -p /opt/Entropydecrease/downloads | |
| # 先传二进制(exe/blockmap),后传元数据(yml/json): | |
| # 避免 latest.yml 率先就位时指向尚未上传完成的安装包(更新 404 窗口期) | |
| - name: Upload installer binaries to server | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| source: release-windows-latest/*.exe,release-windows-latest/*.blockmap | |
| target: /opt/Entropydecrease/downloads | |
| strip_components: 1 | |
| - name: Publish update metadata (last, avoids 404 window) | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| source: release-windows-latest/latest.yml,release-windows-latest/latest.json | |
| target: /opt/Entropydecrease/downloads | |
| strip_components: 1 | |
| # --------------------------------------------------------------- | |
| # 同步至 OSS(CDN 源站) | |
| # 为何需要 OSS:ECS 出网仅约 6Mbps,而 CDN 预热需**每个边缘节点各 | |
| # 拉一份完整文件**,回源总量 = 节点数 x 172MB,以 ECS 为源站时 | |
| # 预热耗时达几十分钟至数小时且会打满带宽影响 API。 | |
| # OSS 出网带宽极大,可使预热降至分钟级且完全不占用 ECS。 | |
| # | |
| # 路径与 ECS 保持一致(downloads/ 前缀),因此无需改变 | |
| # DOWNLOAD_BASE_URL,也无需改前端与 electron-updater 配置。 | |
| # ECS 同步仍保留:存量客户端的更新源仍指向 ECS,且作为兜底。 | |
| # --------------------------------------------------------------- | |
| - name: Upload to OSS (CDN origin) | |
| if: env.OSS_BUCKET != '' && env.HAS_ALIYUN_AK == 'true' | |
| env: | |
| AK_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }} | |
| AK_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} | |
| OSS_ENDPOINT_IN: ${{ secrets.OSS_ENDPOINT }} | |
| FILE_NAME: ${{ steps.meta.outputs.file }} | |
| run: | | |
| set -eo pipefail | |
| # 未配置 OSS_ENDPOINT 时默认杭州(与 ECS 同地域) | |
| OSS_ENDPOINT="${OSS_ENDPOINT_IN:-oss-cn-hangzhou.aliyuncs.com}" | |
| curl -sL https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz -o /tmp/aliyun.tgz | |
| tar -xzf /tmp/aliyun.tgz -C /tmp | |
| # 新版 aliyun CLI 的 oss 子命令已移除 -i/-k 短参数(会报 | |
| # unknown flag -i),故改用非交互式 configure 写入 profile 携带凭证 | |
| /tmp/aliyun configure set --profile ossci --mode AK --region cn-hangzhou \ | |
| --access-key-id "$AK_ID" --access-key-secret "$AK_SECRET" | |
| oss_cp() { | |
| /tmp/aliyun oss cp "$1" "oss://$OSS_BUCKET/downloads/$2" \ | |
| --force -e "$OSS_ENDPOINT" --profile ossci | |
| } | |
| cd release-windows-latest | |
| # 同 ECS 的顺序约定:先二进制后元数据,避免 latest.yml 率先就位 | |
| # 而指向尚未上传完成的安装包 | |
| oss_cp "$FILE_NAME" "$FILE_NAME" | |
| oss_cp "$FILE_NAME.blockmap" "$FILE_NAME.blockmap" | |
| oss_cp latest.yml latest.yml | |
| oss_cp latest.json latest.json | |
| echo "OSS sync done: oss://$OSS_BUCKET/downloads/" | |
| # 保留策略:仅保留最近 3 个版本的安装包,防止磁盘增长 | |
| - name: Prune old versions (keep last 3) | |
| uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| script: | | |
| cd /opt/Entropydecrease/downloads | |
| ls -t -- *.exe 2>/dev/null | tail -n +4 | while read -r f; do | |
| rm -f -- "$f" "$f.blockmap" | |
| done | |
| # --------------------------------------------------------------- | |
| # CDN 预热:大文件在 CDN 上按分片缓存,未被请求过的分片不在缓存中, | |
| # 首批用户会退化为实时回源而受源站带宽限制。 | |
| # 实测:已缓存分片 10.5MB/s,未缓存分片仅 106KB/s(相差约 95 倍), | |
| # 故预热不是可选优化而是大文件分发的必要步骤。 | |
| # 预热为异步任务,API 返回后后台继续拉取,此处不等待完成。 | |
| # --------------------------------------------------------------- | |
| - name: Prefetch CDN cache | |
| # 仅在已切换至 CDN 且配置了预热凭证时执行;未配置则跳过 | |
| if: env.DOWNLOAD_BASE_URL != '' && env.HAS_ALIYUN_AK == 'true' | |
| # 预热失败不应推翻已成功的发布,仅标记警告 | |
| continue-on-error: true | |
| env: | |
| AK_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }} | |
| AK_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} | |
| FILE_NAME: ${{ steps.meta.outputs.file }} | |
| run: | | |
| set -eo pipefail | |
| # 上一步可能被跳过(未配 OSS),故不假设 CLI 已存在 | |
| if [ ! -x /tmp/aliyun ]; then | |
| curl -sL https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz -o /tmp/aliyun.tgz | |
| tar -xzf /tmp/aliyun.tgz -C /tmp | |
| fi | |
| /tmp/aliyun configure set --profile cdnci --mode AK --region cn-hangzhou \ | |
| --access-key-id "$AK_ID" --access-key-secret "$AK_SECRET" | |
| BASE="${DOWNLOAD_BASE_URL%/}" | |
| # 预热安装包与 blockmap(差量更新依赖); | |
| # latest.yml/json 按设计不缓存,且仅几百字节,无需预热 | |
| OBJECTS="$BASE/$FILE_NAME"$'\n'"$BASE/$FILE_NAME.blockmap" | |
| echo "Prefetching:" | |
| echo "$OBJECTS" | |
| /tmp/aliyun cdn PushObjectCache \ | |
| --profile cdnci \ | |
| --ObjectPath "$OBJECTS" \ | |
| --Area domestic | |
| # --------------------------------------------------------------- | |
| # 安装包体积监控:防止包体积无序膨胀影响用户下载体验与 CDN 成本。 | |
| # 当 .exe 超过阈值时仅输出警告(不阻塞发布),以便团队及时排查 | |
| # 是否误打包了大文件或依赖。 | |
| # --------------------------------------------------------------- | |
| - name: Check installer size | |
| env: | |
| # 安装包体积警告阈值(单位:字节),200MB | |
| SIZE_THRESHOLD: 209715200 | |
| run: | | |
| set -eo pipefail | |
| echo '## 📦 安装包体积报告' >> "$GITHUB_STEP_SUMMARY" | |
| echo '' >> "$GITHUB_STEP_SUMMARY" | |
| echo "| 文件 | 大小 |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "|------|------|" >> "$GITHUB_STEP_SUMMARY" | |
| for exe in release-windows-latest/*.exe; do | |
| [ -f "$exe" ] || continue | |
| SIZE=$(stat -c%s "$exe") | |
| NAME=$(basename "$exe") | |
| # 转换为人类可读的 MB | |
| SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $SIZE / 1048576}") | |
| THRESHOLD_MB=$(awk "BEGIN {printf \"%.0f\", $SIZE_THRESHOLD / 1048576}") | |
| echo "| $NAME | ${SIZE_MB} MB |" >> "$GITHUB_STEP_SUMMARY" | |
| if [ "$SIZE" -gt "$SIZE_THRESHOLD" ]; then | |
| echo "::warning::${NAME} 体积 ${SIZE_MB}MB 超过阈值 ${THRESHOLD_MB}MB,请检查是否误打包了大文件或依赖" | |
| echo '' >> "$GITHUB_STEP_SUMMARY" | |
| echo "> ⚠️ **${NAME}** 体积 ${SIZE_MB} MB 超过阈值 ${THRESHOLD_MB} MB,请关注包体积膨胀" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "${NAME}: ${SIZE_MB} MB(阈值 ${THRESHOLD_MB} MB,正常)" | |
| fi | |
| done | |
| # --------------------------------------------------------------- | |
| # 发布结果校验:"空 Release"已静默发生过两次(v0.27.0 因 macOS 腿 | |
| # 拖垮构建、v0.28.7 因 workflow 解析失败),而两次都是事后手工才 | |
| # 发现的——此时 electron-updater 拉不到 latest.yml,自动更新静默断链。 | |
| # 此步骤使该故障在 CI 中即时暴露。 | |
| # --------------------------------------------------------------- | |
| - name: Verify release assets | |
| if: startsWith(github.ref, 'refs/tags/') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| # release job 不做 checkout(仅下载 artifact),gh 无法从 git 仓库 | |
| # 推断仓库而报 "not a git repository",故必须显式指定 GH_REPO | |
| GH_REPO: ${{ github.repository }} | |
| TAG: ${{ github.ref_name }} | |
| run: | | |
| set -eo pipefail | |
| names=$(gh release view "$TAG" --json assets --jq '.assets[].name') | |
| echo "Assets of $TAG:" | |
| echo "$names" | |
| for required in '\.exe$' '\.exe\.blockmap$' '^latest\.yml$'; do | |
| if ! echo "$names" | grep -qE "$required"; then | |
| echo "::error::Release $TAG 缺少匹配 $required 的资产,自动更新将不可用" | |
| exit 1 | |
| fi | |
| done | |
| echo "发布资产完整校验通过" |