release.yml 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. name: Release Client
  2. on:
  3. push:
  4. tags:
  5. - 'v*'
  6. workflow_dispatch:
  7. inputs:
  8. tag_name:
  9. description: '要发布的 tag,例如 v2.0.1'
  10. required: true
  11. type: string
  12. permissions:
  13. contents: write
  14. jobs:
  15. create-release:
  16. name: Create GitHub Release
  17. runs-on: ubuntu-latest
  18. steps:
  19. - name: Checkout
  20. uses: actions/checkout@v4
  21. with:
  22. fetch-depth: 0
  23. ref: ${{ inputs.tag_name || github.ref }}
  24. - name: Create release notes from commits
  25. env:
  26. TAG_NAME: ${{ inputs.tag_name || github.ref_name }}
  27. run: |
  28. PREVIOUS_TAG="$(git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null || true)"
  29. if [ -n "$PREVIOUS_TAG" ]; then
  30. RANGE="$PREVIOUS_TAG..$TAG_NAME"
  31. COMPARE_URL="https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${TAG_NAME}"
  32. else
  33. RANGE="$TAG_NAME"
  34. COMPARE_URL="https://github.com/${{ github.repository }}/commits/${TAG_NAME}"
  35. fi
  36. {
  37. echo "## 更新内容"
  38. echo
  39. git log "$RANGE" --no-merges --pretty=format:'- %s (%h)' || true
  40. echo
  41. echo
  42. echo "**Full Changelog**: $COMPARE_URL"
  43. } > release_notes.md
  44. - name: Create or update GitHub Release
  45. env:
  46. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  47. TAG_NAME: ${{ inputs.tag_name || github.ref_name }}
  48. run: |
  49. if gh release view "$TAG_NAME" >/dev/null 2>&1; then
  50. gh release edit "$TAG_NAME" --title "$TAG_NAME" --notes-file release_notes.md
  51. else
  52. gh release create "$TAG_NAME" --title "$TAG_NAME" --notes-file release_notes.md
  53. fi
  54. build:
  55. name: Build ${{ matrix.os }}
  56. needs: create-release
  57. runs-on: ${{ matrix.os }}
  58. strategy:
  59. fail-fast: false
  60. matrix:
  61. os: [windows-latest, macos-latest]
  62. defaults:
  63. run:
  64. working-directory: client
  65. shell: bash
  66. steps:
  67. - name: Checkout
  68. uses: actions/checkout@v4
  69. with:
  70. ref: ${{ inputs.tag_name || github.ref }}
  71. - name: Setup Node.js
  72. uses: actions/setup-node@v4
  73. with:
  74. node-version: 22
  75. cache: npm
  76. cache-dependency-path: client/package-lock.json
  77. - name: Cache Electron build downloads
  78. if: runner.os == 'macOS'
  79. uses: actions/cache@v4
  80. with:
  81. path: |
  82. ~/Library/Caches/electron
  83. ~/Library/Caches/electron-builder
  84. key: ${{ runner.os }}-electron-${{ hashFiles('client/package-lock.json') }}
  85. restore-keys: |
  86. ${{ runner.os }}-electron-
  87. - name: Install dependencies
  88. run: npm ci
  89. - name: Install build type dependencies
  90. run: npm install --no-save @types/plist
  91. - name: Sync package version from tag
  92. env:
  93. TAG_NAME: ${{ inputs.tag_name || github.ref_name }}
  94. run: |
  95. VERSION="${TAG_NAME#v}"
  96. npm version "$VERSION" --no-git-tag-version --allow-same-version
  97. - name: Generate macOS icon
  98. if: runner.os == 'macOS'
  99. run: |
  100. rm -rf assets/icon.iconset
  101. mkdir -p assets/icon.iconset
  102. sips -z 16 16 assets/icon_256.png --out assets/icon.iconset/icon_16x16.png
  103. sips -z 32 32 assets/icon_256.png --out assets/icon.iconset/icon_16x16@2x.png
  104. sips -z 32 32 assets/icon_256.png --out assets/icon.iconset/icon_32x32.png
  105. sips -z 64 64 assets/icon_256.png --out assets/icon.iconset/icon_32x32@2x.png
  106. sips -z 128 128 assets/icon_256.png --out assets/icon.iconset/icon_128x128.png
  107. sips -z 256 256 assets/icon_256.png --out assets/icon.iconset/icon_128x128@2x.png
  108. sips -z 256 256 assets/icon_256.png --out assets/icon.iconset/icon_256x256.png
  109. sips -z 512 512 assets/icon_256.png --out assets/icon.iconset/icon_256x256@2x.png
  110. sips -z 512 512 assets/icon_256.png --out assets/icon.iconset/icon_512x512.png
  111. sips -z 1024 1024 assets/icon_256.png --out assets/icon.iconset/icon_512x512@2x.png
  112. iconutil -c icns assets/icon.iconset -o assets/icon.icns
  113. - name: Build renderer
  114. run: npm run build
  115. - name: Preload Electron macOS runtimes
  116. if: runner.os == 'macOS'
  117. run: |
  118. export ELECTRON_CACHE="$HOME/Library/Caches/electron"
  119. ELECTRON_VERSION="$(node -p "require('./node_modules/electron/package.json').version")"
  120. cache_dir="$ELECTRON_CACHE"
  121. mkdir -p "$cache_dir"
  122. for arch in x64 arm64; do
  123. file="$cache_dir/electron-v${ELECTRON_VERSION}-darwin-${arch}.zip"
  124. if [ -f "$file" ] && unzip -tq "$file" >/dev/null 2>&1; then
  125. echo "Electron ${ELECTRON_VERSION} darwin-${arch} already cached."
  126. continue
  127. fi
  128. tmp="${file}.tmp"
  129. rm -f "$file" "$tmp"
  130. curl --fail --location --retry 5 --retry-all-errors --retry-delay 10 \
  131. "https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/electron-v${ELECTRON_VERSION}-darwin-${arch}.zip" \
  132. --output "$tmp"
  133. unzip -tq "$tmp" >/dev/null
  134. mv "$tmp" "$file"
  135. done
  136. - name: Build Windows NSIS and ZIP artifacts
  137. if: runner.os == 'Windows'
  138. run: npx electron-builder --win nsis zip --publish never
  139. - name: Build Windows MSI artifact
  140. if: runner.os == 'Windows'
  141. run: npx electron-builder --win msi --publish never -c.productName=OpenBidKit_Yibiao -c.extraMetadata.author=mark -c.copyright="Copyright (c) 2026 mark"
  142. - name: Build and publish macOS artifacts
  143. if: runner.os == 'macOS'
  144. run: |
  145. export ELECTRON_CACHE="$HOME/Library/Caches/electron"
  146. export ELECTRON_BUILDER_CACHE="$HOME/Library/Caches/electron-builder"
  147. for attempt in 1 2 3; do
  148. echo "Build macOS artifacts, attempt ${attempt}/3."
  149. if npx electron-builder --mac --publish never; then
  150. exit 0
  151. fi
  152. if [ "$attempt" -eq 3 ]; then
  153. exit 1
  154. fi
  155. sleep $((attempt * 20))
  156. done
  157. - name: Package macOS DMG with instructions
  158. if: runner.os == 'macOS'
  159. run: |
  160. shopt -s nullglob
  161. for dmg in release/*.dmg; do
  162. base="$(basename "$dmg" .dmg)"
  163. package_dir="release/${base}-package"
  164. rm -rf "$package_dir"
  165. mkdir -p "$package_dir"
  166. cp "$dmg" "$package_dir/"
  167. cat > "$package_dir/macOS使用说明.txt" <<'EOF'
  168. macOS 使用说明
  169. 如果双击 App 提示“已损坏,无法打开”或“无法验证开发者”,这是因为本软件未进行 Apple 官方签名和公证,并不代表文件真的损坏。
  170. 使用步骤:
  171. 1. 双击 dmg 文件。
  172. 2. 将“易标投标工具箱.app”拖到“应用程序”文件夹。
  173. 3. 打开“终端”App。
  174. 4. 执行下面命令:
  175. xattr -dr com.apple.quarantine /Applications/易标投标工具箱.app
  176. 5. 再从“应用程序”中打开“易标投标工具箱”。
  177. 如果仍无法打开,请右键点击 App,选择“打开”,再确认打开。
  178. EOF
  179. (cd release && ditto -c -k --sequesterRsrc --keepParent "$(basename "$package_dir")" "${base}-package.zip")
  180. rm -rf "$package_dir"
  181. rm -f "$dmg"
  182. done
  183. - name: Upload release assets
  184. env:
  185. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  186. TAG_NAME: ${{ inputs.tag_name || github.ref_name }}
  187. run: |
  188. shopt -s nullglob
  189. files=(release/*.exe release/*.msi release/*.zip release/*.blockmap release/latest*.yml)
  190. if [ ${#files[@]} -eq 0 ]; then
  191. echo "No release assets found."
  192. exit 1
  193. fi
  194. gh release upload "$TAG_NAME" "${files[@]}" --clobber