Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ concurrency:
group: desktop-release-${{ github.ref }}
cancel-in-progress: false

# A manual run is a release rehearsal: both platforms build and go through the
# same signing gates a tag does, with nothing published and the installers
# attached to the run instead. Only the release-repo steps stay tag-gated, so a
# credential that has expired or a configuration Electron Builder no longer
# accepts surfaces on demand rather than on the tag that needed it.
jobs:
# Create one draft before either platform uploads. It stays private until both
# jobs and the final downloaded-asset validation succeed.
Expand Down Expand Up @@ -179,11 +184,11 @@ jobs:
echo 'No macOS signing certificate configured; building unsigned.'
fi

# A tag build that quietly produces an unsigned app is worse than a failed
# A build that quietly produces an unsigned app is worse than a failed
# one: macOS rejects unsigned updates, so it ships a release users cannot
# install or update from. Manual runs stay free to build unsigned.
- name: Require signing for tagged releases
if: startsWith(github.ref, 'refs/tags/desktop-v')
# install or update from. Manual runs are held to the same bar, which is
# what makes them a rehearsal rather than a smoke test.
- name: Require signing
working-directory: apps/desktop
run: node --import tsx scripts/assert-release-signing.ts
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand All @@ -206,7 +211,6 @@ jobs:
node --import tsx apps/desktop/scripts/verify-update-manifest.ts mac apps/desktop/dist "$DESKTOP_VERSION"

- name: Verify macOS signatures and notarization
if: startsWith(github.ref, 'refs/tags/desktop-v')
shell: bash
run: |
set -euo pipefail
Expand Down Expand Up @@ -313,9 +317,9 @@ jobs:
working-directory: apps/desktop
run: node --import tsx scripts/stage-runtime.ts

# Only non-empty WIN_CSC_* signing secrets are exported. Without them,
# Windows artifacts are unsigned and installers trigger a SmartScreen
# warning on first run.
# Only non-empty WIN_CSC_* signing secrets are exported. Without them the
# artifacts are unsigned and installers trigger a SmartScreen warning, so
# the credential check below fails the run rather than shipping one.
- name: Resolve Windows signing credentials
shell: bash
env:
Expand Down Expand Up @@ -347,8 +351,7 @@ jobs:
if [ -n "$value" ]; then printf '%s<<%s\n%s\n%s\n' "$name" "$delimiter" "$value" "$delimiter" >> "$GITHUB_ENV"; fi
done

- name: Require signing for tagged Windows releases
if: startsWith(github.ref, 'refs/tags/desktop-v')
- name: Require signing
working-directory: apps/desktop
run: node --import tsx scripts/assert-windows-release-signing.ts

Expand All @@ -366,7 +369,6 @@ jobs:
node --import tsx apps/desktop/scripts/verify-update-manifest.ts win apps/desktop/dist "$DESKTOP_VERSION"

- name: Verify Windows release signatures
if: startsWith(github.ref, 'refs/tags/desktop-v')
working-directory: apps/desktop
run: node --import tsx scripts/verify-windows-signatures.ts

Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/tests/desktop-release-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ describe('desktop release workflow', () => {
expect(workflow).toContain("if: startsWith(github.ref, 'refs/tags/desktop-v')\n shell: bash\n env:\n GH_TOKEN:")
})

it('runs every signing gate on a manual rehearsal, not only on a tag', () => {
// A manual run is the only chance to prove the signing path before the tag
// that depends on it. Gating these behind the tag would make a rehearsal
// pass while the credentials it was meant to exercise were already broken.
const rehearsed = [
'scripts/assert-release-signing.ts',
'scripts/assert-windows-release-signing.ts',
'scripts/verify-windows-signatures.ts',
'xcrun stapler validate',
]
for (const step of rehearsed) {
const stepIndex = workflow.indexOf(step)
expect(stepIndex).toBeGreaterThan(-1)
const precedingStep = workflow.lastIndexOf(' - name:', stepIndex)
// Any condition at all, not just a tag test, would skip one trigger:
// `github.event_name == 'workflow_dispatch'` reintroduces the same hole
// from the other side. These steps must be unconditional.
const declaration = workflow.slice(precedingStep, stepIndex)
expect(declaration).not.toMatch(/^\s+if:/mu)
}
})

Comment thread
coderabbitai[bot] marked this conversation as resolved.
it('downloads and revalidates uploaded assets before publishing', () => {
expect(workflow).toContain('gh release download "$RELEASE_TAG"')
expect(workflow).toContain('scripts/verify-update-manifest.ts mac release-assets')
Expand Down
Loading