Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(workflow): correct branch name and resolve test failure #127

fix(workflow): correct branch name and resolve test failure

fix(workflow): correct branch name and resolve test failure #127

Workflow file for this run

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 }}