name: 构建与发布 on: workflow_dispatch: push: branches: - main tags: - 'v*' pull_request: branches: - main permissions: contents: read concurrency: group: build-${{ github.ref }} cancel-in-progress: ${{ github.ref_type != 'tag' }} env: # 仓库的 .npmrc 指向 npmmirror,GitHub runner 在境外,走官方源更稳 npm_config_registry: https://registry.npmjs.org/ ELECTRON_MIRROR: https://github.com/electron/electron/releases/download/ jobs: validate: name: 单测与集成测试 runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - name: 校验发布标签与版本号一致 if: github.ref_type == 'tag' run: node -e "const p=require('./package.json'); const expected='v'+p.version; if(process.env.GITHUB_REF_NAME!==expected){throw new Error('标签应为 '+expected+',实际 '+process.env.GITHUB_REF_NAME)}" - name: 安装依赖 run: npm ci # 下载完整日志需要仓库管理员权限。失败详情转成注解,这样没有管理员权限 # 的人也能在提交页直接看到是哪条断言挂了,不必去翻日志。 - name: 单元测试 shell: bash run: | npm test > /tmp/unit.log 2>&1 || { # 注解有长度上限,只挑失败条目与错误细节,别把通过的用例也塞进去 grep -E '^(✖|not ok)' /tmp/unit.log | head -20 echo "::error title=单元测试失败::$(grep -E '^(✖|not ok)|AssertionError|actual:|expected:|operator:|^\s+at ' /tmp/unit.log \ | head -40 | cut -c1-300 | sed 's/%/%25/g; s/\r//g' | awk '{printf "%s%%0A", $0}')" exit 1 } tail -6 /tmp/unit.log # Electron 集成测试要开真实窗口,无头环境靠 xvfb 提供 X server - name: Electron 集成测试 shell: bash run: | annotate() { echo "::error title=$1::$(tail -80 "$2" | sed 's/%/%25/g; s/\r//g' | awk '{printf "%s%%0A", $0}')" exit 1 } sudo apt-get update > /tmp/apt.log 2>&1 || annotate "apt 更新失败" /tmp/apt.log sudo apt-get install -y xvfb libnss3 libatk1.0-0t64 libatk-bridge2.0-0t64 \ libcups2t64 libgbm1 libasound2t64 libgtk-3-0t64 >> /tmp/apt.log 2>&1 \ || annotate "运行库安装失败" /tmp/apt.log # runner 上的 chrome-sandbox 拿不到 root:root 4755,SUID 沙箱起不来。 # 只在 CI 关沙箱,不要把这个开关带进构建产物。 for suite in startup download cover annotation reader-features library-notes ai-scope; do echo "::group::$suite" xvfb-run -a npx electron --no-sandbox "src/_test/electron/$suite.integration.js" \ > "/tmp/$suite.log" 2>&1 || { echo "::endgroup::"; annotate "集成测试失败 $suite" "/tmp/$suite.log"; } tail -3 "/tmp/$suite.log" echo "::endgroup::" done package: name: 打包 ${{ matrix.platform }} ${{ matrix.arch }} needs: validate strategy: fail-fast: false matrix: include: - platform: windows arch: x64 runner: windows-2025 - platform: macos arch: arm64 runner: macos-15 - platform: linux arch: x64 runner: ubuntu-24.04 - platform: linux arch: arm64 runner: ubuntu-24.04-arm runs-on: ${{ matrix.runner }} timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - name: 缓存 Electron 运行时 uses: actions/cache@v4 with: path: node_modules/.cache key: electron-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('package.json') }} - name: 安装依赖 run: npm ci - name: 构建并校验发布件 run: npm run release -- --platform ${{ matrix.platform }} --arch ${{ matrix.arch }} - name: 上传发布件 uses: actions/upload-artifact@v4 with: name: peoplelib-${{ matrix.platform }}-${{ matrix.arch }} path: dist/release/${{ matrix.platform }}-${{ matrix.arch }} if-no-files-found: error compression-level: 0 retention-days: 30 release: name: 发布 GitHub Release if: github.event_name == 'push' && github.ref_type == 'tag' needs: package runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 24 - name: 核对标签指向当前提交 shell: bash run: | set -euo pipefail expected="v$(node -p "require('./package.json').version")" test "$GITHUB_REF_NAME" = "$expected" test "$(git rev-parse "refs/tags/$GITHUB_REF_NAME^{commit}")" = "$GITHUB_SHA" - name: 下载各平台发布件 uses: actions/download-artifact@v4 with: pattern: peoplelib-* path: dist/release-downloads - name: 回验校验和并汇总 run: npm run release -- --verify dist/release-downloads # 先建草稿再转正式,避免上传中途失败留下一个资产不全的 Release - name: 创建 Release 并上传 shell: bash env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail tag="$GITHUB_REF_NAME" if gh release view "$tag" >/dev/null 2>&1; then gh release edit "$tag" --draft --verify-tag else gh release create "$tag" --draft --verify-tag --generate-notes --title "PeopleLib $tag" fi gh release upload "$tag" dist/release-upload/* --clobber gh release edit "$tag" --draft=false