Merge pull request #2161 from debpalash/fix/electron-release-empty-signing

Fix Electron release packaging with missing signing credentials
This commit is contained in:
Palash Debnath
2026-09-16 16:08:26 -07:00
committed by GitHub
4 changed files with 97 additions and 4 deletions
+28 -4
View File
@@ -6,6 +6,10 @@ on:
tags: ['v*']
workflow_dispatch:
inputs:
release_tag:
description: "Existing version tag to package using this workflow from main (optional)"
type: string
default: ''
publish:
description: "Publish the tagged Electron release after all platforms pass"
type: boolean
@@ -19,9 +23,12 @@ permissions:
contents: read
concurrency:
group: electron-release-${{ github.ref }}
group: electron-release-${{ inputs.release_tag || github.ref_name }}
cancel-in-progress: false
env:
RELEASE_REF: ${{ inputs.release_tag && format('refs/tags/{0}', inputs.release_tag) || github.ref }}
jobs:
validate:
# The transition tag is assembled after the manual Tauri draft succeeds.
@@ -29,9 +36,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_REF }}
- name: Require an exact version tag
env:
REF: ${{ github.ref }}
REF: ${{ env.RELEASE_REF }}
WORKFLOW_REF: ${{ github.ref }}
RELEASE_TAG_OVERRIDE: ${{ inputs.release_tag }}
ALLOW_UNSIGNED: ${{ inputs.allow_unsigned }}
DISPATCH_ACTOR: ${{ github.actor }}
RERUN_ACTOR: ${{ github.triggering_actor }}
@@ -42,8 +53,12 @@ jobs:
echo "Only the repository owner may accept unsigned installers"; exit 1;
}
fi
if [ -n "$RELEASE_TAG_OVERRIDE" ]; then
test "$WORKFLOW_REF" = refs/heads/main || { echo "Tag overrides require the workflow from main"; exit 1; }
fi
VERSION=$(node -p "require('./frontend/package.json').version")
test "$REF" = "refs/tags/v$VERSION" || { echo "Dispatch on the exact version tag"; exit 1; }
test "$REF" = "refs/tags/v$VERSION" || { echo "Select the exact version tag"; exit 1; }
test "$(git rev-parse HEAD)" = "$(git rev-parse "$REF^{commit}")" || { echo "Checkout does not match the release tag"; exit 1; }
package:
needs: validate
runs-on: ${{ matrix.runner }}
@@ -81,6 +96,8 @@ jobs:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_REF }}
- uses: actions/setup-node@v4
with:
node-version: '22'
@@ -129,6 +146,11 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.ELECTRON_CSC_KEY_PASSWORD }}
working-directory: electron
run: |
# An empty CSC_LINK is interpreted as the working directory by the
# signer. Omit absent credentials rather than passing empty strings.
if [ -z "${CSC_LINK:-}" ]; then
unset CSC_LINK CSC_KEY_PASSWORD
fi
bun x electron-builder --config electron-builder.config.mjs ${{ matrix.flags }} --publish never
node tests/packaging-contract.mjs --artifact
node tests/update-package-contract.mjs --platform ${{ matrix.platform }} --arch ${{ matrix.arch }}
@@ -174,11 +196,13 @@ jobs:
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
TAG: ${{ inputs.release_tag || github.ref_name }}
SUNSET_TAG: ${{ vars.TAURI_SUNSET_TAG }}
PUBLISH: ${{ inputs.publish }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_REF }}
- uses: actions/download-artifact@v4
with:
pattern: electron-release-*
+8
View File
@@ -8,6 +8,14 @@ the frozen-backend fallback mirror it for their toolchains.
## [Unreleased]
**Highlights**
- Electron packaging can recover without changing a release tag
### CI
- Handle missing Electron signing credentials and retry packaging fixes without moving release tags (#2157)
## [0.5.3] — 2026-09-17
**Highlights**
+9
View File
@@ -188,3 +188,12 @@ choice. Tauri's signing keys do not sign Electron packages.
For the transition tag, automatic Electron release jobs are skipped. Build the
manual Tauri sunset draft first, then dispatch Electron on the same tag after
its signed updater feeds exist. Later tags build Electron automatically.
If a packaging-workflow fix is needed after tagging, keep the release tag
immutable. Merge and validate the workflow fix on main, then dispatch
`electron-release.yml` from main with `release_tag=vX.Y.Z`. Validation and every
packaging/release job check out that exact tag; only the workflow comes from
main. Empty signing secrets are omitted from the builder environment so drafts
and explicitly accepted unsigned builds do not interpret the working directory
as a certificate. Publication still requires `publish=true` and the same guards.
+52
View File
@@ -51,3 +51,55 @@ def test_electron_release_scopes_signing_secrets_and_gates_unsigned_owner_dispat
assert guard["env"]["RERUN_ACTOR"] == "${{ github.triggering_actor }}"
publish = jobs["release"]["steps"][-1]
assert publish["if"] == "github.event_name == 'workflow_dispatch' && inputs.publish == true"
@pytest.mark.parametrize('credentials,expected', [
({}, '|'),
({'CSC_LINK': '', 'CSC_KEY_PASSWORD': ''}, '|'),
({'CSC_LINK': 'fake-certificate', 'CSC_KEY_PASSWORD': 'fake-password'}, 'x|x'),
])
def test_packaging_omits_empty_signing_credentials(tmp_path, credentials, expected):
import os
import subprocess
import yaml
workflow = yaml.load((ROOT / '.github/workflows/electron-release.yml').read_text(), Loader=yaml.BaseLoader)
step = next(s for s in workflow['jobs']['package']['steps'] if s.get('name') == 'Package without publishing')
for name, body in [('bun', 'printf "%s|%s" "${CSC_LINK+x}" "${CSC_KEY_PASSWORD+x}" > "$CAPTURE_PATH"'), ('node', 'exit 0')]:
executable = tmp_path / name
executable.write_text('#!/bin/sh\n' + body + '\n')
executable.chmod(0o755)
env = {k: v for k, v in os.environ.items() if k not in {'CSC_LINK', 'CSC_KEY_PASSWORD'}}
env.update(credentials)
capture = tmp_path / 'signing-env'
env.update(PATH=str(tmp_path) + os.pathsep + env['PATH'], CAPTURE_PATH=str(capture))
script = step['run'].replace('${{ matrix.flags }}', '--mac --arm64').replace('${{ matrix.platform }}', 'darwin').replace('${{ matrix.arch }}', 'arm64')
subprocess.run(['bash', '-e', '-c', script], env=env, check=True)
assert capture.read_text() == expected
@pytest.mark.parametrize('workflow_ref,override,ref,head_matches,ok', [
('refs/heads/main', 'v1.2.3', 'refs/tags/v1.2.3', True, True),
('refs/tags/v1.2.3', '', 'refs/tags/v1.2.3', True, True),
('refs/heads/feature', 'v1.2.3', 'refs/tags/v1.2.3', True, False),
('refs/heads/main', 'v1.2.3', 'refs/tags/v1.2.3', False, False),
('refs/heads/main', 'v9.9.9', 'refs/tags/v9.9.9', True, False),
])
def test_release_tag_override_preserves_exact_tag_checkout(tmp_path, workflow_ref, override, ref, head_matches, ok):
import os
import subprocess
import yaml
workflow = yaml.load((ROOT / '.github/workflows/electron-release.yml').read_text(), Loader=yaml.BaseLoader)
for job in workflow['jobs'].values():
for step in job.get('steps', []):
if step.get('uses', '').startswith('actions/checkout@'):
assert step['with']['ref'] == '${{ env.RELEASE_REF }}'
for name, body in [('node', 'echo 1.2.3'), ('git', 'if [ "$2" = HEAD ]; then echo "$TEST_HEAD"; else echo tagged; fi')]:
executable = tmp_path / name
executable.write_text('#!/bin/sh\n' + body + '\n')
executable.chmod(0o755)
guard = next(s for s in workflow['jobs']['validate']['steps'] if 'run' in s)
env = dict(os.environ, PATH=str(tmp_path) + os.pathsep + os.environ['PATH'], REF=ref,
WORKFLOW_REF=workflow_ref, RELEASE_TAG_OVERRIDE=override, ALLOW_UNSIGNED='false',
TEST_HEAD='tagged' if head_matches else 'different')
result = subprocess.run(['bash', '-e', '-c', guard['run']], env=env, capture_output=True)
assert (result.returncode == 0) == ok, result.stderr