Skip to content

feat(dist): per-OS installers, release-asset upload, startup update notifier - #23

Merged
Xander-git merged 7 commits into
mainfrom
feat/distribution-installers-update-notifier
May 30, 2026
Merged

feat(dist): per-OS installers, release-asset upload, startup update notifier#23
Xander-git merged 7 commits into
mainfrom
feat/distribution-installers-update-notifier

Conversation

@Xander-git

Copy link
Copy Markdown
Collaborator

Summary

Hardens cross-OS distribution and lays the groundwork for future auto-updates. Three increments (all opted into during planning):

  1. Release assets — published releases now carry downloadable binaries.
  2. Real installers.exe / .dmg / .AppImage instead of a raw zipped folder.
  3. Startup update notifier — the precursor to a tufup silent auto-updater.

The app already produced PyInstaller --onedir bundles via a 3-runner matrix; previously the build only uploaded login-gated workflow artifacts (the README's "publishes under Releases" claim was aspirational). This wires the last mile.

Step 1 — Release-asset upload (.github/workflows/build.yml)

  • Adds permissions: contents: write.
  • New Attach assets to release step (softprops/action-gh-release@v2, gated if: github.event_name == 'release') uploads each OS's installer + raw archive to the published release; matrix jobs append to the same tag.
  • Trigger stays release: published (publish-triggered, per decision). PR/dispatch builds still smoke-test + upload-artifact.

Step 2 — Per-OS installers (new packaging/)

  • Windows → Inno Setup .exe: installs the onedir to Program Files, Start-menu/desktop shortcuts to the ExLab-Wizard.exe CLI alias, uninstaller, silent WebView2 Evergreen bootstrap (pywebview needs it). No autostart registry writes — the in-app AutostartManager owns that, avoiding double-registration.
  • macOS.dmg via create-dmg (with hdiutil fallback) wrapping the existing .app.
  • Linux → AppImage via appimagetool. Deliberately not Flatpak/Snap — their sandboxes break secretstorage (keyring) + GTK-WebKit (webview).
  • All degrade gracefully on the missing app icon (none in assets/icons/ yet).

Step 3 — Startup update notifier (new src/exlab_wizard/update_check/)

  • One-shot daemon-thread probe on tray startup (mirrors StatusTicker) → GitHub Releases API via httpx (reusing the LIMS client shape, required GitHub User-Agent).
  • On a newer tag: one OS notification via the existing notify() wrapper + a "Check for updates…" tray item opening the releases page.
  • Gated on config.update_check.enabled (default on, per decision; offline sites set false).
  • Degrades silently on offline/firewalled/rate-limited/malformed-tag — never raises, never falsely prompts.
  • Notifier-only: tufup silent auto-update and code-signing remain deferred (Design Spec §15.6 / §15.8 item 3).

Also adds packaging>=23 (version comparison) and [project.urls].

Test plan

  • ruff check / ruff format --check clean on all touched files
  • mypy src/exlab_wizard/update_check — no issues
  • ✅ Our suites green: pytest tests/unit/update_check tests/unit/tray tests/unit/config (incl. 17 new tests covering version compare, HTTP fetch ok/403/500, runner newer/equal/none)
  • ✅ Workflow YAML validated (on: = pull_request / release / workflow_dispatch)
  • ⚠️ 4 pre-existing tests/unit/sync/test_nas_client* timing flakes — confirmed identical on the clean tree (changes stashed); unrelated to this PR
  • ⬜ CI dry-run (push a throwaway v0.0.0-rc1 tag) and per-OS installer smoke are manual follow-ups before the first real release

Releasing

Bump __version__ (src/exlab_wizard/__init__.py + pyproject.toml in lockstep), then publish a GitHub Release on the vX.Y.Z tag → CI builds and attaches all three installers.

🤖 Generated with Claude Code

Xander-git and others added 7 commits May 30, 2026 04:06
- Move design_specs/ into docs/design_specs/ and fix relative links in
  the frontend spec to match the new location
- Remove obsolete REMAINING_WORK planning docs
- Anchor graphify-out/ ignore rule to repo root
- Update sync/logo SVG assets and add sync_nas asset
- Regenerate graphify-out/ knowledge-graph artifacts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design for: inline content-file editing, structured Copier question
form, file upload into template dirs, and per-instance template
folders (typed equipment|project|run subfolders serving as both
child-source and provenance copy). Adds template/{authoring,lint,
manifest,resolution,provenance}.py and a NiceGUI editor page;
factors §5.8 lint out of TemplateEngine.resolve into a shared module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otifier

Harden distribution and lay the groundwork for future auto-updates.

Step 1 — Release assets: build.yml now grants `contents: write` and, on a
published release, attaches each OS's installer + raw archive via
softprops/action-gh-release. PR/dispatch builds still upload workflow
artifacts. Trigger stays `release: published`.

Step 2 — Real installers (new packaging/):
- Windows: Inno Setup .exe (Start-menu/desktop shortcut to the ExLab-Wizard
  CLI alias, uninstaller, silent WebView2 bootstrap; no autostart writes —
  the in-app AutostartManager owns that).
- macOS: .dmg via create-dmg (hdiutil fallback) wrapping the .app.
- Linux: AppImage via appimagetool (not Flatpak/Snap — sandbox breaks
  keyring + webview). All degrade gracefully on the absent app icon.

Step 3 — Startup update notifier (new src/exlab_wizard/update_check/):
one-shot daemon-thread probe of the GitHub Releases API (httpx, mirroring
the LIMS client shape); on a newer tag fires one OS notification via the
existing notify() wrapper and surfaces a "Check for updates…" tray item
opening the releases page. Gated on config update_check.enabled (default
on; offline sites set false). Notifier-only — tufup silent auto-update and
code-signing remain deferred (Design Spec §15.6 / §15.8 item 3).

Adds packaging>=23 (version comparison) and [project.urls]; 17 unit tests
covering version compare, the HTTP fetch (ok/403/500), and the runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
choco install innosetup does not add ISCC.exe to the current shell PATH on
the windows-latest runner, so the build step failed with "iscc not
recognized". Resolve the compiler from its known install dir (with a search
fallback) before invoking it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Git-bash on windows-latest rewrites the leading-slash "/DAppVersion=..."
argument into a Windows path, so ISCC treated it as a second script
filename ("You may not specify more than one script filename"). Set
MSYS_NO_PATHCONV=1 for the compiler invocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lint job's `ruff format --check .` flags these four files repo-wide
(already red on main). Format them so the PR's lint check passes; no
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Xander-git
Xander-git merged commit f040cb1 into main May 30, 2026
6 of 8 checks passed
@Xander-git
Xander-git deleted the feat/distribution-installers-update-notifier branch May 30, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant