add auto-update via tauri-plugin-updater

Signed by a new key (public half embedded in tauri.conf.json, private
half + password live in GitHub secrets). CI now signs release artifacts
and publishes latest.json; the app checks on boot and prompts to
install and restart.
This commit is contained in:
Horizontal42 2026-07-01 13:58:32 +03:00
parent 6602e4bbf5
commit 6efae0c706
12 changed files with 381 additions and 10 deletions

View file

@ -41,6 +41,8 @@ jobs:
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with: with:
tagName: ${{ github.ref_name }} tagName: ${{ github.ref_name }}
releaseName: "Summarum ${{ github.ref_name }}" releaseName: "Summarum ${{ github.ref_name }}"

3
.gitignore vendored
View file

@ -10,3 +10,6 @@ numi-master/
# one-off debugging artifacts # one-off debugging artifacts
scripts/smoke-* scripts/smoke-*
# updater signing key — private, never publish (see .tauri-signing/README.txt)
.tauri-signing/

24
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "summarum", "name": "summarum",
"version": "1.0.0", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "summarum", "name": "summarum",
"version": "1.0.0", "version": "1.2.0",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.18.0", "@codemirror/autocomplete": "^6.18.0",
"@codemirror/commands": "^6.8.0", "@codemirror/commands": "^6.8.0",
@ -19,6 +19,8 @@
"@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-global-shortcut": "^2.2.0", "@tauri-apps/plugin-global-shortcut": "^2.2.0",
"@tauri-apps/plugin-opener": "^2.2.0", "@tauri-apps/plugin-opener": "^2.2.0",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"decimal.js": "^10.4.3" "decimal.js": "^10.4.3"
}, },
"devDependencies": { "devDependencies": {
@ -1234,6 +1236,24 @@
"@tauri-apps/api": "^2.11.0" "@tauri-apps/api": "^2.11.0"
} }
}, },
"node_modules/@tauri-apps/plugin-process": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz",
"integrity": "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.8.0"
}
},
"node_modules/@tauri-apps/plugin-updater": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.1.tgz",
"integrity": "sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.10.1"
}
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",

View file

