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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -344,5 +344,5 @@ jobs:
if-no-files-found: ignore

# NOTE: packaging the desktop app (electron-builder + the uv Python sidecar) is a
# separate, tag-triggered release workflow — see electron/PACKAGING.md. It is not
# wired here because it needs per-OS signing secrets / vendored uv binaries.
# separate, tag-triggered release workflow — see .github/workflows/release.yml. It
# is not wired here because it needs per-OS signing secrets / vendored uv binaries.
34 changes: 29 additions & 5 deletions .github/workflows/prepare_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
# electron/package.json is the source of truth for the app's semver
# (electron-builder/electron-updater read it); the release tag must be
# v<that version>. Tag shapes and the stable/beta channel mapping are
# documented in RELEASING.md.
# documented in release.yml's `channel` job.
- name: Compute new version
id: version
env:
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
exit 1
fi

# ── Pre-flight checks from RELEASING.md — fail HERE, not at release time ──
# ── Release pre-flight checks — fail HERE, not at release time ──────────
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
Expand Down Expand Up @@ -220,10 +220,28 @@ jobs:

### Review checklist
- [ ] Version is correct in \`electron/package.json\`
- [ ] Manual checks from RELEASING.md done on a real machine (GPU path,
distributed navigator, clean shutdown)
- [ ] CI passes

### Manual checks the automated suite cannot cover
Do these on a real machine — CI has no GPU/display and the migrated
suite forces \`SPYDE_NO_DASK=1\`:
- [ ] **Distributed navigator path** — open a multi-GB 4D-STEM scan, drag the
navigator; the diffraction pattern must track without freezing. If the
\`repro_*.py\` distributed scripts are present, run them directly
(\`uv run python -m spyde.tests.repro_<name>\`); they spin a real
\`LocalCluster\` and won't run under pytest.
- [ ] **GPU find-vectors / orientation** — on a CUDA box, run Find Vectors and
Orientation Mapping on a real dataset (e.g. \`pyxem.data.sped_ag()\`); confirm
results look right and nothing segfaults. (The numba subpixel kernel is unit-
tested for arithmetic, but the live CUDA path needs a real run.)
- [ ] **No-GPU fallback** — on a machine without \`numba\`/CUDA, the app must still
launch and Find Vectors must fall back to CPU. (Guarded by
\`test_find_vectors_no_numba.py\`, but verify the PACKAGED app.)
- [ ] **IPF colour-key legend** — open an Orientation map; the colour-key triangle
pins in the corner on the 2-D map and hides on the 3-D view.
- [ ] **Clean shutdown** — quit the app; confirm no orphaned \`python.exe\` / Dask
worker processes remain (Task Manager / \`ps\`).

### After merging
Tag **the merge commit on main** — the tag MUST be exactly \`${TAG}\`:
\`\`\`bash
Expand All @@ -232,4 +250,10 @@ jobs:
git push origin ${TAG}
\`\`\`
The tag push triggers \`release.yml\` (3-platform build → draft release →
publish once all legs pass)."
publish once all legs pass).
- [ ] **Confirm the release actually has installers** before announcing it:
\`gh release view ${TAG} --repo CSSFrancis/spyde --json assets --jq '.assets[].name'\`
— expect the three installers (\`SpyDE Setup *.exe\`, \`SpyDE-*.dmg\`,
\`SpyDE-*.AppImage\`), their \`.blockmap\`s, and the \`latest*.yml\` update
feeds. \`release.yml\`'s \`finalize\` job refuses to un-draft an
asset-less release, but check."
14 changes: 9 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: Release

# Fires only when a version tag (e.g. v0.1.0) is pushed.
# Builds the Electron desktop app (with the uv-managed Python sidecar — see
# electron/PACKAGING.md, DISTRIBUTION_PLAN.md "Option A") on each platform in
# parallel, publishing straight to a DRAFT GitHub Release so electron-builder
# electron/electron-builder.yml + electron/scripts/bundle-python.mjs) on each
# platform in parallel, publishing straight to a DRAFT GitHub Release so electron-builder
# generates the latest.yml/latest-mac.yml/latest-linux.yml feeds electron-
# updater's autoUpdater needs. The draft only goes live once all three legs
# succeed (`finalize`, gated on `needs: build`) so users never see a partial
Expand Down Expand Up @@ -170,7 +170,8 @@ jobs:
with:
fetch-depth: 0

# electron/package.json's "version" is hand-set (RELEASING.md) — it's
# electron/package.json's "version" is hand-set (bumped by the Prepare
# Release workflow, prepare_release.yml) — it's
# what electron-builder/electron-updater treat as the app's semver, so a
# drift here would ship an installer electron-updater can't reason about
# correctly (or a release that silently doesn't offer itself as an
Expand Down Expand Up @@ -244,7 +245,9 @@ jobs:

# ── macOS code signing (only when secrets are present) ─────────────────────
# Import the Developer ID Application cert into a throwaway keychain so
# electron-builder can sign + (with notarize:true) notarize. See SIGNING.md.
# electron-builder can sign + (with notarize:true) notarize. The full
# cert-acquisition + secret-setup walkthrough (and the six-secret table)
# lives in .github/workflows/verify_signing_secrets.yml's header comment.
# Gated on both runner.os == macOS AND the cert secret being set, so a fork
# / secret-less run skips it and electron-builder produces an unsigned build
# (rather than failing) — matching electron-builder.yml's no-identity-null.
Expand Down Expand Up @@ -288,7 +291,8 @@ jobs:
# update-feed metadata (latest.yml / latest-mac.yml / latest-linux.yml)
# electron-updater's autoUpdater reads at runtime — publish:false builds
# never generate these files at all. On macOS the app is signed + notarized
# when MAC_SIGNING_ENABLED (see the signing steps above + SIGNING.md).
# when MAC_SIGNING_ENABLED (see the signing steps above +
# verify_signing_secrets.yml).
# -c.publish.owner/.repo pin the publish target to THE REPO THIS WORKFLOW
# RUNS IN — the same-repo assumption the draft-release design above already
# makes (GITHUB_TOKEN can't publish cross-repo anyway). Without the
Expand Down
74 changes: 72 additions & 2 deletions .github/workflows/verify_signing_secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,78 @@
# Apple with the API key + key-id + issuer + team-id, proving those four all
# work together (fails loudly if any is wrong), without submitting a build.
#
# Run: Actions tab → "Verify signing secrets" → Run workflow. Delete this file
# once signing is confirmed working end-to-end, or keep it as a pre-release probe.
# Run: Actions tab → "Verify signing secrets" → Run workflow. Keep this file as
# a pre-release probe — it is also the home of the secret-setup walkthrough below.
#
# ═══════════════════════════════════════════════════════════════════════════════
# HOW THE SIX SECRETS ARE MADE (one-time cert/key setup, done on a Mac)
# ═══════════════════════════════════════════════════════════════════════════════
#
# Signing an Apple app = two required steps since Catalina: (1) CODE-SIGN the
# .app/.dmg/.zip with a Developer ID Application cert, and (2) NOTARIZE (upload
# to Apple, staple the ticket). electron-builder does both during `npm run dist`
# (notarize: true in electron/electron-builder.yml); release.yml's mac leg
# imports the cert + stages the API key, gated on MAC_SIGNING_ENABLED (true when
# MAC_CERT_P12_BASE64 is set — a fork/secret-less run builds UNSIGNED, not red).
#
# Step 1 — Create the certificate. Distributing OUTSIDE the App Store needs a
# "Developer ID Application" cert — NOT "Mac App Distribution" and NOT
# "Developer ID Installer" (that one is only for .pkg installers, which SpyDE
# doesn't ship). Xcode → Settings → Accounts → your Apple ID → Manage
# Certificates → + → Developer ID Application (or developer.apple.com →
# Certificates, IDs & Profiles → Certificates → +). Lands the cert + private
# key in your login keychain.
#
# Intermediate-chain gotcha: the cert chains up through Apple's CURRENT
# "Developer ID Certification Authority (G2)" intermediate — NOT the older
# non-G2 "Developer ID Certification Authority" (being retired; new certs
# chain to G2):
# Apple Root CA
# └─ Developer ID Certification Authority (G2) ← the intermediate
# └─ Developer ID Application: You (TEAMID) ← your signing cert
# You normally don't pick this by hand, but a CI runner's keychain missing the
# G2 intermediate shows up as "unable to build chain" / CSSMERR_TP_NOT_TRUSTED.
# Guard against it by exporting the full chain into the .p12 (Step 2) — and
# release.yml belt-and-braces fetches
# https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer into the CI
# keychain anyway.
#
# Step 2 — Export as .p12 (CI has no keychain). In Keychain Access, Cmd-click
# BOTH "Developer ID Application: Your Name (TEAMID)" AND "Developer ID
# Certification Authority (G2)" (download + double-click the G2 .cer first if
# it isn't shown), right-click → Export … (2 items) → certificate.p12 with an
# export password. Then: base64 -i certificate.p12 -o certificate.p12.base64
#
# Step 3 — App Store Connect API key (for notarization; preferred over an
# app-specific password — revocable, no 2FA prompts). appstoreconnect.apple.com
# → Users and Access → Integrations tab → App Store Connect API → + → role
# "Developer". DOWNLOAD THE .p8 — you can only download it once. Note the Key
# ID and (top of that page) the Issuer ID (a UUID). Your Team ID (10 chars) is
# on developer.apple.com → Membership.
#
# Step 4 — The six repo secrets (Settings → Secrets and variables → Actions):
#
# | Secret | Value |
# |----------------------|----------------------------------------------------|
# | MAC_CERT_P12_BASE64 | contents of certificate.p12.base64 (Step 2) |
# | MAC_CERT_PASSWORD | the .p12 export password (Step 2) |
# | APPLE_API_KEY_P8 | full contents of the .p8 file (Step 3) |
# | APPLE_API_KEY_ID | the API Key ID (Step 3) |
# | APPLE_API_ISSUER_ID | the Issuer ID (UUID, Step 3) |
# | APPLE_TEAM_ID | your 10-char Team ID |
#
# Set the .p8 byte-for-byte (`gh secret set APPLE_API_KEY_P8 < AuthKey_X.p8`)
# — never through TextEdit; smart-quote substitution mangles PEM keys (the
# validation below diagnoses exactly that). NB electron-builder wants
# APPLE_API_KEY as a *file path* — release.yml's "Stage notarization API key"
# step writes the secret to disk and exports the path.
#
# Verify a published build on any Mac:
# spctl -a -vvv -t install SpyDE-*.dmg → "accepted, source=Notarized Developer ID"
# codesign -dv --verbose=4 /Applications/SpyDE.app → Authority=Developer ID Application
# stapler validate SpyDE-*.dmg → "The validate action worked!"
# Check the .zip too — it is the auto-update payload and must be notarized, or
# updates re-trigger Gatekeeper.
name: Verify signing secrets

on:
Expand Down
Loading
Loading