From 12487794a3c9bdf645c11edbf3095979bca4d997 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:26:06 -0400 Subject: [PATCH] Engine: app.update.check reports whether a newer release is published (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same check as the app's Help › Check for Updates and fieldcraft-cli update --check: the latest release, whether it is newer, its page, whether it is signed and the asset this install would download. It only reports. Checking reaches the network, so only the desktop app and the CLI switch it on; tests, scratch sessions and the browser build refuse without asking. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- Cargo.lock | 1 + THIRD-PARTY-LICENSES.txt | 135 +++++++++++++++++++++++++++----- crates/engine/Cargo.toml | 2 + crates/engine/src/catalogue.rs | 2 +- crates/engine/src/cmd/forms.rs | 1 + crates/engine/src/cmd/mod.rs | 2 + crates/engine/src/cmd/update.rs | 59 ++++++++++++++ crates/engine/src/lib.rs | 1 + docs/features.md | 2 +- 9 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 crates/engine/src/cmd/update.rs diff --git a/Cargo.lock b/Cargo.lock index c7208b0..f9ae3d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1320,6 +1320,7 @@ dependencies = [ "fieldcraft-parallel", "fieldcraft-render", "fieldcraft-solver", + "fieldcraft-update", "log", "png", "serde", diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt index a04eee9..a576be9 100644 --- a/THIRD-PARTY-LICENSES.txt +++ b/THIRD-PARTY-LICENSES.txt @@ -1,6 +1,6 @@ Third-party software in FieldCraft -FieldCraft (the desktop app, fieldcraft-cli and the web app) is built from the 474 crates below. +FieldCraft (the desktop app, fieldcraft-cli and the web app) is built from the 477 crates below. Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files. FieldCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE. @@ -123,6 +123,7 @@ fastrand 2.5.0 (Apache-2.0 OR MIT) fax 0.2.7 (MIT) fdeflate 0.3.7 (MIT OR Apache-2.0) fearless_simd 0.4.1 (Apache-2.0 OR MIT) +filetime 0.2.29 (MIT/Apache-2.0) flate2 1.1.10 (MIT OR Apache-2.0) float-cmp 0.9.0 (MIT) foldhash 0.2.0 (Zlib) @@ -190,6 +191,7 @@ matext4cgmath 0.1.0 (Apache-2.0) memchr 2.8.3 (Unlicense OR MIT) memmap2 0.9.11 (MIT OR Apache-2.0) memoffset 0.9.1 (MIT) +minisign-verify 0.3.0 (MIT) miniz_oxide 0.8.9 (MIT OR Zlib OR Apache-2.0) miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0) moxcms 0.8.1 (BSD-3-Clause OR Apache-2.0) @@ -343,6 +345,7 @@ svgtypes 0.16.1 (Apache-2.0 OR MIT) syn 1.0.109 (MIT OR Apache-2.0) syn 2.0.119 (MIT OR Apache-2.0) syn 3.0.9 (MIT OR Apache-2.0) +tar 0.4.46 (MIT OR Apache-2.0) tempfile 3.27.0 (MIT OR Apache-2.0) termcolor 1.4.1 (Unlicense OR MIT) thiserror 1.0.69 (MIT OR Apache-2.0) @@ -781,21 +784,21 @@ ahash 0.8.12, arbitrary 1.5.0, arrayvec 0.7.8, async-channel 2.5.0, async-execut core-foundation 0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23, data-url 0.3.2, derive_arbitrary 1.5.0, either 1.19.0, equivalent 1.0.2, errno 0.3.14, euclid 0.22.14, -event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10, futures-lite -2.6.1, gethostname 1.1.0, glow 0.17.0, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, -httparse 1.10.1, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys 0.3.106, khronos-egl -6.0.0, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, num-complex 0.4.6, -num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1, -parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png -0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core 1.13.0, -renderdoc-sys 1.1.0, robust 1.2.0, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, rustix 1.1.5, -rustls 0.23.46, rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, security-framework -3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, simplecss 0.2.2, -smallvec 1.16.3, smol_str 0.2.2, svgtypes 0.16.1, syn 1.0.109, tempfile 3.27.0, ttf-parser 0.25.1, -unicode-segmentation 1.13.3, unicode-width 0.2.2, uuid 1.28.0, wasi 0.11.1+wasi-snapshot-preview1, -wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro -0.2.129, wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl -0.1.12, wit-bindgen 0.57.1 +event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, filetime 0.2.29, flate2 1.1.10, +futures-lite 2.6.1, gethostname 1.1.0, glow 0.17.0, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi +0.5.3, httparse 1.10.1, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys 0.3.106, +khronos-egl 6.0.0, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, +num-complex 0.4.6, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, +parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, +plain 0.2.3, png 0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core +1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, +rustix 1.1.5, rustls 0.23.46, rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, +security-framework 3.7.0, security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 +1.2.0, simplecss 0.2.2, smallvec 1.16.3, smol_str 0.2.2, svgtypes 0.16.1, syn 1.0.109, tar 0.4.46, +tempfile 3.27.0, ttf-parser 0.25.1, unicode-segmentation 1.13.3, unicode-width 0.2.2, uuid 1.28.0, +wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, +wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support 0.2.129, +wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12, wit-bindgen 0.57.1 ================================================================================ Apache License @@ -3154,9 +3157,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ -cfg-if 1.0.5, js-sys 0.3.106, openssl-probe 0.2.1, scoped-tls 1.0.1, wasm-bindgen 0.2.129, -wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support 0.2.129, -wasm-bindgen-shared 0.2.129, web-sys 0.3.106 +cfg-if 1.0.5, filetime 0.2.29, js-sys 0.3.106, openssl-probe 0.2.1, scoped-tls 1.0.1, wasm-bindgen +0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support +0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106 ================================================================================ Copyright (c) 2014 Alex Crichton @@ -8036,6 +8039,70 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +================================================================================ +minisign-verify 0.3.0 +================================================================================ + +Copyright (c) 2019-2025 Frank Denis + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +-- + +Code in the src/crypto folder is derived from the rust-crypto project: +https://github.com/DaGenix/rust-crypto + +Original ISC license follows: + +Copyright (c) 2006-2009 Graydon Hoare +Copyright (c) 2009-2013 Mozilla Foundation + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + ================================================================================ miniz_oxide 0.8.9, miniz_oxide 0.9.1 ================================================================================ @@ -11456,6 +11523,36 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +================================================================================ +tar 0.4.46 +================================================================================ + +Copyright (c) The tar-rs Project Contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + ================================================================================ tempfile 3.27.0 ================================================================================ diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index f3d10e2..c0c3aae 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -25,6 +25,8 @@ serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } log = { workspace = true } +# app.update.check (docs/updates.md); the browser build has no update check. +fieldcraft-update = { workspace = true } [dev-dependencies] png = { workspace = true } diff --git a/crates/engine/src/catalogue.rs b/crates/engine/src/catalogue.rs index 0b961ad..d4fd79a 100644 --- a/crates/engine/src/catalogue.rs +++ b/crates/engine/src/catalogue.rs @@ -597,7 +597,7 @@ pub static CATALOGUE: &[Capability] = &[ Tools, "Help", "Draft a GitHub bug report with the version, the OS and the last error; you review it, and nothing is sent until you submit it", - &["diagnostics.crash_reports", "diagnostics.crash_report", "diagnostics.crash_test"], + &["diagnostics.crash_reports", "diagnostics.crash_report", "diagnostics.crash_test", "app.update.check"], ), ]; diff --git a/crates/engine/src/cmd/forms.rs b/crates/engine/src/cmd/forms.rs index 9359c38..c1f03b7 100644 --- a/crates/engine/src/cmd/forms.rs +++ b/crates/engine/src/cmd/forms.rs @@ -1716,6 +1716,7 @@ mod tests { const STATE: &[&str] = &[ "open a design first", + "update checks are off", "`boundary_conditions` or `loads`", "give one of", "is not an explicit", diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index ead6954..77fa861 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod seeds; pub(crate) mod solve; mod symmetry; mod tags; +pub mod update; pub(crate) mod view; use crate::schema::ProblemKind; @@ -158,6 +159,7 @@ pub static ALL: &[CommandSpec] = &[ diagnostics::CRASH_REPORT, diagnostics::BUG_REPORT, diagnostics::CRASH_TEST, + update::CHECK, ]; /// The problem with required parameter `key`: [`ProblemKind::Required`] when it is left out, diff --git a/crates/engine/src/cmd/update.rs b/crates/engine/src/cmd/update.rs new file mode 100644 index 0000000..debfd5f --- /dev/null +++ b/crates/engine/src/cmd/update.rs @@ -0,0 +1,59 @@ +//! `app.update.check`: is a newer FieldCraft published? The same check as the desktop app's +//! Help › Check for Updates and `fieldcraft-cli update --check` (docs/updates.md). It only +//! reports: nothing is downloaded or installed, and no dialog opens. +//! +//! Checking reaches the network, so it is switched on only by the programs that may +//! ([`enable_checks`]: the desktop app and the CLI); everywhere else (tests, scratch sessions, +//! the browser) the command refuses without asking anything. + +use std::sync::atomic::{AtomicBool, Ordering}; + +use serde_json::Value; + +use crate::{CommandKind, CommandSpec, EngineError, Result, Session}; + +pub const CHECK: CommandSpec = CommandSpec { + id: "app.update.check", + label: "Check for Updates", + params: r#"{} — asks GitHub Releases for the latest published FieldCraft release and compares it with this version: current, latest, newer, url (its page and release notes), published, signed (whether it can be verified and installed from the app) and asset (the file this install would download; null when it cannot update itself). Reports only: nothing is downloaded or installed. Desktop app and fieldcraft-cli only"#, + form: Some(&[]), + kind: CommandKind::Session, + requires: &[], + run: check, +}; + +static ENABLED: AtomicBool = AtomicBool::new(false); + +/// Let `app.update.check` reach the release server (the desktop app and the CLI do). +pub fn enable_checks(on: bool) { + ENABLED.store(on, Ordering::SeqCst); +} + +#[cfg(target_arch = "wasm32")] +fn check(_: &mut Session, _: &Value) -> Result { + Err(EngineError::Params("not in the browser: the web app offers Reload to update when a new version is deployed".into())) +} + +#[cfg(not(target_arch = "wasm32"))] +fn check(_: &mut Session, _: &Value) -> Result { + if !ENABLED.load(Ordering::SeqCst) { + return Err(EngineError::Params("update checks are off in this session (the desktop app and fieldcraft-cli check for updates)".into())); + } + let check = fieldcraft_update::net::Client::from_env().check().map_err(|e| EngineError::Params(e.to_string()))?; + let asset = fieldcraft_update::install::here().asset_name(&check.latest.version, std::env::consts::ARCH); + Ok(check.to_json(asset.as_deref())) +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + /// Off unless a program switches it on: tests and scratch sessions never reach the network. + #[test] + fn checks_are_off_unless_enabled() { + let e = Session::default().execute("app.update.check", &json!({})).unwrap_err(); + assert!(e.to_string().contains("update checks are off"), "{e}"); + } +} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index b9bb7ab..69523e0 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -21,6 +21,7 @@ pub mod logfile; pub use cmd::analytic_surfaces; pub use cmd::replace::{MESHERS, Replacement, replacement}; +pub use cmd::update::enable_checks as enable_update_checks; pub use fieldcraft_io::{Item as ViewItem, ItemKind}; pub mod history; pub mod progress; diff --git a/docs/features.md b/docs/features.md index e71c60c..13889b0 100644 --- a/docs/features.md +++ b/docs/features.md @@ -163,4 +163,4 @@ the control channel and AI agents over MCP (docs/mcp.md) run the same commands. | View | Show All — Show every item hidden in the viewport (hide, show and isolate items from the browser) | ready | `view.show_all`, (`view.hide`), (`view.show`), (`view.isolate`) | | History | Undo — Undo the last command that changed the model | ready | `edit.undo` | | History | Redo — Redo the command undone last | ready | `edit.redo` | -| Help | Report a Bug — Draft a GitHub bug report with the version, the OS and the last error; you review it, and nothing is sent until you submit it | ready | `diagnostics.bug_report`, `diagnostics.crash_reports`, `diagnostics.crash_report`, `diagnostics.crash_test` | +| Help | Report a Bug — Draft a GitHub bug report with the version, the OS and the last error; you review it, and nothing is sent until you submit it | ready | `diagnostics.bug_report`, `diagnostics.crash_reports`, `diagnostics.crash_report`, `diagnostics.crash_test`, `app.update.check` |