Make preview versions monotonic and distinguishable from production #37

Merged
jknapp merged 3 commits from fix/preview-version-numbering into main 2026-08-27 17:41:38 +00:00
Owner

Summary

  • build-app-preview.yml computed its patch number as git rev-list --count <latest tag>..HEAD — the exact formula build-app.yml documents as broken and replaced. It measures distance from whichever tag sorts highest, not a monotonic counter, so it resets to zero on every release and previews went backwards (0.4.62 -> 0.4.0) the moment a release landed. Ported the same "highest patch used + 1" computation build-app.yml already uses for real releases.
  • The installed preview also reported a bare, production-looking version, since the bundle version fields (tauri.conf.json / Cargo.toml / package.json) strip the -preview.<sha> suffix before being patched (the Windows MSI's ProductVersion has no room for one — not something verifiable without an actual Windows build, so that mechanism was left alone). Instead, preview builds now bake the suffix into the binary via a TRIPLE_C_BUILD_SUFFIX build-time env var, and get_app_version() appends it when present. A production build sets nothing, so this is a no-op there.
  • Added prerelease to GitHubRelease and filter on it in check_for_updates — currently a no-op against real data (nothing mirrored to GitHub is ever prerelease: true), but the updater is no longer structurally incapable of enforcing a channel split if one is ever made explicit.
  • Deleted sync-release.yml: workflow_dispatch-only, reading gitea.event.release.* fields a manual dispatch never populates, so it could never have actually run. build-app.yml's inline mirror already does the same job.
  • Refactored check_for_updates's filtering into a pure, testable pick_update helper (this file had no tests before) and added tests for it and the new get_app_version suffix handling.

Closes #32

Test plan

  • cargo check / cargo clippy — clean, no new warnings
  • cargo test — 501 passed (7 new)
  • npx tsc --noEmit / npm run test — clean, 611 passed (unaffected by this change)
  • YAML syntax validated for the edited workflow
  • This PR itself is the real-world test of the preview version computation and the Windows/macOS/Linux builds, since build-app-preview.yml is the PR check
## Summary - `build-app-preview.yml` computed its patch number as `git rev-list --count <latest tag>..HEAD` — the exact formula `build-app.yml` documents as broken and replaced. It measures distance from whichever tag sorts highest, not a monotonic counter, so it resets to zero on every release and previews went **backwards** (0.4.62 -> 0.4.0) the moment a release landed. Ported the same "highest patch used + 1" computation `build-app.yml` already uses for real releases. - The installed preview also reported a bare, production-looking version, since the bundle version fields (tauri.conf.json / Cargo.toml / package.json) strip the `-preview.<sha>` suffix before being patched (the Windows MSI's `ProductVersion` has no room for one — not something verifiable without an actual Windows build, so that mechanism was left alone). Instead, preview builds now bake the suffix into the binary via a `TRIPLE_C_BUILD_SUFFIX` build-time env var, and `get_app_version()` appends it when present. A production build sets nothing, so this is a no-op there. - Added `prerelease` to `GitHubRelease` and filter on it in `check_for_updates` — currently a no-op against real data (nothing mirrored to GitHub is ever `prerelease: true`), but the updater is no longer structurally incapable of enforcing a channel split if one is ever made explicit. - Deleted `sync-release.yml`: `workflow_dispatch`-only, reading `gitea.event.release.*` fields a manual dispatch never populates, so it could never have actually run. `build-app.yml`'s inline mirror already does the same job. - Refactored `check_for_updates`'s filtering into a pure, testable `pick_update` helper (this file had no tests before) and added tests for it and the new `get_app_version` suffix handling. Closes #32 ## Test plan - [x] `cargo check` / `cargo clippy` — clean, no new warnings - [x] `cargo test` — 501 passed (7 new) - [x] `npx tsc --noEmit` / `npm run test` — clean, 611 passed (unaffected by this change) - [x] YAML syntax validated for the edited workflow - [ ] This PR itself is the real-world test of the preview version computation and the Windows/macOS/Linux builds, since `build-app-preview.yml` is the PR check
jknapp added 1 commit 2026-08-27 17:12:35 +00:00
Make preview versions monotonic and distinguishable from production
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 3s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m41s
Build App (Preview) / build-windows (pull_request) Successful in 4m51s
Build App (Preview) / build-linux (pull_request) Successful in 6m26s
Build App (Preview) / prune-previews (pull_request) Successful in 4s
b71e15c2c0
build-app-preview.yml computed its patch number as
`git rev-list --count <latest tag>..HEAD` — the exact formula build-app.yml
itself documents as broken and replaced (#26): a distance from whichever
tag sorts highest, not a counter, so it resets to zero on every release and
previews went backwards (0.4.62 -> 0.4.0) the moment one landed. Ported the
same "one past the highest patch already used" computation build-app.yml
uses for real releases, reading the same tags (including -mac/-win
suffixes), so a preview built right before a release now computes the
exact number that release is about to take — semver already orders
`0.4.12-preview.<sha> < 0.4.12`, so a preview user is offered the release
the moment it ships instead of being silently pinned forever.

The installed preview's reported version was also indistinguishable from
production: the bundle's own version field strips the `-preview.<sha>`
suffix before touching tauri.conf.json/Cargo.toml/package.json, since the
Windows MSI's ProductVersion has no room for one. Rather than risk that
(unverifiable without an actual Windows build), preview builds now bake
the suffix into the binary separately via a TRIPLE_C_BUILD_SUFFIX
build-time env var, and get_app_version() appends it when present — a
production build sets nothing, so this is a no-op there.

Also: added `prerelease` to `GitHubRelease` and filter on it in
check_for_updates (currently a no-op against real data — nothing mirrored
to GitHub is ever prerelease:true — but the updater is no longer
structurally incapable of enforcing a channel split if one is ever made
explicit). And deleted sync-release.yml: workflow_dispatch-only, reading
gitea.event.release.* fields a manual dispatch never populates, so it
could never have actually run; build-app.yml's inline mirror already does
the same job.

Refactored check_for_updates' filtering into a pure, testable pick_update
helper (this file had no tests before), and added tests for it and the
new get_app_version suffix handling.

Fixes #32.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
jknapp added 1 commit 2026-08-27 17:24:33 +00:00
Actually offer a preview the release it precedes, and fix two more gaps
Secret Scan / scan (push) Successful in 4s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m39s
Build App (Preview) / build-windows (pull_request) Successful in 4m50s
Build App (Preview) / build-linux (pull_request) Successful in 7m25s
Build App (Preview) / prune-previews (pull_request) Successful in 6s
945883bb9d
An Opus review of the previous commit found its headline claim didn't
hold: a preview and the release it precedes compute to the identical
numeric version by construction, but check_for_updates compared with a
strict `>` against the bare CARGO_PKG_VERSION (never the suffixed display
string), so `(0,4,13) > (0,4,13)` is false and the release was never
offered. Plain semver ordering doesn't make a `-preview.<sha>` suffix sort
below the same numeric release on its own here, since the comparison
never sees the suffix at all.

pick_update now takes is_preview_build, derived from whether
TRIPLE_C_BUILD_SUFFIX was baked in, and relaxes that one comparison to
`>=` — so "a release exists at my own number" reads as an update. A
production build still requires strictly newer.

Also: ported build-app.yml's `git tag --points-at HEAD` guard into the
preview version computation. Without it, workflow_dispatch (which this
workflow allows on main, not just PR builds) run on a commit a release
was already cut from would compute one past that release — reintroducing
"preview outranks production" through the manual-dispatch door. And
corrected two comments that claimed the prerelease filter was currently a
no-op: backfill-releases.yml mirrors every Gitea release to GitHub
unfiltered, prerelease flag included, so it's real defence-in-depth
against a dispatched backfill leaking a preview release, not a no-op.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
jknapp added 1 commit 2026-08-27 17:34:58 +00:00
Dedupe the preview-build predicate, fix two comment inaccuracies
Secret Scan / scan (push) Successful in 24s
Build App (Preview) / compute-version (pull_request) Successful in 6s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m39s
Build App (Preview) / build-windows (pull_request) Successful in 4m44s
Build App (Preview) / build-linux (pull_request) Successful in 6m17s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
049232099b
Final review pass gave this a clean bill of health overall but named
three small things:

- get_app_version() and check_for_updates() each read
  option_env!("TRIPLE_C_BUILD_SUFFIX") independently with slightly
  different idioms — if one were ever edited alone, the About panel and
  the update check could silently disagree about whether this is a
  preview build. Extracted preview_build_suffix() as the single place
  that reads and classifies it.
- pick_update's doc comment described the unparseable-tag case as a
  `-preview.<sha>` suffix; the actual tag build-app-preview.yml creates is
  `preview-<sha>` (no version, no dot) — already correct in the
  neighboring GitHubRelease::prerelease comment, just not here.
- That same prerelease comment claimed defence against a preview release
  leaking through backfill-releases.yml, but a preview's tag already fails
  semver parsing on its own — this field's actual job is the case parsing
  can't catch: a normally-tagged release someone flags prerelease on
  Gitea (a hotfix candidate, an RC) that a backfill would otherwise mirror
  as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
jknapp merged commit 9b55a12b32 into main 2026-08-27 17:41:38 +00:00
jknapp deleted branch fix/preview-version-numbering 2026-08-27 17:41:38 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#37