diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-22 10:31:05 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-22 10:31:05 +0000 |
| commit | 21d0f61f100a778b505169b62e1cf2b615c1833e (patch) | |
| tree | da2e791e566c9cd75d541d94b288ef06bd599a81 | |
| parent | 273555f53458eb80d15c3d5c1910e57428818ee4 (diff) | |
feat(bundle): report newer nightly builds on startup
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | bundle/AGENTS/DEVELOPMENT.md | 2 | ||||
| -rw-r--r-- | bundle/README.md | 2 | ||||
| -rw-r--r-- | bundle/ThirdPartyNotices.md | 2 | ||||
| -rw-r--r-- | bundle/src-tauri/Cargo.lock | 679 | ||||
| -rw-r--r-- | bundle/src-tauri/Cargo.toml | 3 | ||||
| -rw-r--r-- | bundle/src-tauri/build.rs | 3 | ||||
| -rw-r--r-- | bundle/src-tauri/src/main.rs | 7 | ||||
| -rw-r--r-- | bundle/src-tauri/src/update.rs | 124 |
8 files changed, 819 insertions, 3 deletions
diff --git a/bundle/AGENTS/DEVELOPMENT.md b/bundle/AGENTS/DEVELOPMENT.md index df9ca28..40d9892 100644 --- a/bundle/AGENTS/DEVELOPMENT.md +++ b/bundle/AGENTS/DEVELOPMENT.md @@ -33,3 +33,5 @@ After building the compiler, run `cargo test --release --manifest-path src-tauri The native menu is defined once in `src-tauri/src/menu.rs` with Tauri's menu API; macOS shows it in the system menu bar, Windows and Linux at the top of each window. Only the macOS application menu is platform-specific. Edit keeps the predefined clipboard and undo items, which macOS needs in the webview. macOS Quit (Cmd+Q) is a custom item that closes every window, so each runs its unsaved-changes prompt and the app exits after the last; the predefined Quit terminates without them. Windows and Linux close windows instead. Custom item ids name commands in `src/main.js`; a click emits a `menu` event with the id to the focused window. Run/Stop (F5) toggles like the Run button; its label is fixed because all windows share the menu. Keyboard shortcuts are Monaco `KeyMod.CtrlCmd` keybindings: the editor registers Find and Toggle Line Comment, and the document handler matches the menu's commands with `StandardKeyboardEvent.equals`, so a shortcut runs once whether the webview or the native menu receives the key first. Keep menu accelerators and handler bindings in sync. Never test `ctrlKey`/`metaKey` directly; Monaco resolves CtrlCmd to Cmd on macOS and Ctrl elsewhere. Use brief English UI labels and messages. Keep one document per window and prefilled stdin. More documents use native OS windows created by the `new_window` command from the configured window; do not add tabs or in-page switching. Before feature commits, run affected plugin tests, build the frontend and check native compilation as appropriate. Do not add a separate UI test framework for routine changes. + +`src-tauri/src/update.rs` checks for a newer nightly once per launch (not per window) on a background thread, fetching the release's `version.json` and comparing `bundle.time` with the embedded build time. If newer, a dialog points to the release page; there is no auto-update and every failure is silent. Release builds must set `CPSC_BUNDLE_TIME` (UTC `YYYYMMDD-HHMMSS`) and `CPSC_BUNDLE_COMMIT` (short hash) at build time; without them (dev, `cargo test`, Linux CI) the check does nothing. `CPSC_BUNDLE_VERSION_URL` overrides the version.json URL at build time for local tests against a local HTTP server; never hit the real release in tests. diff --git a/bundle/README.md b/bundle/README.md index 7288463..f7224e9 100644 --- a/bundle/README.md +++ b/bundle/README.md @@ -7,3 +7,5 @@ Shortcuts use Cmd on macOS and Ctrl on Windows: **N** new, **Shift+N** new windo Nightly provides a standalone Windows x64 `.exe` and a macOS Apple Silicon `.tar.xz` containing the `.app`. Windows extracts the included `cpsc.exe` into the current working directory on startup; macOS keeps it inside the `.app`. Windows uses the system WebView2 runtime. For development and build instructions, see [AGENTS/DEVELOPMENT.md](AGENTS/DEVELOPMENT.md). + +At startup the Bundle checks whether a newer nightly exists and tells you where to download it; it never updates itself. diff --git a/bundle/ThirdPartyNotices.md b/bundle/ThirdPartyNotices.md index df9741e..2a98b95 100644 --- a/bundle/ThirdPartyNotices.md +++ b/bundle/ThirdPartyNotices.md @@ -1,6 +1,6 @@ # Third-party notices -The editor uses Monaco Editor (MIT), vscode-textmate (MIT), vscode-oniguruma (MIT), and jsonc-parser (MIT). Tauri and its dialog plugin are available under MIT or Apache-2.0. Their upstream license texts are distributed with the installed dependencies. +The editor uses Monaco Editor (MIT), vscode-textmate (MIT), vscode-oniguruma (MIT), and jsonc-parser (MIT). Tauri and its dialog and opener plugins are available under MIT or Apache-2.0. The startup update check uses ureq (MIT or Apache-2.0), rustls (Apache-2.0, ISC or MIT), ring (Apache-2.0 and ISC) and webpki-roots (CDLA-Permissive-2.0). Their upstream license texts are distributed with the installed dependencies. `vendor/vscode/2026-dark.json`, `dark_modern.json`, `dark_plus.json` and `dark_vs.json` are copied without modifications from Microsoft Visual Studio Code, commit `f93fdeb454a0f0fafe5f310408cec48dd3b6f58a`, under the MIT license in `vendor/vscode/LICENSE.txt`. diff --git a/bundle/src-tauri/Cargo.lock b/bundle/src-tauri/Cargo.lock index c461524..9d9808e 100644 --- a/bundle/src-tauri/Cargo.lock +++ b/bundle/src-tauri/Cargo.lock @@ -48,6 +48,137 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] name = "atk" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -149,6 +280,19 @@ dependencies = [ ] [[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] name = "brotli" version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -339,6 +483,15 @@ dependencies = [ ] [[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "cookie" version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -394,10 +547,13 @@ version = "0.1.0" dependencies = [ "flate2", "serde", + "serde_json", "tauri", "tauri-build", "tauri-plugin-dialog", + "tauri-plugin-opener", "tokio", + "ureq", ] [[package]] @@ -727,6 +883,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" [[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] name = "equivalent" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -750,7 +933,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", ] [[package]] @@ -876,6 +1079,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] name = "futures-macro" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1025,6 +1241,17 @@ dependencies = [ [[package]] name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" @@ -1219,6 +1446,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1499,6 +1732,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1727,6 +1979,12 @@ dependencies = [ ] [[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] name = "litemap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2108,12 +2366,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] +name = "open" +version = "5.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" +dependencies = [ + "dunce", + "is-wsl", + "libc", +] + +[[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] name = "pango" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2139,6 +2418,12 @@ dependencies = [ ] [[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2227,6 +2512,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] name = "pkg-config" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2272,6 +2568,20 @@ dependencies = [ ] [[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] name = "portable-atomic" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2532,6 +2842,20 @@ dependencies = [ ] [[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] name = "rustc-hash" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2547,6 +2871,54 @@ dependencies = [ ] [[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] name = "rustversion" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2958,6 +3330,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] name = "swift-rs" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3264,6 +3642,28 @@ dependencies = [ ] [[package]] +name = "tauri-plugin-opener" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53810be1b4fed22bb6ac3f4cab5a2b80d9391d1af436bda145607532bc0bb219" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.21", + "url", + "windows", + "zbus", +] + +[[package]] name = "tauri-runtime" version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3364,6 +3764,19 @@ dependencies = [ ] [[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] name = "tendril" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3646,10 +4059,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3699,6 +4124,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] name = "unicode-ident" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3711,6 +4147,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde93ff05758a36a851a398a772b0b79a82fbe3c186a46f2696baaeaf84c966b" +dependencies = [ + "base64 0.23.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + +[[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3736,6 +4206,12 @@ dependencies = [ ] [[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3965,6 +4441,15 @@ dependencies = [ ] [[package]] +name = "webpki-roots" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] name = "webview2-com" version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4157,6 +4642,15 @@ dependencies = [ [[package]] name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" @@ -4190,6 +4684,22 @@ dependencies = [ [[package]] name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" @@ -4198,7 +4708,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", @@ -4231,6 +4741,12 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" @@ -4243,6 +4759,12 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" @@ -4255,12 +4777,24 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" @@ -4273,6 +4807,12 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" @@ -4285,6 +4825,12 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" @@ -4297,6 +4843,12 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" @@ -4309,6 +4861,12 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" @@ -4442,6 +5000,76 @@ dependencies = [ ] [[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.9", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] name = "zerofrom" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4463,6 +5091,12 @@ dependencies = [ ] [[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] name = "zerotrie" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4506,3 +5140,44 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zvariant" +version = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.9", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.9", + "winnow 1.0.4", +] diff --git a/bundle/src-tauri/Cargo.toml b/bundle/src-tauri/Cargo.toml index 573db28..08a737b 100644 --- a/bundle/src-tauri/Cargo.toml +++ b/bundle/src-tauri/Cargo.toml @@ -10,6 +10,9 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } tauri-plugin-dialog = "2" +tauri-plugin-opener = "2" +ureq = { version = "3", default-features = false, features = ["rustls"] } +serde_json = "1" serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["process", "io-util", "sync", "macros", "rt-multi-thread", "time"] } diff --git a/bundle/src-tauri/build.rs b/bundle/src-tauri/build.rs index bf7e5df..a99d668 100644 --- a/bundle/src-tauri/build.rs +++ b/bundle/src-tauri/build.rs @@ -1,5 +1,8 @@ fn main() { #[cfg(windows)] println!("cargo:rerun-if-changed=binaries/cpsc.exe.gz"); + for name in ["CPSC_BUNDLE_TIME", "CPSC_BUNDLE_COMMIT", "CPSC_BUNDLE_VERSION_URL"] { + println!("cargo:rerun-if-env-changed={name}"); + } tauri_build::build() } diff --git a/bundle/src-tauri/src/main.rs b/bundle/src-tauri/src/main.rs index 37b7431..c361260 100644 --- a/bundle/src-tauri/src/main.rs +++ b/bundle/src-tauri/src/main.rs @@ -3,6 +3,7 @@ mod compiler; mod menu; mod runner; +mod update; use compiler::Compiler; use runner::{OutputEvent, Runner}; use std::{ @@ -90,6 +91,12 @@ fn main() { let compiler = Compiler::load(); tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_opener::init()) + // Once per launch, not per window. + .setup(|app| { + update::check(app.handle().clone()); + Ok(()) + }) .menu(menu::build) // Menu items act on the focused window, like its keyboard shortcuts. .on_menu_event(|app, event| { diff --git a/bundle/src-tauri/src/update.rs b/bundle/src-tauri/src/update.rs new file mode 100644 index 0000000..1d2127d --- /dev/null +++ b/bundle/src-tauri/src/update.rs @@ -0,0 +1,124 @@ +// Startup check for a newer nightly build. It only reports; there is no auto-update. +// Release builds get CPSC_BUNDLE_TIME and CPSC_BUNDLE_COMMIT at build time; without +// them (dev, tests) nothing runs. Every failure is silent. +use serde::Deserialize; +use std::time::Duration; +use tauri::AppHandle; +use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind}; +use tauri_plugin_opener::OpenerExt; + +const LOCAL_TIME: Option<&str> = option_env!("CPSC_BUNDLE_TIME"); +const LOCAL_COMMIT: Option<&str> = option_env!("CPSC_BUNDLE_COMMIT"); +const VERSION_URL: &str = match option_env!("CPSC_BUNDLE_VERSION_URL") { + Some(url) => url, + None => "https://github.com/ovfor4/cie-pseudocode-compiler/releases/download/nightly-build/version.json", +}; +const RELEASE_PAGE: &str = "https://github.com/ovfor4/cie-pseudocode-compiler/releases/tag/nightly-build"; + +#[cfg(windows)] +const ASSET: &str = "cpsc-bundle-latest-Windows-x86_64.exe"; +#[cfg(target_os = "macos")] +const ASSET: &str = "cpsc-bundle-latest-Darwin-arm64.tar.xz"; +#[cfg(not(any(windows, target_os = "macos")))] +const ASSET: &str = ""; + +#[derive(Deserialize)] +struct Product { + time: String, + commit: String, +} + +#[derive(Deserialize)] +struct Versions { + bundle: Product, +} + +fn latest(json: &str) -> Option<Product> { + serde_json::from_str::<Versions>(json).ok().map(|v| v.bundle) +} + +// Times look like YYYYMMDD-HHMMSS, so a string comparison orders them. +fn valid(time: &str) -> bool { + let b = time.as_bytes(); + b.len() == 15 && b[8] == b'-' && b.iter().enumerate().all(|(i, c)| i == 8 || c.is_ascii_digit()) +} + +fn newer(remote_time: &str, local_time: &str) -> bool { + valid(remote_time) && valid(local_time) && remote_time > local_time +} + +fn fetch() -> Option<Product> { + let agent: ureq::Agent = ureq::Agent::config_builder() + .timeout_global(Some(Duration::from_secs(10))) + .build() + .into(); + let body = agent.get(VERSION_URL).call().ok()?.body_mut().read_to_string().ok()?; + latest(&body) +} + +pub fn check(app: AppHandle) { + let (Some(time), Some(commit)) = (LOCAL_TIME, LOCAL_COMMIT) else { + return; + }; + std::thread::spawn(move || { + let Some(remote) = fetch() else { return }; + if !newer(&remote.time, time) { + return; + } + let download = if ASSET.is_empty() { + String::new() + } else { + format!("Download {ASSET} from:\n") + }; + let message = format!( + "A newer CPSC Bundle is available.\n\nInstalled: {time} ({commit})\nLatest: {} ({})\n\n{download}{RELEASE_PAGE}", + remote.time, remote.commit + ); + let opener = app.clone(); + app.dialog() + .message(message) + .title("Update available") + .kind(MessageDialogKind::Info) + .buttons(MessageDialogButtons::OkCancelCustom( + "Open Release Page".into(), + "Later".into(), + )) + .show(move |open| { + if open { + let _ = opener.opener().open_url(RELEASE_PAGE, None::<&str>); + } + }); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compares_times() { + assert!(newer("20261022-044114", "20261021-140654")); + assert!(!newer("20261022-044114", "20261022-044114")); + assert!(!newer("20261021-140654", "20261022-044114")); + assert!(!newer("garbage", "20261022-044114")); + assert!(!newer("20261022-044114", "2026102-0441144")); + assert!(!newer("20261022x044114", "20200101-000000")); + } + + #[test] + fn parses_version_json() { + let json = r#"{ + "compiler": { "time": "20261022-044114", "commit": "620a154" }, + "plugin": { "time": "20261021-140654", "commit": "899c2af" }, + "bundle": { "time": "20261022-044114", "commit": "620a154" } + }"#; + let bundle = latest(json).unwrap(); + assert_eq!((bundle.time.as_str(), bundle.commit.as_str()), ("20261022-044114", "620a154")); + } + + #[test] + fn rejects_garbage_json() { + assert!(latest("not json").is_none()); + assert!(latest("{}").is_none()); + } +} |
