Skip to content
All lab

Note · 2026

One-command signed releases

Refusing the two release failures that are otherwise silent

A desktop app that updates itself is only as safe as its release process. One script replaced six manual steps — and refuses, up front, the two mistakes that otherwise surface on someone else's PC weeks later.

The problem

eBudget is a Windows desktop app that updates itself: every installed copy checks a feed, downloads the new installer and verifies its signature before installing. Shipping a release used to be six manual steps, and two of the ways it could go wrong were silent.

  • A version that is not higher than the current one builds perfectly, and then never offers itself to anyone — the updater only compares numbers. No error appears anywhere.
  • A build whose signing step failed still produces a working-looking installer, which every installed copy then refuses.

Both would be discovered in the office, not at the desk where the release was made.

One command

npm run release 1.0.2
npm run release 1.0.2 --tag

The script checks everything it can before it touches anything: that the target is a real version, that it is higher than the current one, and that the signing key is present — its contents, because the documented path-based variable is not honoured by the pinned CLI and would only fail at the very last step of a ten-minute build.

Then it bumps both version files (they must agree), builds, and refuses to publish anything unsigned.

A public feed for a private app

The updater downloads with no credentials, and a private repository's release assets need an authenticated request it cannot make. So the installers and the update manifest live in a separate public repository that holds nothing but releases. The download stays open; a financial system's source does not.

The trap that only shows up later

GitHub renames uploaded assets, replacing spaces with dots. The installer is built with spaces in its name, so a manifest pointing at the original name would 404 — at update time, on someone else's PC, long after the release looked successful. The script uploads under a name with nothing to rewrite, and writes the manifest itself, so the asset name and the URL that points at it are decided in one place.

Tagging, safely

With --tag it also commits the version bump, tags and pushes, so CI archives the signed installer — otherwise the only copies of a shipped build live on one PC. Two rules keep that safe: it refuses to start on a dirty working tree, and it commits only after the build is proven signed, so a tag never points at a version that does not build.