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
This commit is contained in:
程序员阿江(Relakkes) 2026-06-17 04:47:52 +08:00
parent f7ebe668b5
commit f9b48a3031
2 changed files with 38 additions and 6 deletions

View File

@ -10,6 +10,11 @@ on:
required: false required: false
default: true default: true
type: boolean type: boolean
notarize_macos:
description: 'Notarize macOS artifacts'
required: false
default: true
type: boolean
permissions: permissions:
contents: write contents: write
@ -173,6 +178,7 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
MACOS_NOTARIZE: ${{ github.event_name != 'workflow_dispatch' || inputs.notarize_macos }}
run: | run: |
set -euo pipefail set -euo pipefail
echo "::group::macOS signing diagnostics" echo "::group::macOS signing diagnostics"
@ -182,9 +188,18 @@ jobs:
xcrun --find notarytool xcrun --find notarytool
xcrun notarytool --version || true xcrun notarytool --version || true
security find-identity -v -p codesigning || true security find-identity -v -p codesigning || true
echo "macOS notarization requested: ${MACOS_NOTARIZE}"
echo "::endgroup::" echo "::endgroup::"
max_attempts=2 if [ "$MACOS_NOTARIZE" = "true" ]; then
build_timeout_seconds=2100 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() { run_signed_electron_builder() {
local timeout_seconds="$1" 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" 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 rm -rf build-artifacts/electron
set +e 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=$? status=$?
set -e set -e
if [ "$status" -eq 0 ]; then 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 run: bun run test:package-smoke --platform ${{ matrix.smoke_platform }} --package-kind release --artifacts-dir desktop/build-artifacts/electron
- name: Verify macOS launch policy - 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 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 - name: Warn unsigned macOS launch policy skipped
if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true' 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." run: echo "::warning::Skipping macOS Gatekeeper package-smoke because this release is unsigned. Ship install-macos-unsigned.sh with the DMG."

View File

@ -106,12 +106,19 @@ describe('release desktop workflow', () => {
const gatekeeperStep = workflow.match( const gatekeeperStep = workflow.match(
/- name: Verify macOS launch policy[\s\S]*?(?:\n\s{6}- name:|$)/, /- name: Verify macOS launch policy[\s\S]*?(?:\n\s{6}- name:|$)/,
)?.[0] )?.[0]
const notarizationWarningStep = workflow.match(
/- name: Warn macOS notarization skipped[\s\S]*?(?:\n\s{6}- name:|$)/,
)?.[0]
const unsignedWarningStep = workflow.match( const unsignedWarningStep = workflow.match(
/- name: Warn unsigned macOS launch policy skipped[\s\S]*?(?:\n\s{6}- name:|$)/, /- name: Warn unsigned macOS launch policy skipped[\s\S]*?(?:\n\s{6}- name:|$)/,
)?.[0] )?.[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(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("if: matrix.smoke_platform == 'macos' && needs.signing-preflight.outputs.macos_signed != 'true'")
expect(unsignedWarningStep).toContain('install-macos-unsigned.sh') expect(unsignedWarningStep).toContain('install-macos-unsigned.sh')
expect(workflow.indexOf('Verify macOS launch policy')).toBeLessThan(workflow.indexOf('Upload release artifacts for final publish')) 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_ID: ${{ secrets.APPLE_ID }}')
expect(signedBuildStep).toContain('APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}') 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('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).not.toContain('CSC_IDENTITY_AUTO_DISCOVERY')
expect(signedBuildStep).toContain('timeout-minutes: 80') expect(signedBuildStep).toContain('timeout-minutes: 80')
expect(signedBuildStep).toContain("DEBUG: 'electron-builder,electron-osx-sign,electron-notarize*'") expect(signedBuildStep).toContain("DEBUG: 'electron-builder,electron-osx-sign,electron-notarize*'")
expect(signedBuildStep).toContain('macOS signing diagnostics') expect(signedBuildStep).toContain('macOS signing diagnostics')
expect(signedBuildStep).toContain('xcrun --find notarytool') expect(signedBuildStep).toContain('xcrun --find notarytool')
expect(signedBuildStep).toContain('security find-identity -v -p codesigning') 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('max_attempts=2')
expect(signedBuildStep).toContain('build_timeout_seconds=2100') 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('run_signed_electron_builder')
expect(signedBuildStep).toContain('Signed electron-builder timed out') expect(signedBuildStep).toContain('Signed electron-builder timed out')
expect(signedBuildStep).toContain('pkill -TERM -P "$build_pid"') 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('Starting signed electron-builder attempt')
expect(signedBuildStep).toContain('Finished signed electron-builder attempt') expect(signedBuildStep).toContain('Finished signed electron-builder attempt')
expect(signedBuildStep).toContain('retrying after 120 seconds') 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("if: matrix.smoke_platform != 'macos' || needs.signing-preflight.outputs.macos_signed != 'true'")
expect(unsignedBuildStep).toContain("CSC_IDENTITY_AUTO_DISCOVERY: 'false'") expect(unsignedBuildStep).toContain("CSC_IDENTITY_AUTO_DISCOVERY: 'false'")