初始化项目 #1
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: Release Desktop | |
| on: | |
| push: | |
| tags: ['v*.*.*'] | |
| workflow_dispatch: | |
| inputs: | |
| draft: | |
| description: 'Create as draft release' | |
| required: false | |
| default: true | |
| type: boolean | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-desktop-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| signing-preflight: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| macos_signed: ${{ steps.validate.outputs.macos_signed }} | |
| steps: | |
| - name: Validate release signing and notarization secrets | |
| id: validate | |
| shell: bash | |
| env: | |
| CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }} | |
| CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| WIN_CSC_LINK: ${{ secrets.WINDOWS_CERTIFICATE }} | |
| WIN_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} | |
| run: | | |
| # macOS signing + notarization is preferred: Squirrel.Mac auto-update and | |
| # first-launch Gatekeeper approval work best with a notarized Developer ID build. | |
| # A migration release may still ship unsigned while Apple Developer ID setup is pending. | |
| missing=() | |
| [ -n "$CSC_LINK" ] || missing+=("MACOS_CERTIFICATE") | |
| [ -n "$CSC_KEY_PASSWORD" ] || missing+=("MACOS_CERTIFICATE_PASSWORD") | |
| [ -n "$APPLE_ID" ] || missing+=("APPLE_ID") | |
| [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ] || missing+=("APPLE_APP_SPECIFIC_PASSWORD") | |
| [ -n "$APPLE_TEAM_ID" ] || missing+=("APPLE_TEAM_ID") | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf '::warning::Missing macOS signing/notarization secrets (%s): macOS artifacts will be unsigned and users must use install-macos-unsigned.sh.\n' "${missing[*]}" | |
| echo "macos_signed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "macos_signed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Windows signing is optional: an unsigned NSIS installer still auto-updates | |
| # (electron-updater), it only triggers SmartScreen warnings. Warn, do not block, | |
| # so releases can ship with an Apple Developer ID alone. | |
| win_missing=() | |
| [ -n "$WIN_CSC_LINK" ] || win_missing+=("WINDOWS_CERTIFICATE") | |
| [ -n "$WIN_CSC_KEY_PASSWORD" ] || win_missing+=("WINDOWS_CERTIFICATE_PASSWORD") | |
| if [ "${#win_missing[@]}" -gt 0 ]; then | |
| printf '::warning::Windows signing secrets missing (%s): the Windows build will be unsigned. Auto-update still works, but users will see SmartScreen warnings.\n' "${win_missing[*]}" | |
| fi | |
| build: | |
| needs: | |
| - signing-preflight | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: macos-latest | |
| target_triple: aarch64-apple-darwin | |
| builder_args: --mac dmg zip --arm64 | |
| label: macOS-ARM64 | |
| smoke_platform: macos | |
| - platform: macos-latest | |
| target_triple: x86_64-apple-darwin | |
| builder_args: --mac dmg zip --x64 | |
| label: macOS-x64 | |
| smoke_platform: macos | |
| - platform: ubuntu-22.04 | |
| target_triple: x86_64-unknown-linux-gnu | |
| builder_args: --linux AppImage deb --x64 | |
| label: Linux-x64 | |
| smoke_platform: linux | |
| - platform: ubuntu-22.04-arm | |
| target_triple: aarch64-unknown-linux-gnu | |
| builder_args: --linux AppImage deb --arm64 | |
| label: Linux-ARM64 | |
| smoke_platform: linux | |
| - platform: windows-latest | |
| target_triple: x86_64-pc-windows-msvc | |
| builder_args: --win nsis --x64 | |
| label: Windows-x64 | |
| smoke_platform: windows | |
| runs-on: ${{ matrix.platform }} | |
| name: Build (${{ matrix.label }}) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Read version | |
| id: version | |
| shell: bash | |
| run: | | |
| VERSION=$(node -p "require('./desktop/package.json').version") | |
| echo "value=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Validate tag matches version | |
| if: github.event_name == 'push' | |
| shell: bash | |
| run: | | |
| EXPECTED_TAG="v${{ steps.version.outputs.value }}" | |
| if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then | |
| echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}" | |
| exit 1 | |
| fi | |
| - name: Install Linux dependencies | |
| if: contains(matrix.platform, 'ubuntu') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y build-essential curl wget file libfuse2 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install root dependencies | |
| run: bun install | |
| - name: Install desktop dependencies | |
| working-directory: desktop | |
| run: bun install | |
| - name: Install adapter dependencies | |
| working-directory: adapters | |
| run: bun install | |
| - name: Build sidecars | |
| working-directory: desktop | |
| env: | |
| BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache | |
| SIDECAR_TARGET_TRIPLE: ${{ matrix.target_triple }} | |
| run: bun run build:sidecars | |
| - name: Build renderer and Electron bundles | |
| working-directory: desktop | |
| run: | | |
| bun run build | |
| bun run build:electron | |
| - name: Build Electron release artifacts | |
| working-directory: desktop | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 未签名发布:故意不传 CSC_LINK / APPLE_* / WIN_CSC_* 这些 env。 | |
| # 当对应 secret 缺失时它们会被渲染成空字符串,而 electron-builder 一旦看到 | |
| # CSC_LINK 这个 key 就会进入证书签名流程,把空路径当成证书去加载,最终报 | |
| # "<workdir> not a file" 而失败。拿到 Apple Developer ID / Windows 证书后, | |
| # 再把对应签名 env 加回来即可开启签名 + 公证。 | |
| CSC_IDENTITY_AUTO_DISCOVERY: 'false' | |
| run: bunx electron-builder ${{ matrix.builder_args }} --publish never | |
| - name: Verify packaged app structure | |
| run: bun run test:package-smoke --platform ${{ matrix.smoke_platform }} --package-kind release --artifacts-dir desktop/build-artifacts/electron | |
| - name: Verify macOS launch policy | |
| if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' | |
| run: bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper | |
| - name: Warn unsigned macOS launch policy skipped | |
| if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true' | |
| run: echo "::warning::Skipping macOS Gatekeeper package-smoke because this release is unsigned. Ship install-macos-unsigned.sh with the DMG." | |
| - name: Validate matrix release asset set | |
| shell: bash | |
| working-directory: desktop/build-artifacts/electron | |
| env: | |
| APP_VERSION: ${{ steps.version.outputs.value }} | |
| run: | | |
| case "${{ matrix.label }}" in | |
| macOS-ARM64) | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip.blockmap" | |
| "latest-mac.yml" | |
| ) | |
| ;; | |
| macOS-x64) | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap" | |
| "latest-mac.yml" | |
| ) | |
| ;; | |
| Linux-x64) | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb" | |
| "latest-linux.yml" | |
| ) | |
| ;; | |
| Linux-ARM64) | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb" | |
| "latest-linux-arm64.yml" | |
| ) | |
| ;; | |
| Windows-x64) | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-win-x64.exe" | |
| "Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap" | |
| "latest.yml" | |
| ) | |
| ;; | |
| *) | |
| echo "::error::No expected asset list for matrix label ${{ matrix.label }}" | |
| exit 1 | |
| ;; | |
| esac | |
| missing=() | |
| for file in "${expected[@]}"; do | |
| [ -f "$file" ] || missing+=("$file") | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf '::error::Missing release assets for %s: %s\n' "${{ matrix.label }}" "${missing[*]}" | |
| exit 1 | |
| fi | |
| - name: Namespace update metadata assets | |
| shell: bash | |
| working-directory: desktop/build-artifacts/electron | |
| run: | | |
| shopt -s nullglob | |
| for file in latest*.yml; do | |
| mv "$file" "${file%.yml}-${{ matrix.label }}.yml" | |
| done | |
| - name: Upload update metadata for merge | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-update-metadata-${{ matrix.label }} | |
| path: desktop/build-artifacts/electron/latest*.yml | |
| if-no-files-found: ignore | |
| - name: Upload release artifacts for final publish | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-release-artifacts-${{ matrix.label }} | |
| path: | | |
| desktop/build-artifacts/electron/*.dmg | |
| desktop/build-artifacts/electron/*.zip | |
| desktop/build-artifacts/electron/*.exe | |
| desktop/build-artifacts/electron/*.AppImage | |
| desktop/build-artifacts/electron/*.deb | |
| desktop/build-artifacts/electron/*.blockmap | |
| desktop/build-artifacts/electron/*.yml | |
| if-no-files-found: error | |
| publish-release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Read version | |
| id: version | |
| shell: bash | |
| run: | | |
| VERSION=$(node -p "require('./desktop/package.json').version") | |
| echo "value=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Validate tag matches version | |
| if: github.event_name == 'push' | |
| shell: bash | |
| run: | | |
| EXPECTED_TAG="v${{ steps.version.outputs.value }}" | |
| if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then | |
| echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}" | |
| exit 1 | |
| fi | |
| - name: Load release notes | |
| id: release_notes | |
| shell: bash | |
| run: | | |
| NOTES_FILE="release-notes/v${{ steps.version.outputs.value }}.md" | |
| if [ ! -f "$NOTES_FILE" ]; then | |
| echo "::error::Missing release notes file: $NOTES_FILE" | |
| exit 1 | |
| fi | |
| { | |
| echo 'body<<__RELEASE_NOTES__' | |
| cat "$NOTES_FILE" | |
| echo | |
| echo '__RELEASE_NOTES__' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install root dependencies | |
| run: bun install | |
| - name: Download release artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: desktop-release-artifacts-* | |
| path: artifacts/release-assets | |
| merge-multiple: true | |
| - name: Validate complete release asset set | |
| shell: bash | |
| env: | |
| APP_VERSION: ${{ steps.version.outputs.value }} | |
| run: | | |
| expected=( | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.dmg.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-arm64.zip.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.dmg.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip" | |
| "Claude-Code-Haha-${APP_VERSION}-mac-x64.zip.blockmap" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.AppImage" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-amd64.deb" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-arm64.AppImage" | |
| "Claude-Code-Haha-${APP_VERSION}-linux-arm64.deb" | |
| "Claude-Code-Haha-${APP_VERSION}-win-x64.exe" | |
| "Claude-Code-Haha-${APP_VERSION}-win-x64.exe.blockmap" | |
| ) | |
| missing=() | |
| for file in "${expected[@]}"; do | |
| [ -f "artifacts/release-assets/$file" ] || missing+=("$file") | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf '::error::Missing complete release assets: %s\n' "${missing[*]}" | |
| exit 1 | |
| fi | |
| - name: Download update metadata artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: desktop-update-metadata-* | |
| path: artifacts/update-metadata | |
| merge-multiple: true | |
| - name: Merge standard update metadata | |
| run: bun run scripts/release-update-metadata.ts --metadata-dir artifacts/update-metadata --out-dir artifacts/update-metadata-standard | |
| - name: Validate standard update metadata set | |
| shell: bash | |
| run: | | |
| expected=( | |
| "latest-mac.yml" | |
| "latest-linux.yml" | |
| "latest-linux-arm64.yml" | |
| "latest.yml" | |
| ) | |
| missing=() | |
| for file in "${expected[@]}"; do | |
| [ -f "artifacts/update-metadata-standard/$file" ] || missing+=("$file") | |
| done | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf '::error::Missing standard update metadata: %s\n' "${missing[*]}" | |
| exit 1 | |
| fi | |
| - name: Publish complete GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.version.outputs.value }} | |
| name: MiMo-Codex v${{ steps.version.outputs.value }} | |
| body: ${{ steps.release_notes.outputs.body }} | |
| draft: ${{ github.event_name == 'workflow_dispatch' && inputs.draft || false }} | |
| prerelease: false | |
| fail_on_unmatched_files: true | |
| files: | | |
| artifacts/release-assets/**/*.dmg | |
| artifacts/release-assets/**/*.zip | |
| artifacts/release-assets/**/*.exe | |
| artifacts/release-assets/**/*.AppImage | |
| artifacts/release-assets/**/*.deb | |
| artifacts/release-assets/**/*.blockmap | |
| artifacts/update-metadata-standard/*.yml | |
| desktop/scripts/install-macos-unsigned.sh |