Merge pull request #2161 from debpalash/fix/electron-release-empty-signing
Fix Electron release packaging with missing signing credentials
This commit is contained in:
@@ -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,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**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user