fix: 同步 pnpm lockfile 以通过 CI / align pnpm lockfile for CI #72
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 | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| strategy: | |
| matrix: | |
| include: | |
| # macOS: Use Intel runner for x64, Apple Silicon runner for arm64 | |
| # CRITICAL: macos-15-intel is x86_64, macos-15 is Apple Silicon (arm64) | |
| - os: macos-15-intel # Intel (x86_64) runner for x64 builds | |
| platform: mac | |
| arch: x64 | |
| - os: macos-15 # Apple Silicon (arm64) runner for arm64 builds | |
| platform: mac | |
| arch: arm64 | |
| - os: windows-latest | |
| platform: win | |
| arch: x64 | |
| - os: windows-latest | |
| platform: win | |
| arch: arm64 | |
| - os: ubuntu-latest | |
| platform: linux | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| # Only use arm64 Node on macOS arm64 runners. Windows/Linux runners are x64. | |
| # 仅在 macOS arm64 runner 上使用 arm64 Node,Windows/Linux runner 都是 x64 | |
| architecture: ${{ matrix.platform == 'mac' && matrix.arch == 'arm64' && 'arm64' || 'x64' }} | |
| - name: Check Environment Architecture | |
| shell: bash | |
| run: | | |
| echo "System Architecture (uname -m): $(uname -m)" | |
| echo "Node Architecture (process.arch): $(node -p 'process.arch')" | |
| # Only enforce arch match on macOS where we have dedicated runners | |
| if [ "${{ matrix.platform }}" = "mac" ] && [ "${{ matrix.arch }}" = "arm64" ] && [ "$(node -p 'process.arch')" != "arm64" ]; then | |
| echo "ERROR: Node.js is not arm64 on macOS arm64 runner!" | |
| exit 1 | |
| fi | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| # 显式使用与 package.json 中一致的 pnpm 版本 | |
| version: 9.15.0 | |
| run_install: false | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-${{ matrix.arch || 'default' }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.arch || 'default' }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Clean previous build artifacts | |
| shell: bash | |
| run: | | |
| rm -rf dist || true | |
| - name: Build for ${{ matrix.platform }} ${{ matrix.arch }} | |
| shell: bash | |
| run: | | |
| if [ -n "${{ matrix.arch }}" ]; then | |
| # Build specific architecture (Windows or macOS with arch specified) | |
| # 构建特定架构(Windows 或指定了 arch 的 macOS) | |
| if [ "${{ matrix.platform }}" = "win" ]; then | |
| pnpm vite build && pnpm electron-builder --win --${{ matrix.arch }} --publish never | |
| elif [ "${{ matrix.platform }}" = "mac" ]; then | |
| pnpm vite build && pnpm electron-builder --mac --${{ matrix.arch }} --publish never | |
| fi | |
| else | |
| pnpm electron:build:${{ matrix.platform }} | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Verify macOS app architecture | |
| if: matrix.platform == 'mac' && matrix.arch != '' | |
| shell: bash | |
| run: | | |
| ZIP_FILE=$(find dist -maxdepth 1 -name "PromptHub-*-${{ matrix.arch }}.zip" | head -n 1) | |
| if [ -z "$ZIP_FILE" ]; then | |
| echo "Expected macOS ZIP artifact not found for ${{ matrix.arch }}" | |
| exit 1 | |
| fi | |
| TMP_DIR=$(mktemp -d) | |
| ditto -x -k "$ZIP_FILE" "$TMP_DIR/app" | |
| APP_BIN=$(find "$TMP_DIR/app" -path "*/PromptHub.app/Contents/MacOS/PromptHub" | head -n 1) | |
| if [ -z "$APP_BIN" ]; then | |
| echo "PromptHub.app binary not found inside $ZIP_FILE" | |
| exit 1 | |
| fi | |
| ACTUAL_ARCHS=$(lipo -archs "$APP_BIN") | |
| echo "Detected macOS binary archs: $ACTUAL_ARCHS" | |
| if [ "${{ matrix.arch }}" = "x64" ] && [ "$ACTUAL_ARCHS" != "x86_64" ]; then | |
| echo "Expected x86_64 binary, got: $ACTUAL_ARCHS" | |
| exit 1 | |
| fi | |
| if [ "${{ matrix.arch }}" = "arm64" ] && [ "$ACTUAL_ARCHS" != "arm64" ]; then | |
| echo "Expected arm64 binary, got: $ACTUAL_ARCHS" | |
| exit 1 | |
| fi | |
| - name: Verify Windows installer architecture | |
| if: matrix.platform == 'win' && matrix.arch != '' | |
| shell: bash | |
| run: | | |
| EXE_FILE=$(find dist -maxdepth 1 -name "PromptHub-Setup-*-${{ matrix.arch }}.exe" | head -n 1) | |
| if [ -z "$EXE_FILE" ]; then | |
| echo "Expected Windows installer not found for ${{ matrix.arch }}" | |
| exit 1 | |
| fi | |
| node scripts/verify-windows-installer-arch.js --file "$EXE_FILE" --arch "${{ matrix.arch }}" | |
| - name: Normalize Windows update metadata | |
| if: matrix.platform == 'win' && matrix.arch != '' | |
| shell: bash | |
| run: | | |
| # Generate appropriate yml files for each architecture | |
| # 为每个架构生成适当的 yml 文件 | |
| if [ "${{ matrix.arch }}" = "arm64" ]; then | |
| if [ -f "dist/latest.yml" ]; then | |
| node scripts/fix-latest-yml.js --file dist/latest.yml --arch arm64 | |
| # Rename to latest-arm64.yml for arm64 builds | |
| mv dist/latest.yml dist/latest-arm64.yml | |
| echo "Renamed latest.yml to latest-arm64.yml for ARM64" | |
| fi | |
| else | |
| # x64: Generate latest-x64.yml for backward compatibility with older versions | |
| # x64: 生成 latest-x64.yml 以兼容旧版本 | |
| # electron-builder already generates correct filenames via artifactName | |
| # electron-builder 已通过 artifactName 配置生成正确的文件名 | |
| if [ -f "dist/latest.yml" ]; then | |
| node scripts/fix-latest-yml.js --file dist/latest.yml --arch x64 | |
| # Create latest-x64.yml as a copy for backward compatibility with old versions | |
| # 创建 latest-x64.yml 副本以兼容旧版本 | |
| cp dist/latest.yml dist/latest-x64.yml | |
| echo "Created latest-x64.yml for backward compatibility" | |
| fi | |
| fi | |
| # Remove duplicate NSIS blockmap without arch (prevents release upload errors) | |
| shopt -s nullglob | |
| for file in dist/*.exe.blockmap; do | |
| case "$file" in | |
| *-x64.exe.blockmap|*-arm64.exe.blockmap) ;; | |
| *) rm -f "$file" ;; | |
| esac | |
| done | |
| - name: Normalize macOS update metadata | |
| if: matrix.platform == 'mac' && matrix.arch != '' | |
| shell: bash | |
| run: | | |
| # Keep x64 latest-mac.yml as the canonical filename for backward compatibility. | |
| # ARM64 manifest is stored separately and merged in the release job. | |
| # 保留 x64 latest-mac.yml 作为兼容旧客户端的标准文件名。 | |
| # ARM64 manifest 单独保存,稍后在 release job 中合并。 | |
| if [ -f "dist/latest-mac.yml" ]; then | |
| if [ "${{ matrix.arch }}" = "arm64" ]; then | |
| mv dist/latest-mac.yml dist/latest-mac-arm64.yml | |
| echo "Saved ARM64 manifest as latest-mac-arm64.yml" | |
| else | |
| # x64 remains latest-mac.yml; also keep an explicit alias for debugging. | |
| # x64 继续保留 latest-mac.yml,同时生成一个显式别名便于调试。 | |
| cp dist/latest-mac.yml dist/latest-mac-x64.yml | |
| echo "Created latest-mac-x64.yml alias" | |
| fi | |
| fi | |
| - name: Remove debug files | |
| shell: bash | |
| run: | | |
| # 删除 builder-debug.yml | |
| find dist -name "builder-debug.yml" -delete || true | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.platform }}-${{ matrix.arch || 'default' }}-build | |
| path: | | |
| dist/*.dmg | |
| dist/*.zip | |
| dist/*-x64.exe | |
| dist/*-arm64.exe | |
| dist/*.AppImage | |
| dist/*.deb | |
| dist/*.blockmap | |
| dist/latest*.yml | |
| if-no-files-found: ignore | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/') | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Set release version | |
| shell: bash | |
| run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" | |
| - name: List artifacts for debugging | |
| shell: bash | |
| run: | | |
| echo "=== Artifact structure ===" | |
| find artifacts -type f -name "*.yml" -o -name "*.exe" -o -name "*.dmg" | head -50 | |
| - name: Create Release with gh CLI | |
| shell: bash | |
| run: | | |
| # 1. Prepare Release Notes | |
| cat > release_notes.md <<EOF | |
| ## 📦 下载安装 | |
| | 平台 | 下载 | | |
| |------|------| | |
| | Windows | [](https://github.com/legeling/PromptHub/releases/latest/download/PromptHub-Setup-${VERSION}-x64.exe) [](https://github.com/legeling/PromptHub/releases/latest/download/PromptHub-Setup-${VERSION}-arm64.exe) | | |
| | macOS | [](https://github.com/legeling/PromptHub/releases/latest/download/PromptHub-${VERSION}-arm64.dmg) [](https://github.com/legeling/PromptHub/releases/latest/download/PromptHub-${VERSION}-x64.dmg) | | |
| | Linux | [](https://github.com/legeling/PromptHub/releases/latest/download/PromptHub-${VERSION}-x64.AppImage) [](https://github.com/legeling/PromptHub/releases/latest/download/prompthub_${VERSION}_amd64.deb) | | |
| ### ⚠️ macOS 首次启动 | |
| 如果提示"无法打开"或"已损坏",请在终端执行: | |
| \`\`\`bash | |
| sudo xattr -rd com.apple.quarantine /Applications/PromptHub.app | |
| \`\`\` | |
| --- | |
| ## 🔄 本次更新 | |
| 详细更新日志请查看 [更新日志](https://github.com/legeling/PromptHub#-更新日志) | |
| EOF | |
| # 2. Collect and Deduplicate Assets | |
| echo "Collecting and deduplicating assets..." | |
| mkdir -p release_assets | |
| # Find all matching files and copy them to a single directory | |
| # This handles duplicates by overwriting (latest version wins) | |
| find artifacts -type f \( \ | |
| -name "PromptHub-*-x64.dmg" -o \ | |
| -name "PromptHub-*-arm64.dmg" -o \ | |
| -name "PromptHub-Setup-*-x64.exe" -o \ | |
| -name "PromptHub-Setup-*-arm64.exe" -o \ | |
| -name "PromptHub-*-x64.AppImage" -o \ | |
| -name "prompthub_*_amd64.deb" -o \ | |
| -name "PromptHub-*.zip" -o \ | |
| -name "latest*.yml" -o \ | |
| -name "*.blockmap" \ | |
| \) -exec cp -f {} release_assets/ \; | |
| echo "Files to upload (deduplicated):" | |
| ls -1 release_assets/ | |
| # 3. Merge macOS manifests into one canonical latest-mac.yml for both Intel and Apple Silicon. | |
| # This keeps old mac clients updating via latest-mac.yml while also publishing latest-mac-arm64.yml. | |
| if [ -f "release_assets/latest-mac.yml" ] && [ -f "release_assets/latest-mac-arm64.yml" ]; then | |
| node scripts/merge-mac-latest-yml.js \ | |
| --x64 release_assets/latest-mac.yml \ | |
| --arm64 release_assets/latest-mac-arm64.yml \ | |
| --output release_assets/latest-mac.yml | |
| cp release_assets/latest-mac.yml release_assets/latest-mac-arm64.yml | |
| cp release_assets/latest-mac.yml release_assets/latest-mac-x64.yml | |
| fi | |
| # 4. Verify manifest hashes before publishing. A mismatch here would become a broken auto update. | |
| node scripts/verify-update-manifest.js \ | |
| release_assets/latest.yml \ | |
| release_assets/latest-arm64.yml \ | |
| release_assets/latest-x64.yml \ | |
| release_assets/latest-mac.yml \ | |
| release_assets/latest-mac-arm64.yml \ | |
| release_assets/latest-mac-x64.yml | |
| # 5. Never replace an existing release/tag with different bytes. | |
| # Reusing the same version poisons updater metadata and CDN caches. | |
| if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then | |
| echo "Release ${GITHUB_REF_NAME} already exists. Published versions must be immutable." | |
| exit 1 | |
| fi | |
| # 6. Create Release and Upload | |
| echo "Creating release..." | |
| gh release create "${GITHUB_REF_NAME}" \ | |
| release_assets/* \ | |
| --title "PromptHub ${VERSION}" \ | |
| --notes-file release_notes.md \ | |
| --draft=false \ | |
| --prerelease=${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }} \ | |
| --generate-notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Update Homebrew Cask | |
| if: ${{ !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') && env.HOMEBREW_TAP_TOKEN != '' }} | |
| shell: bash | |
| run: | | |
| # Wait for release assets to be fully available | |
| sleep 15 | |
| bash scripts/update-homebrew-cask.sh "$VERSION" | |
| env: | |
| HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| - name: Skip Homebrew Cask update | |
| if: ${{ !contains(github.ref, 'beta') && !contains(github.ref, 'alpha') && env.HOMEBREW_TAP_TOKEN == '' }} | |
| shell: bash | |
| run: | | |
| echo "HOMEBREW_TAP_TOKEN is not configured; skipping Homebrew cask publish." | |
| env: | |
| HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} |