@ -25,6 +25,8 @@
"@tauri-apps/plugin-dialog": "^2.7.1", "@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-global-shortcut": "^2.2.0", "@tauri-apps/plugin-global-shortcut": "^2.2.0",
"@tauri-apps/plugin-opener": "^2.2.0", "@tauri-apps/plugin-opener": "^2.2.0",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"decimal.js": "^10.4.3" "decimal.js": "^10.4.3"
}, },
"devDependencies": { "devDependencies": {

291
src-tauri/Cargo.lock generated
View file

@ -47,6 +47,15 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.7.2" version = "0.7.2"
@ -690,6 +699,17 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.1.1" version = "2.1.1"
@ -994,6 +1014,16 @@ dependencies = [
"rustc_version", "rustc_version",
] ]
[[package]]
name = "filetime"
version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@ -1877,6 +1907,36 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "jni"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
dependencies = [
"cfg-if",
"combine",
"jni-macros",
"jni-sys 0.4.1",
"log",
"simd_cesu8",
"thiserror 2.0.18",
"walkdir",
"windows-link 0.2.1",
]
[[package]]
name = "jni-macros"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"simd_cesu8",
"syn 2.0.117",
]
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.1" version = "0.3.1"
@ -2078,6 +2138,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minisign-verify"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.9" version = "0.8.9"
@ -2326,6 +2392,18 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-osa-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
dependencies = [
"bitflags 2.13.0",
"objc2",
"objc2-app-kit",
"objc2-foundation",
]
[[package]] [[package]]
name = "objc2-quartz-core" name = "objc2-quartz-core"
version = "0.3.2" version = "0.3.2"
@ -2401,6 +2479,12 @@ dependencies = [
"pathdiff", "pathdiff",
] ]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]] [[package]]
name = "option-ext" name = "option-ext"
version = "0.2.0" version = "0.2.0"
@ -2417,6 +2501,20 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "osakit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
dependencies = [
"objc2",
"objc2-foundation",
"objc2-osa-kit",
"serde",
"serde_json",
"thiserror 2.0.18",
]
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.18.3" version = "0.18.3"
@ -2775,7 +2873,7 @@ dependencies = [
"once_cell", "once_cell",
"socket2", "socket2",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -2966,15 +3064,20 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde", "serde",
"serde_json", "serde_json",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@ -3066,6 +3169,18 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-native-certs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.14.1" version = "1.14.1"
@ -3076,6 +3191,33 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni 0.22.4",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.13" version = "0.103.13"
@ -3108,6 +3250,15 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "0.8.22"
@ -3165,6 +3316,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.13.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.36.1" version = "0.36.1"
@ -3397,6 +3571,22 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "simd_cesu8"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
dependencies = [
"rustc_version",
"simdutf8",
]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.3" version = "1.0.3"
@ -3517,7 +3707,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "summarum" name = "summarum"
version = "1.1.3" version = "1.2.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"reqwest 0.12.28", "reqwest 0.12.28",
@ -3529,7 +3719,9 @@ dependencies = [
"tauri-plugin-dialog", "tauri-plugin-dialog",
"tauri-plugin-global-shortcut", "tauri-plugin-global-shortcut",
"tauri-plugin-opener", "tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-single-instance", "tauri-plugin-single-instance",
"tauri-plugin-updater",
] ]
[[package]] [[package]]
@ -3615,7 +3807,7 @@ dependencies = [
"gdkwayland-sys", "gdkwayland-sys",
"gdkx11-sys", "gdkx11-sys",
"gtk", "gtk",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"ndk", "ndk",
@ -3648,6 +3840,17 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "tar"
version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]] [[package]]
name = "target-lexicon" name = "target-lexicon"
version = "0.12.16" version = "0.12.16"
@ -3671,7 +3874,7 @@ dependencies = [
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
"http", "http",
"jni", "jni 0.21.1",
"libc", "libc",
"log", "log",
"mime", "mime",
@ -3876,6 +4079,16 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "tauri-plugin-process"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a"
dependencies = [
"tauri",
"tauri-plugin",
]
[[package]] [[package]]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.4.2" version = "2.4.2"
@ -3891,6 +4104,39 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
dependencies = [
"base64 0.22.1",
"dirs 6.0.0",
"flate2",
"futures-util",
"http",
"infer",
"log",
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest 0.13.4",
"rustls",
"semver",
"serde",
"serde_json",
"tar",
"tauri",
"tauri-plugin",
"tempfile",
"thiserror 2.0.18",
"time",
"tokio",
"url",
"windows-sys 0.60.2",
"zip",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.11.2" version = "2.11.2"
@ -3901,7 +4147,7 @@ dependencies = [
"dpi", "dpi",
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"objc2", "objc2",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit", "objc2-web-kit",
@ -3924,7 +4170,7 @@ checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
"jni", "jni 0.21.1",
"log", "log",
"objc2", "objc2",
"objc2-app-kit", "objc2-app-kit",
@ -4761,6 +5007,15 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "webpki-root-certs"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.7" version = "1.0.7"
@ -5401,7 +5656,7 @@ dependencies = [
"gtk", "gtk",
"http", "http",
"javascriptcore-rs", "javascriptcore-rs",
"jni", "jni 0.21.1",
"libc", "libc",
"ndk", "ndk",
"objc2", "objc2",
@ -5465,6 +5720,16 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
[[package]]
name = "xattr"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
dependencies = [
"libc",
"rustix",
]
[[package]] [[package]]
name = "xkeysym" name = "xkeysym"
version = "0.2.1" version = "0.2.1"
@ -5635,6 +5900,18 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "zip"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
dependencies = [
"arbitrary",
"crc32fast",
"indexmap 2.14.0",
"memchr",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"

View file

@ -14,6 +14,8 @@ tauri-plugin-autostart = "2"
tauri-plugin-single-instance = "2" tauri-plugin-single-instance = "2"
tauri-plugin-opener = "2" tauri-plugin-opener = "2"
tauri-plugin-dialog = "2" tauri-plugin-dialog = "2"
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }

View file

@ -20,6 +20,8 @@
"autostart:allow-disable", "autostart:allow-disable",
"autostart:allow-is-enabled", "autostart:allow-is-enabled",
"opener:default", "opener:default",
"dialog:default" "dialog:default",
"updater:default",
"process:allow-restart"
] ]
} }

