From f9b48a30314e7f47a37506dd00d9c8ca9233e55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Wed, 17 Jun 2026 04:47:52 +0800 Subject: [PATCH] feat(release): allow signed-only macOS draft builds Add a workflow_dispatch-only notarize_macos switch so draft release runs can build Developer ID signed macOS artifacts without waiting on Apple notarization when GitHub runner networking is failing. Tag push releases still default to notarization and Gatekeeper release checks.\n\nTested: bun test scripts/pr/release-workflow.test.ts\nTested: git diff --check\nTested: bun run scripts/release.ts 0.4.3 --dry\nConfidence: medium\nScope-risk: moderate --- .github/workflows/release-desktop.yml | 27 +++++++++++++++++++++++---- scripts/pr/release-workflow.test.ts | 17 +++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 52e1e219..8150432a 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -10,6 +10,11 @@ on: required: false default: true type: boolean + notarize_macos: + description: 'Notarize macOS artifacts' + required: false + default: true + type: boolean permissions: contents: write @@ -173,6 +178,7 @@ jobs: 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 echo "::group::macOS signing diagnostics" @@ -182,9 +188,18 @@ jobs: xcrun --find notarytool xcrun notarytool --version || true security find-identity -v -p codesigning || true + echo "macOS notarization requested: ${MACOS_NOTARIZE}" echo "::endgroup::" - max_attempts=2 - build_timeout_seconds=2100 + if [ "$MACOS_NOTARIZE" = "true" ]; then + max_attempts=2 + build_timeout_seconds=2100 + builder_args=( ${{ matrix.builder_args }} --publish never ) + else + max_attempts=1 + build_timeout_seconds=900 + builder_args=( ${{ matrix.builder_args }} --publish never -c.mac.notarize=false ) + echo "::warning::macOS notarization is disabled for this draft run; artifacts will be Developer ID signed but not notarized." + fi run_signed_electron_builder() { local timeout_seconds="$1" @@ -223,7 +238,7 @@ jobs: echo "Starting signed electron-builder attempt ${attempt}/${max_attempts} at $(date -u '+%Y-%m-%dT%H:%M:%SZ') with ${build_timeout_seconds}s watchdog" rm -rf build-artifacts/electron set +e - run_signed_electron_builder "$build_timeout_seconds" node ./node_modules/electron-builder/out/cli/cli.js ${{ matrix.builder_args }} --publish never + run_signed_electron_builder "$build_timeout_seconds" node ./node_modules/electron-builder/out/cli/cli.js "${builder_args[@]}" status=$? set -e if [ "$status" -eq 0 ]; then @@ -254,9 +269,13 @@ jobs: 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' + if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && (github.event_name != 'workflow_dispatch' || inputs.notarize_macos == true) run: bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper + - name: Warn macOS notarization skipped + if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && github.event_name == 'workflow_dispatch' && inputs.notarize_macos == false + run: echo "::warning::Skipping macOS Gatekeeper package-smoke because notarization is disabled for this draft. Artifacts are Developer ID signed but not notarized." + - 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." diff --git a/scripts/pr/release-workflow.test.ts b/scripts/pr/release-workflow.test.ts index d4068da2..c89d23ec 100644 --- a/scripts/pr/release-workflow.test.ts +++ b/scripts/pr/release-workflow.test.ts @@ -106,12 +106,19 @@ describe('release desktop workflow', () => { const gatekeeperStep = workflow.match( /- name: Verify macOS launch policy[\s\S]*?(?:\n\s{6}- name:|$)/, )?.[0] + const notarizationWarningStep = workflow.match( + /- name: Warn macOS notarization skipped[\s\S]*?(?:\n\s{6}- name:|$)/, + )?.[0] const unsignedWarningStep = workflow.match( /- name: Warn unsigned macOS launch policy skipped[\s\S]*?(?:\n\s{6}- name:|$)/, )?.[0] - expect(gatekeeperStep).toContain("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true'") + expect(workflow).toContain('notarize_macos:') + expect(workflow).toContain("description: 'Notarize macOS artifacts'") + expect(gatekeeperStep).toContain("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && (github.event_name != 'workflow_dispatch' || inputs.notarize_macos == true)") expect(gatekeeperStep).toContain('bun run test:package-smoke --platform macos --package-kind release --artifacts-dir desktop/build-artifacts/electron --require-macos-gatekeeper') + expect(notarizationWarningStep).toContain("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed == 'true' && github.event_name == 'workflow_dispatch' && inputs.notarize_macos == false") + expect(notarizationWarningStep).toContain('Developer ID signed but not notarized') expect(unsignedWarningStep).toContain("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true'") expect(unsignedWarningStep).toContain('install-macos-unsigned.sh') expect(workflow.indexOf('Verify macOS launch policy')).toBeLessThan(workflow.indexOf('Upload release artifacts for final publish')) @@ -128,14 +135,20 @@ describe('release desktop workflow', () => { 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 }}') + expect(signedBuildStep).toContain("MACOS_NOTARIZE: ${{ github.event_name != 'workflow_dispatch' || inputs.notarize_macos }}") expect(signedBuildStep).not.toContain('CSC_IDENTITY_AUTO_DISCOVERY') expect(signedBuildStep).toContain('timeout-minutes: 80') expect(signedBuildStep).toContain("DEBUG: 'electron-builder,electron-osx-sign,electron-notarize*'") expect(signedBuildStep).toContain('macOS signing diagnostics') expect(signedBuildStep).toContain('xcrun --find notarytool') expect(signedBuildStep).toContain('security find-identity -v -p codesigning') + expect(signedBuildStep).toContain('macOS notarization requested: ${MACOS_NOTARIZE}') expect(signedBuildStep).toContain('max_attempts=2') expect(signedBuildStep).toContain('build_timeout_seconds=2100') + expect(signedBuildStep).toContain('max_attempts=1') + expect(signedBuildStep).toContain('build_timeout_seconds=900') + expect(signedBuildStep).toContain('-c.mac.notarize=false') + expect(signedBuildStep).toContain('macOS notarization is disabled for this draft run') expect(signedBuildStep).toContain('run_signed_electron_builder') expect(signedBuildStep).toContain('Signed electron-builder timed out') expect(signedBuildStep).toContain('pkill -TERM -P "$build_pid"') @@ -147,7 +160,7 @@ describe('release desktop workflow', () => { expect(signedBuildStep).toContain('Starting signed electron-builder attempt') expect(signedBuildStep).toContain('Finished signed electron-builder attempt') expect(signedBuildStep).toContain('retrying after 120 seconds') - expect(signedBuildStep).toContain(electronBuilderCli) + expect(signedBuildStep).toContain('node ./node_modules/electron-builder/out/cli/cli.js "${builder_args[@]}"') expect(unsignedBuildStep).toContain("if: matrix.smoke_platform != 'macos' || needs.signing-preflight.outputs.macos_signed != 'true'") expect(unsignedBuildStep).toContain("CSC_IDENTITY_AUTO_DISCOVERY: 'false'")