diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index ca29fc8b..f5cecc14 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -273,17 +273,15 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} DEBUG: 'electron-builder,electron-osx-sign,electron-notarize*' - # Signed macOS releases require all of these secrets. Keep this step - # separate from the unsigned fallback so empty secrets are never passed - # to electron-builder as CSC_LINK / APPLE_* env vars. - CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }} - CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + # Reuse the keychain prepared for native runtimes. Passing CSC_LINK + # would make electron-builder import the same certificate again. APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} MACOS_NOTARIZE: ${{ github.event_name != 'workflow_dispatch' || inputs.notarize_macos }} run: | set -euo pipefail + export CSC_KEYCHAIN="${CC_HAHA_CI_KEYCHAIN:?macOS signing keychain was not prepared}" echo "::group::macOS signing diagnostics" echo "UTC start: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" sw_vers diff --git a/scripts/pr/release-workflow.test.ts b/scripts/pr/release-workflow.test.ts index 51e8a471..08fc8f26 100644 --- a/scripts/pr/release-workflow.test.ts +++ b/scripts/pr/release-workflow.test.ts @@ -193,14 +193,15 @@ describe('release desktop workflow', () => { test('release workflow signs and notarizes macOS builds only when signing preflight succeeds', () => { const workflow = readReleaseWorkflow() + const importIdentityStep = extractStep(workflow, 'Import macOS signing identity for native runtimes') const signedBuildStep = extractStep(workflow, 'Build signed macOS Electron release artifacts') const unsignedBuildStep = extractStep(workflow, 'Build unsigned Electron release artifacts') expect(workflow).toContain('app_bundle_dir: mac-arm64') expect(workflow).toContain('app_bundle_dir: mac') expect(signedBuildStep).toContain("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true'") - expect(signedBuildStep).toContain('CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}') - expect(signedBuildStep).toContain('CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}') + expect(importIdentityStep).toContain('CSC_LINK: ${{ secrets.MACOS_CERTIFICATE }}') + expect(importIdentityStep).toContain('CSC_KEY_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}') expect(signedBuildStep).toContain('APPLE_ID: ${{ secrets.APPLE_ID }}') expect(signedBuildStep).toContain('APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}') expect(signedBuildStep).toContain('APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}') @@ -257,6 +258,25 @@ describe('release desktop workflow', () => { expect(workflow.indexOf('Build unsigned Electron release artifacts')).toBeLessThan(workflow.indexOf('Verify packaged app structure')) }) + test('macOS packaging reuses the native runtime keychain instead of importing the certificate again', () => { + const workflow = readReleaseWorkflow() + const importIdentityStep = extractStep(workflow, 'Import macOS signing identity for native runtimes') + const signedBuildStep = extractStep(workflow, 'Build signed macOS Electron release artifacts') + const cleanupStep = extractStep(workflow, 'Remove temporary macOS signing keychain') + + expect(importIdentityStep).toContain('echo "CC_HAHA_CI_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"') + expect(signedBuildStep).toContain('export CSC_KEYCHAIN="${CC_HAHA_CI_KEYCHAIN:?macOS signing keychain was not prepared}"') + expect(signedBuildStep).not.toContain('CSC_LINK:') + expect(signedBuildStep).not.toContain('CSC_KEY_PASSWORD:') + expect(cleanupStep).toContain('security delete-keychain "$CC_HAHA_CI_KEYCHAIN"') + expect(workflow.indexOf('Import macOS signing identity for native runtimes')).toBeLessThan( + workflow.indexOf('Build signed macOS Electron release artifacts'), + ) + expect(workflow.indexOf('Remove temporary macOS signing keychain')).toBeGreaterThan( + workflow.indexOf('Verify macOS launch policy'), + ) + }) + test('release workflow requires signed macOS Computer Use and preserves SignPath draft policy', () => { const workflow = readReleaseWorkflow() const signingJob = workflow.match(