fix(workflow): correct branch name and resolve test failure #127
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: Build and Release | |
| # ================================================================== | |
| # 使用说明 | |
| # ================================================================== | |
| # 本工作流用于构建 Papyrus 桌面应用并发布 GitHub Release。 | |
| # | |
| # 【触发方式】 | |
| # 1. 自动触发:push 代码到 main/develop/TS后端重写/v2.0.0beta.x 分支, | |
| # 或 push v* 开头的 tag(如 v2.0.0)时自动运行。 | |
| # 2. 手动触发:进入 Actions → Build and Release → Run workflow。 | |
| # | |
| # 【手动发布步骤】 | |
| # 1. 进入 Actions 页面,选择 "Build and Release" | |
| # 2. 点击右上角 "Run workflow" | |
| # 3. 勾选 "Create GitHub release"(必须勾选,否则只构建不上传) | |
| # 4. (可选)填写 "Release tag",留空则使用当前分支名 | |
| # 5. 选择 "Create as draft release": | |
| # - 勾选(默认)= 创建草稿 Release,需要进 GitHub Releases 页面手动点 Publish | |
| # - 取消勾选 = 直接发布正式 Release,对外立即可见 | |
| # 6. 点击 "Run workflow" 等待构建完成 | |
| # | |
| # 【注意事项】 | |
| # - 自动触发(push tag)时,默认创建草稿 Release | |
| # - 只有 v2.0.0beta.x 分支的 push 会自动执行 release job | |
| # - tag 中包含 alpha/beta/rc 时,会自动标记为 prerelease | |
| # ================================================================== | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| - 'TS后端重写' | |
| - 'v2.0.0-beta.7' | |
| tags: | |
| - 'v*' | |
| pull_request: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| # 总开关:是否执行 release job(上传产物并创建 GitHub Release) | |
| # 默认关闭,防止误触发;勾选后才真正发版 | |
| create_release: | |
| description: 'Create GitHub release' | |
| required: false | |
| default: false | |
| type: boolean | |
| # 手动指定 tag;留空则使用当前分支名或触发事件的 tag | |
| tag: | |
| description: 'Release tag (e.g. v2.0.0)' | |
| required: false | |
| type: string | |
| # Draft 开关:勾选 = 创建草稿 Release(需手动点 Publish) | |
| # 取消勾选 = 直接发布正式 Release(对外立即可见) | |
| draft: | |
| description: 'Create as draft release' | |
| required: false | |
| default: true | |
| type: boolean | |
| env: | |
| APP_NAME: "Papyrus" | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| ELECTRON_BUILDER_BINARIES_MIRROR: "https://npmmirror.com/mirrors/electron-builder-binaries/" | |
| ELECTRON_MIRROR: "https://npmmirror.com/mirrors/electron/" | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.platform }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: "windows" | |
| target: "win" | |
| os: windows-latest | |
| artifact: "Papyrus-Win-x64" | |
| - platform: "macos" | |
| target: "mac" | |
| os: macos-latest | |
| artifact: "Papyrus-Apple-Silicon-arm64" | |
| - platform: "linux" | |
| target: "linux" | |
| os: ubuntu-latest | |
| artifact: "Papyrus-Linux-x64" | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: '24' | |
| cache: 'npm' | |
| cache-dependency-path: | | |
| package-lock.json | |
| frontend/package-lock.json | |
| backend/package-lock.json | |
| - name: Install Root Dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Install Frontend Dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Install Backend Dependencies | |
| working-directory: backend | |
| run: npm ci | |
| - name: Audit Production Dependencies | |
| shell: bash | |
| run: | | |
| echo "=== Frontend production audit ===" | |
| (cd frontend && npm audit --omit=dev --audit-level=high) | |
| echo "=== Backend production audit ===" | |
| (cd backend && npm audit --omit=dev --audit-level=high) | |
| echo "=== Root production audit ===" | |
| npm audit --omit=dev --audit-level=high | |
| echo "Production dependency audit passed." | |
| - name: Typecheck Backend | |
| working-directory: backend | |
| run: npm run typecheck | |
| - name: Typecheck Frontend | |
| working-directory: frontend | |
| run: npm run typecheck | |
| - name: Test Backend | |
| working-directory: backend | |
| shell: bash | |
| run: | | |
| npm test -- --verbose 2>&1 | tee test-output.log | |
| TEST_EXIT=${PIPESTATUS[0]} | |
| if [ $TEST_EXIT -ne 0 ]; then | |
| echo "Tests failed with exit code $TEST_EXIT" | |
| exit $TEST_EXIT | |
| fi | |
| - name: Upload Test Output on Failure | |
| if: failure() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: test-output-${{ matrix.platform }} | |
| path: backend/test-output.log | |
| retention-days: 1 | |
| - name: Build Frontend | |
| working-directory: frontend | |
| run: npm run build | |
| - name: Build Backend | |
| working-directory: backend | |
| run: npm run build | |
| - name: Prune Backend Dependencies | |
| working-directory: backend | |
| run: npm prune --production | |
| - name: Verify Backend Dependencies | |
| shell: bash | |
| run: | | |
| REQUIRED_PKGS=( | |
| "backend/node_modules/fastify/package.json" | |
| "backend/node_modules/@fastify/cors/package.json" | |
| "backend/node_modules/@fastify/rate-limit/package.json" | |
| "backend/node_modules/openai/package.json" | |
| "backend/node_modules/chokidar/package.json" | |
| "backend/node_modules/uuid/package.json" | |
| "backend/node_modules/gray-matter/package.json" | |
| "backend/node_modules/markdown-it/package.json" | |
| ) | |
| MISSING=0 | |
| for pkg in "${REQUIRED_PKGS[@]}"; do | |
| if [ ! -f "$pkg" ]; then | |
| echo "Error: required package not found after prune: $pkg" | |
| MISSING=1 | |
| else | |
| echo "OK: $pkg" | |
| fi | |
| done | |
| if [ $MISSING -ne 0 ]; then | |
| echo "Production dependency verification failed." | |
| exit 1 | |
| fi | |
| echo "All production dependencies verified." | |
| - name: Verify Backend Build | |
| shell: bash | |
| run: | | |
| SERVER_JS="backend/dist/api/server.js" | |
| if [ ! -f "$SERVER_JS" ]; then | |
| echo "Error: Backend entry not found at $SERVER_JS" | |
| exit 1 | |
| fi | |
| SIZE=$(du -h "$SERVER_JS" | cut -f1) | |
| echo "Backend entry built: $SERVER_JS ($SIZE)" | |
| - name: Verify Frontend Build | |
| shell: bash | |
| run: | | |
| INDEX_HTML="frontend/dist/index.html" | |
| if [ ! -f "$INDEX_HTML" ]; then | |
| echo "Error: Frontend index.html not found" | |
| exit 1 | |
| fi | |
| echo "Frontend built successfully" | |
| - name: Cache Electron Builder | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 | |
| with: | |
| path: | | |
| ~/.cache/electron | |
| ~/.cache/electron-builder | |
| ~/Library/Caches/electron | |
| ~/Library/Caches/electron-builder | |
| key: electron-${{ matrix.platform }}-${{ hashFiles('package-lock.json') }} | |
| restore-keys: | | |
| electron-${{ matrix.platform }}- | |
| # macOS signing: uncomment the env block below and set the secrets when an Apple Developer ID is available. | |
| # CSC_LINK, CSC_KEY_PASSWORD, APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID | |
| - name: Build Electron App | |
| run: npx electron-builder --${{ matrix.target }} --publish=never --config .electron-builder.config.js | |
| - name: Verify Electron Build | |
| shell: bash | |
| run: | | |
| echo "Build outputs:" | |
| find dist-electron/ -maxdepth 1 -type f | sort | |
| # Verify the expected installer/package exists and has reasonable size | |
| MIN_SIZE=52428800 # 50 MB sanity check | |
| case "${{ matrix.platform }}" in | |
| windows) | |
| INSTALLER=$(ls dist-electron/*-Setup.exe 2>/dev/null | head -1) | |
| if [ -z "$INSTALLER" ]; then | |
| echo "Error: No Windows installer found" | |
| exit 1 | |
| fi | |
| ;; | |
| macos) | |
| INSTALLER=$(ls dist-electron/*.dmg 2>/dev/null | head -1) | |
| if [ -z "$INSTALLER" ]; then | |
| echo "Error: No macOS dmg found" | |
| exit 1 | |
| fi | |
| ;; | |
| linux) | |
| INSTALLER=$(ls dist-electron/*.AppImage 2>/dev/null | head -1) | |
| DEB=$(ls dist-electron/*.deb 2>/dev/null | head -1) | |
| if [ -z "$INSTALLER" ] && [ -z "$DEB" ]; then | |
| echo "Error: No Linux package found (neither AppImage nor deb)" | |
| exit 1 | |
| fi | |
| ;; | |
| esac | |
| # Sanity check: reject suspiciously small artifacts | |
| for ARTIFACT in "$INSTALLER" "$DEB"; do | |
| if [ -n "$ARTIFACT" ] && [ -f "$ARTIFACT" ]; then | |
| SIZE_BYTES=$(wc -c < "$ARTIFACT") | |
| SIZE_MB=$((SIZE_BYTES / 1048576)) | |
| if [ "$SIZE_BYTES" -lt "$MIN_SIZE" ]; then | |
| echo "Error: Artifact suspiciously small (${SIZE_MB}MB): $ARTIFACT" | |
| exit 1 | |
| fi | |
| echo "Artifact OK: $ARTIFACT (${SIZE_MB}MB)" | |
| fi | |
| done | |
| echo "Build verification passed" | |
| - name: Verify Packaged Dependencies | |
| run: node scripts/verify-packaged-deps.js | |
| - name: Upload Artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: | | |
| dist-electron/*-Setup.exe | |
| dist-electron/*.dmg | |
| dist-electron/*.AppImage | |
| dist-electron/*.deb | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| generate-notes: | |
| name: Generate Release Notes | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 | |
| with: | |
| node-version: '24' | |
| - name: Generate Release Notes | |
| run: | | |
| node scripts/generate-release-notes.js origin/main HEAD | |
| echo "" | |
| echo "=== Generated Release Notes ===" | |
| cat RELEASE_NOTES.md | |
| echo "===============================" | |
| - name: Upload Release Notes | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 | |
| with: | |
| name: release-notes | |
| path: RELEASE_NOTES.md | |
| retention-days: 7 | |
| # ------------------------------------------------------------------ | |
| # Release job: disabled by default. Enable manually via workflow_dispatch | |
| # with create_release=true when ready to publish. | |
| # ------------------------------------------------------------------ | |
| release: | |
| name: Create Release | |
| needs: [build, generate-notes] | |
| runs-on: ubuntu-latest | |
| # 执行 release 的三种场景: | |
| # 1. 手动触发且勾选了 create_release | |
| # 2. push 了 v* 开头的 tag | |
| # 3. push 到 v2.0.0beta.6 分支(保留兼容) | |
| if: github.event.inputs.create_release == 'true' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine Tag | |
| id: tag | |
| run: | | |
| TAG="${{ github.event.inputs.tag || github.ref_name }}" | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| echo "Using tag: $TAG" | |
| - name: Download All Artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 | |
| with: | |
| path: artifacts | |
| pattern: Papyrus-* | |
| - name: Download Release Notes | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 | |
| with: | |
| name: release-notes | |
| path: . | |
| - name: List Artifacts | |
| run: | | |
| echo "Build artifacts:" | |
| find artifacts/ -type f | sort | |
| - name: Determine Release Type | |
| id: release_type | |
| run: | | |
| TAG="${{ steps.tag.outputs.tag }}" | |
| if [[ "$TAG" == *"alpha"* ]] || [[ "$TAG" == *"beta"* ]] || [[ "$TAG" == *"rc"* ]]; then | |
| echo "is_prerelease=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_prerelease=false" >> $GITHUB_OUTPUT | |
| fi | |
| # 决定 Release 是否为草稿状态: | |
| # - 手动触发时,采用 UI 上 draft 开关的值 | |
| # - 自动触发(push tag 等)时,默认创建草稿,防止误发布 | |
| - name: Determine Draft Flag | |
| id: draft_flag | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "is_draft=${{ github.event.inputs.draft }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_draft=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Create Draft Release | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 | |
| with: | |
| files: | | |
| artifacts/Papyrus-Win-x64/**/*-Setup.exe | |
| artifacts/Papyrus-Apple-Silicon-arm64/**/*.dmg | |
| artifacts/Papyrus-Linux-x64/**/*.AppImage | |
| artifacts/Papyrus-Linux-x64/**/*.deb | |
| tag_name: ${{ steps.tag.outputs.tag }} | |
| name: ${{ steps.tag.outputs.tag }} | |
| body_path: RELEASE_NOTES.md | |
| # draft 状态由上面的 Determine Draft Flag step 动态决定 | |
| draft: ${{ steps.draft_flag.outputs.is_draft == 'true' }} | |
| prerelease: ${{ steps.release_type.outputs.is_prerelease == 'true' }} | |
| # 决定release notes的生成方式,默认为true,自动根据提交记录生成release notes,设置为false则使用body_path指定的文件内容作为release notes | |
| generate_release_notes: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |