FJ-27: Add fj version update to self-replace the binary; split version into show/check/update #34

Merged
David merged 2 commits from feat/version-update-subcommands-FJ-27 into main 2026-05-31 15:22:35 +02:00
Owner

Resolves FJ-27.

Summary

Splits fj version into a show/check/update subcommand group matching the reference yt CLI, and adds a self-replacing update that downloads the newest build for the running binary's train and platform, verifies its SHA256, and atomically swaps the running executable.

Changes

  • VersionCommand becomes a wrapper struct holding an optional VersionSubcommand (Show always available; Check/Update gated behind the existing update-check feature), following the Release/Tag/User idiom. Bare fj version still defaults to show with the banner, --verbose, and --json unchanged.
  • The --check flag is removed in favor of fj version check, preserving the up-to-date / behind / ahead / latest-behind output and --json. --train works on both check and update. The check logic now returns typed ReleaseStatus/LatestStatus so check and update share it.
  • fj version update: resolves the active train (baked FJ_TRAIN, overridable by --train), runs the check, and prints "already current" and exits 0 without touching the binary when up to date. When behind it downloads the platform artifact (fj-linux-x86_64 / fj-windows-x86_64.exe via latest_arch()) from the Generic Packages registry, verifies SHA256 against the registry digest, then replaces the running binary via self-replace, reporting old -> new.
  • SHA256 source: Forgejo computes a sha256 for every Generic Packages file, fetched via the forgejo_api client's list_package_files (anonymous, like the existing release check). No manifest changes and no CI changes were needed. Both trains download from the version-pinned path .../generic/forgejo-cli/{version}/{file} (the release binary is published to packages, not as a Release asset).
  • Failure handling: on checksum mismatch, permission error, or download failure the staged temp file is discarded and the running binary is left unchanged, with localized errors.
  • self-replace is added as an optional dep wired into update-check; a plain cargo build pulls in neither it nor the new code. All download/replace logic stays in the fj binary crate, so fj-core/fj-client gain no deps.
  • New msg-version-update-* Fluent IDs (downloading, verifying, replaced, already_current, checksum_mismatch, permission_denied) added to all locales; the update-check hint now points at fj version check.

Verification

  • cargo build (feature off) and cargo build --features update-check both pass.
  • cargo fmt --check, cargo clippy --all-targets (both feature configs), and cargo test (both configs) all pass.
  • MCP-reuse grep cargo tree -p fj-client -p fj-core -e normal | grep -E 'clap|crossterm|fluent' returns nothing; fj-core gains no self-replace.
  • Feature on: fj version --help lists show/check/update; fj version update --help shows --train; bare fj version prints the banner plus the fj version check hint; fj version --json emits the banner JSON.
  • Feature off: fj version --help lists only show; bare fj version prints the banner with no update hint.

Note on the YouTrack trailer

The commit uses a bare #FJ-27 reference (no inline State Done command) per the repo's documented policy: YouTrack applies VCS commit commands on push, not on merge, so a State Done command would transition the issue prematurely when the branch is pushed. State changes are made via the YouTrack MCP/board instead.

Resolves FJ-27. ## Summary Splits `fj version` into a `show`/`check`/`update` subcommand group matching the reference `yt` CLI, and adds a self-replacing `update` that downloads the newest build for the running binary's train and platform, verifies its SHA256, and atomically swaps the running executable. ## Changes - `VersionCommand` becomes a wrapper struct holding an optional `VersionSubcommand` (`Show` always available; `Check`/`Update` gated behind the existing `update-check` feature), following the `Release`/`Tag`/`User` idiom. Bare `fj version` still defaults to `show` with the banner, `--verbose`, and `--json` unchanged. - The `--check` flag is removed in favor of `fj version check`, preserving the up-to-date / behind / ahead / latest-behind output and `--json`. `--train` works on both `check` and `update`. The check logic now returns typed `ReleaseStatus`/`LatestStatus` so `check` and `update` share it. - `fj version update`: resolves the active train (baked `FJ_TRAIN`, overridable by `--train`), runs the check, and prints "already current" and exits 0 without touching the binary when up to date. When behind it downloads the platform artifact (`fj-linux-x86_64` / `fj-windows-x86_64.exe` via `latest_arch()`) from the Generic Packages registry, verifies SHA256 against the registry digest, then replaces the running binary via `self-replace`, reporting old -> new. - SHA256 source: Forgejo computes a sha256 for every Generic Packages file, fetched via the `forgejo_api` client's `list_package_files` (anonymous, like the existing release check). No manifest changes and no CI changes were needed. Both trains download from the version-pinned path `.../generic/forgejo-cli/{version}/{file}` (the release binary is published to packages, not as a Release asset). - Failure handling: on checksum mismatch, permission error, or download failure the staged temp file is discarded and the running binary is left unchanged, with localized errors. - `self-replace` is added as an optional dep wired into `update-check`; a plain `cargo build` pulls in neither it nor the new code. All download/replace logic stays in the `fj` binary crate, so `fj-core`/`fj-client` gain no deps. - New `msg-version-update-*` Fluent IDs (downloading, verifying, replaced, already_current, checksum_mismatch, permission_denied) added to all locales; the update-check hint now points at `fj version check`. ## Verification - `cargo build` (feature off) and `cargo build --features update-check` both pass. - `cargo fmt --check`, `cargo clippy --all-targets` (both feature configs), and `cargo test` (both configs) all pass. - MCP-reuse grep `cargo tree -p fj-client -p fj-core -e normal | grep -E 'clap|crossterm|fluent'` returns nothing; `fj-core` gains no `self-replace`. - Feature on: `fj version --help` lists `show`/`check`/`update`; `fj version update --help` shows `--train`; bare `fj version` prints the banner plus the `fj version check` hint; `fj version --json` emits the banner JSON. - Feature off: `fj version --help` lists only `show`; bare `fj version` prints the banner with no update hint. ## Note on the YouTrack trailer The commit uses a bare `#FJ-27` reference (no inline `State Done` command) per the repo's documented policy: YouTrack applies VCS commit commands on push, not on merge, so a `State Done` command would transition the issue prematurely when the branch is pushed. State changes are made via the YouTrack MCP/board instead.
feat(version): split into show/check/update; add self-replacing update
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m35s
83e09bcb1e
Bring `fj version` in line with the reference `yt` CLI by converting the flat `VersionCommand` flag struct into a subcommand group (`show`, `check`, `update`) via the same wrapper-struct idiom as `Release`/`Tag`/`User`. Bare `fj version` still defaults to `show`, keeping the banner, `--verbose`, and `--json` output unchanged. The `--check` flag is removed; `fj version check` replaces it and preserves the existing up-to-date / behind / ahead / latest-behind messages and `--train` override. `show`, `check`, and `update` share the train-resolution and check logic, which now returns typed `ReleaseStatus`/`LatestStatus` values instead of printing inline.

`fj version update` resolves the active train (baked `FJ_TRAIN`, overridable by `--train`), runs the check, and exits 0 with an "already current" message when up to date without touching the binary. When behind, it downloads the platform artifact for the running binary's train and target (`fj-linux-x86_64` / `fj-windows-x86_64.exe` via `latest_arch()`) from the Generic Packages registry, verifies its SHA256 against the registry digest, then atomically swaps the running executable with `self-replace`. The release binary lives at the version-pinned Generic Packages path (the create-release workflow makes a tagless Release entry; the binary is published to packages), so both trains download from `.../generic/forgejo-cli/{version}/{file}`. Forgejo computes a sha256 for every package file, fetched via the `forgejo_api` client's `list_package_files` (anonymous, like the existing release check) - no new manifest fields or CI changes. On a checksum mismatch, permission error, or download failure the staged temp file is discarded and the running binary is left unchanged.

`self-replace` is added as an optional dep wired into the `update-check` feature; a plain `cargo build` pulls in neither it nor any of the new check/update code. All download/replace logic stays in the `fj` binary crate, so `fj-core`/`fj-client` gain no deps and the MCP-reuse grep still returns nothing. New `msg-version-update-*` Fluent IDs (downloading, verifying, replaced, already_current, checksum_mismatch, permission_denied) are added to all locales, and the update-check hint now points at `fj version check`.

#FJ-27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge branch 'main' into feat/version-update-subcommands-FJ-27
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 1m33s
aad4d7e8ac
David merged commit ae1a6c7cf7 into main 2026-05-31 15:22:35 +02:00
David deleted branch feat/version-update-subcommands-FJ-27 2026-05-31 15:22:35 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/forgejo-cli!34
No description provided.