View file

@ -510,6 +510,8 @@ fn main() {
)) ))
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
save_file, save_file,
load_file, load_file,

View file

@ -29,6 +29,7 @@
"bundle": { "bundle": {
"active": true, "active": true,
"targets": ["nsis"], "targets": ["nsis"],
"createUpdaterArtifacts": true,
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
@ -50,5 +51,13 @@
"languages": ["English", "Russian"] "languages": ["English", "Russian"]
} }
} }
},
"plugins": {
"updater": {
"endpoints": [
"https://github.com/Horizontal42/summarum/releases/latest/download/latest.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDlERDUwODMzRTk4Qzc2NDkKUldSSmRvenBNd2pWbmZmd1JxVEZ4ZllKYmp5bnJHVFlJWmVsRWxBQ3pNaWx2bFZicFE3Rm1NeWYK"
}
} }
} }

View file

@ -47,6 +47,11 @@ const dict: Record<string, Record<string, string>> = {
exportTxt: "Export as text", exportTxt: "Export as text",
exportPrint: "Print / PDF", exportPrint: "Print / PDF",
saved: "Saved", saved: "Saved",
updateAvailable: "Version {} is available. Install and restart now?",
updateInstall: "Install",
updateLater: "Later",
updateInstalling: "Installing update…",
updateFailed: "Update failed",
}, },
ru: { ru: {
settings: "Настройки", settings: "Настройки",
@ -94,6 +99,11 @@ const dict: Record<string, Record<string, string>> = {
exportTxt: "Экспорт в текст", exportTxt: "Экспорт в текст",
exportPrint: "Печать / PDF", exportPrint: "Печать / PDF",
saved: "Сохранено", saved: "Сохранено",
updateAvailable: "Доступна версия {}. Установить и перезапустить сейчас?",
updateInstall: "Установить",
updateLater: "Позже",
updateInstalling: "Установка обновления…",
updateFailed: "Не удалось обновить",
}, },
}; };

View file

@ -13,6 +13,7 @@ import {
import type { LineResult } from "./engine"; import type { LineResult } from "./engine";
import { setLang, detectLang, t } from "./i18n"; import { setLang, detectLang, t } from "./i18n";
import { runExtensions } from "./extensions"; import { runExtensions } from "./extensions";
import { checkForUpdate } from "./updater";
import { EN, RU } from "./engine/vocab-data"; import { EN, RU } from "./engine/vocab-data";
function welcomeText(lang: string): string { function welcomeText(lang: string): string {
@ -654,6 +655,21 @@ async function boot(): Promise<void> {
await getCurrentWindow().show(); await getCurrentWindow().show();
} }
} }
void checkUpdate();
}
async function checkUpdate(): Promise<void> {
const update = await checkForUpdate();
if (!update) return;
const choice = await askModal(t("updateAvailable").replace("{}", update.version), t("updateInstall"), t("updateLater"));
if (choice !== "a") return;
toast(t("updateInstalling"));
try {
await update.install();
} catch {
toast(t("updateFailed"));
}
} }
void boot(); void boot();

26
src/updater.ts Normal file
View file

@ -0,0 +1,26 @@
import { isTauri } from "./storage";
export interface AvailableUpdate {
version: string;
install(): Promise<void>;
}
/** null if no update, offline, or running outside Tauri (`vite dev`). */
export async function checkForUpdate(): Promise<AvailableUpdate | null> {
if (!isTauri()) return null;
try {
const { check } = await import("@tauri-apps/plugin-updater");
const update = await check();
if (!update) return null;
return {
version: update.version,
async install() {
await update.downloadAndInstall();
const { relaunch } = await import("@tauri-apps/plugin-process");
await relaunch();
},
};
} catch {
return null;
}
}