Maintainer Releasing - HenriGrimm/Minnow GitHub Wiki
How updates flow through Minnow β releasing a new version (for maintainers) and
receiving one (for users). Auto-update is built on
electron-updater against GitHub Releases
(henrigrimm/minnow). Implementation: electron/updater.ts +
electron/updater-core.ts; UI in
src/ui/settings-updates.ts and
src/os/update-menubar.ts. Tracked in
MIN-384.
Platform support today: Windows (NSIS), Linux (AppImage), and signed macOS (Developer ID + notarization). Unsigned macOS installs show a disabled updater with a signing note. On Windows/macOS hosts, build Linux with
npm run package:linux:docker(Docker). Dev and browser sessions can't self-update and show a hint instead of controls.
Open Actions β version release β Run workflow:
- Select the branch to release (normally
main). - Enter a stable version such as
0.1.7orv0.1.7, newer than every existing stable tag. - Leave Publish immediately unchecked to create a draft for review, or check it to publish as the Latest Stable release after all builds and verification pass.
- Run the workflow. Its summary links to the completed release.
The workflow builds Windows NSIS, Linux
AppImage, and signed Apple Silicon macOS DMG/ZIP packages using the same commands and
macOS secrets as the nightly beta workflow below. Every job checks out the same commit.
The entered version is applied to package.json and the lockfile in the build workspace;
the workflow does not commit a version bump back to the source branch. To keep source
builds aligned, also bump and commit the version with npm version 0.1.7 --no-git-tag-version.
Before creating a release, it requires all three update feeds and their installers, and checks the version, sizes, and SHA-512 hashes. Assets upload to a draft first. An upload failure leaves that draft unpublished. Existing releases (including drafts) and stable tags cannot be overwritten; delete an incomplete draft before starting a fresh run for the same version. Use Re-run failed jobs to retry a failed package job before a draft has been created. For a completed draft, review the notes and publish it as Latest on GitHub when ready.
Release notes come from documentation/releases/v<version>.md when present; otherwise
GitHub generates notes. Drafts remain invisible to the in-app updater.
You can also start it with the GitHub CLI:
# Build a draft for review
gh workflow run version-release.yml --ref main -f version=0.1.7 -f publish=false
# Build and publish directly to Stable
gh workflow run version-release.yml --ref main -f version=0.1.7 -f publish=trueThe workflow must be present on the default branch before GitHub enables manual runs.
Local packaging never uploads anything (electron-builder runs with --publish never).
You can also create a versioned GitHub release by hand. The build.publish config in
package.json exists only so packaging emits the latest.yml feed
file. Four steps:
Edit version in package.json:
This is mandatory for every release. An installed app decides whether to update by
comparing its own version against the one in latest.yml. If you don't bump, nobody
upgrades. Use semver: patch for fixes, minor for features.
npm run packageThis runs build β electron:build β headless:build β electron-builder and writes to release/pkg/:
| File | Role |
|---|---|
Minnow-Setup-<version>.exe |
The NSIS installer users download (Windows). |
latest.yml |
Windows update feed. Version + SHA512 hashes; electron-updater fetches this to detect a new build. |
Minnow-Setup-<version>.exe.blockmap |
Enables smaller delta downloads between versions. |
Minnow-<version>-x86_64.AppImage |
Linux portable install (npm run package:linux or package:linux:docker on Windows/macOS). |
latest-linux.yml |
Linux update feed β attach alongside the AppImage. |
On https://github.com/henrigrimm/minnow/releases β Draft a new release:
-
Tag:
v<version>(e.g.v1.0.1), created against the commit you packaged from. -
Attach platform artifacts from
release/pkg/(Windows) andrelease/pkg-linux/when you built Linux via Docker. At minimum per platform: installer/binary + matching feed (latest.ymlon Windows,latest-linux.ymlon Linux) +.blockmapwhere electron-builder emits one. Without the feed file, installed apps never see the release. -
The feed and the installer must be from the same build.
electron-updaterhashes the downloaded.exe/ AppImage / zip againstsha512+sizein the YAML. Replacing an installer on an existing tag without replacing that platformβslatest*.ymllooks like βit downloaded but will not install.β After attaching assets, runnode scripts/verify-github-update-feed.mjs. -
Publish the release (do not leave it as a draft). Draft releases are invisible to
electron-updater; Settings will show Could not check for updates until the release is published. - Release notes / body: this text is what users see under "What's new" in Settings and in the menubar popover. Write it for users, not as a changelog dump.
The pre-release checkbox on the GitHub release is the channel switch:
| GitHub setting | Who receives it |
|---|---|
| Latest release (pre-release unchecked) | Everyone on the Stable channel. |
| Pre-release (checkbox checked) | Only users who selected the Beta channel. |
Beta rides GitHub pre-releases via autoUpdater.allowPrerelease β there's no separate
beta feed to maintain.
beta nightly release runs every day at
03:30 UTC (and can be run manually). It packages Windows, Linux, and Apple Silicon macOS
from main. It uses the package version when it is ahead of the latest stable tag;
otherwise it advances to the next patch, then appends a unique
-beta.YYYYMMDD.RUN.ATTEMPT suffix, and publishes it as a GitHub
pre-release only after every installer and update feed has uploaded. Users on the Beta
channel receive it through the existing updater; Stable users do not.
Before enabling its macOS job, add these repository Actions secrets:
| Secret | Value |
|---|---|
MACOS_CERTIFICATE_P12_BASE64 |
Base64-encoded Developer ID Application .p12 certificate. |
MACOS_CERTIFICATE_PASSWORD |
Password for that .p12. |
APPLE_ID |
Apple ID used for notarization. |
APPLE_APP_SPECIFIC_PASSWORD |
Apple app-specific password. |
APPLE_TEAM_ID |
Apple Developer team ID. |
The macOS job intentionally fails when these are absent: unsigned macOS builds disable the in-app updater and are not suitable for the beta release channel.
[ ] Bumped version in package.json (and committed)
[ ] npm run package:win (and package:linux or package:linux:docker) succeeded
[ ] release/pkg/ has .exe + latest.yml + .blockmap; Linux AppImage + latest-linux.yml if shipping Linux
[ ] GitHub release tagged v<version>
[ ] All platform feed files and installers attached
[ ] Release notes written for users
[ ] Pre-release flag set correctly (unchecked = Stable, checked = Beta)
Builds are unsigned, so the first manual install of the .exe may trigger a
SmartScreen warning ("More info β Run anyway"). Auto-updates after that first install are
silent β the user doesn't re-clear SmartScreen on every update.
Automatic and calm by design. On a packaged Windows install:
-
Check. 15 seconds after launch, then every 4 hours, the app fetches
latest.ymland compares versions. Launch is never blocked on this. - Download. A newer version downloads in the background automatically β no prompt, no interruption.
- Install on your terms. Click Restart to update when you're ready. If you never do, the update installs automatically the next time you quit Minnow normally.
Menubar pill (left of the settings gear) β silent unless there's something to act on:
| State | Pill |
|---|---|
| Up to date | Hidden β zero noise. |
| Downloading | β 67% |
| Ready | Accent Restart Β· 1.0.1 β click for a popover with release notes, Restart now / Later, and a link to Settings. |
Settings β General β App updates β the full picture and all manual controls:
- Status strip: up to date / checking / downloading % / ready / couldn't check.
- Installed version, Last checked, Next automatic check.
- Update channel: Stable / Beta toggle.
- Check for updates button (manual check).
- Restart to update button (appears when a build is ready).
- What's new expander with the release notes.
Toggle Stable β Beta in Settings β General β App updates. No reinstall β the choice
persists to ~/.minnow/updater.json and triggers an immediate re-check. Beta simply opts
you into GitHub pre-releases, which may be less stable; switch back anytime.
Deliberately quiet:
- Background check fails (offline, GitHub unreachable) β silent, logged, retried next cycle. No toasts.
- You click "Check for updates" and it fails β an inline error in the status strip (the only place failures surface, since you asked).
- A download is interrupted β retried on the next cycle; a build that already finished downloading stays "ready" and is never lost to a later failed check.
The complete "old install upgrades itself" path can only be exercised once two versions exist on GitHub Releases. The practical first validation:
- Release
1.0.1, install it. - Release
1.0.2. - Launch the
1.0.1install and confirm it detects1.0.2, downloads, shows the menubar pill, and installs on restart.
Unit coverage for the state machine and UI lives in
test/electron/updater-core.test.mts,
test/settings/settings-updates.test.mts,
and test/os/update-menubar.test.mts.
| Concern | Where |
|---|---|
| Updater controller (schedule, download, install) | electron/updater.ts |
| State machine (pure, testable) | electron/updater-core.ts |
IPC channels (minnow:updater:*) |
electron/ipc-channels.ts |
| Renderer bridge + display helpers | src/electron/updater-client.ts |
| Settings UI | src/ui/settings-updates.ts |
| Menubar pill + popover | src/os/update-menubar.ts |
| Persisted channel choice | ~/.minnow/updater.json |
Publish config (--publish never locally) |
package.json build.publish + scripts/electron-builder-run.mjs
|
| Packaging targets, extra resources, asar unpack |
package.json build + context.md
|
| macOS signing & notarization | macos-signing.md |
{ "version": "1.0.1" // was 1.0.0 }