ruby_everywhere 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/everywhere/blake2b.rb +116 -0
- data/lib/everywhere/cli.rb +4 -0
- data/lib/everywhere/commands/publish.rb +267 -0
- data/lib/everywhere/commands/updates_keygen.rb +69 -0
- data/lib/everywhere/config.rb +38 -1
- data/lib/everywhere/javascript/bridge.js +119 -0
- data/lib/everywhere/minisign.rb +205 -0
- data/lib/everywhere/s3.rb +232 -0
- data/lib/everywhere/update_manifest.rb +115 -0
- data/lib/everywhere/version.rb +2 -2
- data/support/shell/src-tauri/Cargo.lock +123 -1
- data/support/shell/src-tauri/Cargo.toml +7 -1
- data/support/shell/src-tauri/src/main.rs +26 -0
- data/support/shell/src-tauri/src/updater.rs +745 -0
- data/support/shell/src-tauri/tauri.conf.json +1 -1
- metadata +23 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Everywhere
|
|
7
|
+
# The update-feed contract: bucket key layout + latest.json / index.json
|
|
8
|
+
# bodies. This is THE interface between `every publish`, the shell's Rust
|
|
9
|
+
# updater, and the Platform's release publishing — all three speak exactly
|
|
10
|
+
# these shapes, so keep changes here schema-versioned and additive.
|
|
11
|
+
#
|
|
12
|
+
# Layout (channel is a *release channel* — stable/beta — never the receipt's
|
|
13
|
+
# distribution_channel):
|
|
14
|
+
#
|
|
15
|
+
# {prefix?}/{channel}/{os}/{arch}/
|
|
16
|
+
# latest.json mutable pointer, written last, Cache-Control: no-cache
|
|
17
|
+
# index.json append-only publish history (rollback + stats source)
|
|
18
|
+
# My-App-1.2.0.zip immutable + .minisig sibling
|
|
19
|
+
# My-App-latest.zip convenience alias (CopyObject) for download links
|
|
20
|
+
#
|
|
21
|
+
# The shell only ever does:
|
|
22
|
+
# GET {updates.url}/{channel}/{os}/{arch}/latest.json?current={version}
|
|
23
|
+
# — a raw bucket/CDN or a dynamic Platform endpoint serve the identical JSON.
|
|
24
|
+
module UpdateManifest
|
|
25
|
+
SCHEMA = 1
|
|
26
|
+
|
|
27
|
+
IMMUTABLE_CACHE = "public, max-age=31536000, immutable"
|
|
28
|
+
MUTABLE_CACHE = "no-cache"
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def dir(channel:, os:, arch:, prefix: nil)
|
|
33
|
+
[prefix, channel, os, arch].compact.reject(&:empty?).join("/")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def artifact_key(filename, **loc) = "#{dir(**loc)}/#{filename}"
|
|
37
|
+
def latest_key(**loc) = "#{dir(**loc)}/latest.json"
|
|
38
|
+
def index_key(**loc) = "#{dir(**loc)}/index.json"
|
|
39
|
+
|
|
40
|
+
# Where images embedded in release notes live: channel-scoped (shared by
|
|
41
|
+
# every os/arch target of the release), versioned so they're immutable and
|
|
42
|
+
# rollback-safe. notes_html rewrites embedded images to these URLs at
|
|
43
|
+
# publish time — signed/expiring storage URLs must never leak into a feed.
|
|
44
|
+
def notes_asset_key(filename, channel:, version:, prefix: nil)
|
|
45
|
+
"#{[ prefix, channel ].compact.reject(&:empty?).join("/")}/notes/#{version}/#{filename}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# "My-App-1.2.0.zip" -> "My-App-latest.zip" (keeps the extension).
|
|
49
|
+
def latest_alias(filename, version)
|
|
50
|
+
aliased = filename.sub("-#{version}", "-latest")
|
|
51
|
+
aliased == filename ? nil : aliased
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# One latest.json body (also an index.json entry), built from a build
|
|
55
|
+
# receipt (dist/release.json) + publish-time facts.
|
|
56
|
+
#
|
|
57
|
+
# Release notes ship in two forms: `notes` is the human-authored source
|
|
58
|
+
# (markdown; plain text is valid markdown), `notes_html` is pre-rendered
|
|
59
|
+
# sanitized-by-construction HTML the app can inject straight into a
|
|
60
|
+
# changelog modal via the bridge. Pass notes_html explicitly to override
|
|
61
|
+
# the default markdown rendering (the Platform does — its notes are
|
|
62
|
+
# authored rich).
|
|
63
|
+
def entry(receipt:, url:, signature:, channel:, notes: nil, notes_html: nil, published_at: Time.now.utc)
|
|
64
|
+
artifact = receipt.fetch("artifact")
|
|
65
|
+
sha256 = artifact.fetch("checksum").to_s.delete_prefix("sha256:")
|
|
66
|
+
notes_html ||= render_notes_html(notes)
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
"schema" => SCHEMA,
|
|
70
|
+
"app" => {
|
|
71
|
+
"name" => receipt.dig("app", "name"),
|
|
72
|
+
"bundle_id" => receipt.dig("app", "bundle_id")
|
|
73
|
+
},
|
|
74
|
+
"channel" => channel,
|
|
75
|
+
"os" => receipt.dig("target", "os"),
|
|
76
|
+
"arch" => receipt.dig("target", "arch"),
|
|
77
|
+
"version" => receipt.dig("app", "version"),
|
|
78
|
+
"published_at" => published_at.iso8601,
|
|
79
|
+
"notes" => notes,
|
|
80
|
+
"notes_html" => notes_html,
|
|
81
|
+
"url" => url,
|
|
82
|
+
"sha256" => sha256,
|
|
83
|
+
"size_bytes" => artifact.fetch("size_bytes"),
|
|
84
|
+
"signature" => signature,
|
|
85
|
+
"manifest_checksum" => receipt["manifest_checksum"]
|
|
86
|
+
}.compact
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# index.json: append-only history, newest first. Re-publishing the same
|
|
90
|
+
# version replaces its entry (idempotent re-runs don't duplicate).
|
|
91
|
+
def index_append(existing_json, entry)
|
|
92
|
+
entries = existing_json ? JSON.parse(existing_json).fetch("entries", []) : []
|
|
93
|
+
entries = entries.reject { |e| e["version"] == entry["version"] }
|
|
94
|
+
JSON.pretty_generate({ "schema" => SCHEMA, "entries" => [entry] + entries }) << "\n"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def index_find(index_json, version)
|
|
98
|
+
return nil unless index_json
|
|
99
|
+
|
|
100
|
+
JSON.parse(index_json).fetch("entries", []).find { |e| e["version"] == version }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_json(entry) = JSON.pretty_generate(entry) << "\n"
|
|
104
|
+
|
|
105
|
+
# Markdown -> HTML for notes_html. Kramdown with GFM-ish niceties off the
|
|
106
|
+
# shelf; raw HTML in the source passes through (the feed is the app
|
|
107
|
+
# developer's own signed content — same trust as the artifact itself).
|
|
108
|
+
def render_notes_html(notes)
|
|
109
|
+
return nil if notes.nil? || notes.strip.empty?
|
|
110
|
+
|
|
111
|
+
require "kramdown"
|
|
112
|
+
Kramdown::Document.new(notes, auto_ids: false, entity_output: :as_char).to_html.strip
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/everywhere/version.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Everywhere
|
|
4
|
-
VERSION = "0.1.
|
|
4
|
+
VERSION = "0.1.9"
|
|
5
5
|
|
|
6
6
|
# Version of the vendored @rubyeverywhere/bridge JS this gem ships. Tracks
|
|
7
7
|
# bridge/package.json — bump it whenever lib/everywhere/javascript/bridge.js is
|
|
8
8
|
# refreshed. `every install` stamps it into the vendored file so the build
|
|
9
9
|
# receipt can record which bridge shipped; it versions independently of the CLI.
|
|
10
|
-
BRIDGE_VERSION = "0.
|
|
10
|
+
BRIDGE_VERSION = "0.3.0"
|
|
11
11
|
end
|
|
@@ -991,17 +991,21 @@ dependencies = [
|
|
|
991
991
|
|
|
992
992
|
[[package]]
|
|
993
993
|
name = "example-shell"
|
|
994
|
-
version = "0.
|
|
994
|
+
version = "0.2.0"
|
|
995
995
|
dependencies = [
|
|
996
|
+
"minisign-verify",
|
|
996
997
|
"objc2",
|
|
997
998
|
"objc2-foundation",
|
|
998
999
|
"objc2-web-kit",
|
|
1000
|
+
"semver",
|
|
999
1001
|
"serde_json",
|
|
1002
|
+
"sha2",
|
|
1000
1003
|
"tauri",
|
|
1001
1004
|
"tauri-build",
|
|
1002
1005
|
"tauri-plugin-clipboard-manager",
|
|
1003
1006
|
"tauri-plugin-dialog",
|
|
1004
1007
|
"tauri-plugin-notification",
|
|
1008
|
+
"ureq",
|
|
1005
1009
|
]
|
|
1006
1010
|
|
|
1007
1011
|
[[package]]
|
|
@@ -2087,6 +2091,12 @@ version = "0.3.17"
|
|
|
2087
2091
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2088
2092
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
|
2089
2093
|
|
|
2094
|
+
[[package]]
|
|
2095
|
+
name = "minisign-verify"
|
|
2096
|
+
version = "0.2.5"
|
|
2097
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2098
|
+
checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
|
|
2099
|
+
|
|
2090
2100
|
[[package]]
|
|
2091
2101
|
name = "miniz_oxide"
|
|
2092
2102
|
version = "0.8.9"
|
|
@@ -2986,6 +2996,20 @@ dependencies = [
|
|
|
2986
2996
|
"windows-sys 0.60.2",
|
|
2987
2997
|
]
|
|
2988
2998
|
|
|
2999
|
+
[[package]]
|
|
3000
|
+
name = "ring"
|
|
3001
|
+
version = "0.17.14"
|
|
3002
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3003
|
+
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
|
3004
|
+
dependencies = [
|
|
3005
|
+
"cc",
|
|
3006
|
+
"cfg-if",
|
|
3007
|
+
"getrandom 0.2.17",
|
|
3008
|
+
"libc",
|
|
3009
|
+
"untrusted",
|
|
3010
|
+
"windows-sys 0.52.0",
|
|
3011
|
+
]
|
|
3012
|
+
|
|
2989
3013
|
[[package]]
|
|
2990
3014
|
name = "rustc-hash"
|
|
2991
3015
|
version = "2.1.3"
|
|
@@ -3014,6 +3038,41 @@ dependencies = [
|
|
|
3014
3038
|
"windows-sys 0.61.2",
|
|
3015
3039
|
]
|
|
3016
3040
|
|
|
3041
|
+
[[package]]
|
|
3042
|
+
name = "rustls"
|
|
3043
|
+
version = "0.23.42"
|
|
3044
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3045
|
+
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
|
|
3046
|
+
dependencies = [
|
|
3047
|
+
"log",
|
|
3048
|
+
"once_cell",
|
|
3049
|
+
"ring",
|
|
3050
|
+
"rustls-pki-types",
|
|
3051
|
+
"rustls-webpki",
|
|
3052
|
+
"subtle",
|
|
3053
|
+
"zeroize",
|
|
3054
|
+
]
|
|
3055
|
+
|
|
3056
|
+
[[package]]
|
|
3057
|
+
name = "rustls-pki-types"
|
|
3058
|
+
version = "1.15.0"
|
|
3059
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3060
|
+
checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
|
|
3061
|
+
dependencies = [
|
|
3062
|
+
"zeroize",
|
|
3063
|
+
]
|
|
3064
|
+
|
|
3065
|
+
[[package]]
|
|
3066
|
+
name = "rustls-webpki"
|
|
3067
|
+
version = "0.103.13"
|
|
3068
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3069
|
+
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
|
3070
|
+
dependencies = [
|
|
3071
|
+
"ring",
|
|
3072
|
+
"rustls-pki-types",
|
|
3073
|
+
"untrusted",
|
|
3074
|
+
]
|
|
3075
|
+
|
|
3017
3076
|
[[package]]
|
|
3018
3077
|
name = "rustversion"
|
|
3019
3078
|
version = "1.0.23"
|
|
@@ -3418,6 +3477,12 @@ version = "0.11.1"
|
|
|
3418
3477
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3419
3478
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
3420
3479
|
|
|
3480
|
+
[[package]]
|
|
3481
|
+
name = "subtle"
|
|
3482
|
+
version = "2.6.1"
|
|
3483
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3484
|
+
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
3485
|
+
|
|
3421
3486
|
[[package]]
|
|
3422
3487
|
name = "swift-rs"
|
|
3423
3488
|
version = "1.0.7"
|
|
@@ -4326,6 +4391,30 @@ version = "1.13.3"
|
|
|
4326
4391
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4327
4392
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
|
4328
4393
|
|
|
4394
|
+
[[package]]
|
|
4395
|
+
name = "untrusted"
|
|
4396
|
+
version = "0.9.0"
|
|
4397
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4398
|
+
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|
4399
|
+
|
|
4400
|
+
[[package]]
|
|
4401
|
+
name = "ureq"
|
|
4402
|
+
version = "2.12.1"
|
|
4403
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4404
|
+
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
|
4405
|
+
dependencies = [
|
|
4406
|
+
"base64 0.22.1",
|
|
4407
|
+
"flate2",
|
|
4408
|
+
"log",
|
|
4409
|
+
"once_cell",
|
|
4410
|
+
"rustls",
|
|
4411
|
+
"rustls-pki-types",
|
|
4412
|
+
"serde",
|
|
4413
|
+
"serde_json",
|
|
4414
|
+
"url",
|
|
4415
|
+
"webpki-roots 0.26.11",
|
|
4416
|
+
]
|
|
4417
|
+
|
|
4329
4418
|
[[package]]
|
|
4330
4419
|
name = "url"
|
|
4331
4420
|
version = "2.5.8"
|
|
@@ -4639,6 +4728,24 @@ dependencies = [
|
|
|
4639
4728
|
"system-deps",
|
|
4640
4729
|
]
|
|
4641
4730
|
|
|
4731
|
+
[[package]]
|
|
4732
|
+
name = "webpki-roots"
|
|
4733
|
+
version = "0.26.11"
|
|
4734
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4735
|
+
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
|
4736
|
+
dependencies = [
|
|
4737
|
+
"webpki-roots 1.0.9",
|
|
4738
|
+
]
|
|
4739
|
+
|
|
4740
|
+
[[package]]
|
|
4741
|
+
name = "webpki-roots"
|
|
4742
|
+
version = "1.0.9"
|
|
4743
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4744
|
+
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
|
4745
|
+
dependencies = [
|
|
4746
|
+
"rustls-pki-types",
|
|
4747
|
+
]
|
|
4748
|
+
|
|
4642
4749
|
[[package]]
|
|
4643
4750
|
name = "webview2-com"
|
|
4644
4751
|
version = "0.38.2"
|
|
@@ -4875,6 +4982,15 @@ dependencies = [
|
|
|
4875
4982
|
"windows-targets 0.42.2",
|
|
4876
4983
|
]
|
|
4877
4984
|
|
|
4985
|
+
[[package]]
|
|
4986
|
+
name = "windows-sys"
|
|
4987
|
+
version = "0.52.0"
|
|
4988
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
4989
|
+
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
|
4990
|
+
dependencies = [
|
|
4991
|
+
"windows-targets 0.52.6",
|
|
4992
|
+
]
|
|
4993
|
+
|
|
4878
4994
|
[[package]]
|
|
4879
4995
|
name = "windows-sys"
|
|
4880
4996
|
version = "0.59.0"
|
|
@@ -5377,6 +5493,12 @@ dependencies = [
|
|
|
5377
5493
|
"synstructure",
|
|
5378
5494
|
]
|
|
5379
5495
|
|
|
5496
|
+
[[package]]
|
|
5497
|
+
name = "zeroize"
|
|
5498
|
+
version = "1.9.0"
|
|
5499
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
5500
|
+
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
|
5501
|
+
|
|
5380
5502
|
[[package]]
|
|
5381
5503
|
name = "zerotrie"
|
|
5382
5504
|
version = "0.2.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "example-shell"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Generic desktop shell: boots a packaged Rails app as a sidecar and shows it in a webview"
|
|
5
5
|
edition = "2021"
|
|
6
6
|
|
|
@@ -13,6 +13,12 @@ serde_json = "1"
|
|
|
13
13
|
tauri-plugin-notification = "2"
|
|
14
14
|
tauri-plugin-dialog = "2"
|
|
15
15
|
tauri-plugin-clipboard-manager = "2"
|
|
16
|
+
# Self-updater (src/updater.rs): blocking HTTP fits the shell's plain-thread
|
|
17
|
+
# style; minisign-verify is the zero-dep verifier for our publish signatures.
|
|
18
|
+
ureq = { version = "2", features = ["json"] }
|
|
19
|
+
semver = "1"
|
|
20
|
+
sha2 = "0.10"
|
|
21
|
+
minisign-verify = "0.2"
|
|
16
22
|
|
|
17
23
|
# macOS: set the WebView's `applicationNameForUserAgent`, which WKWebView
|
|
18
24
|
# natively APPENDS to its real default UA — so we get the true system UA plus
|
|
@@ -19,6 +19,8 @@ use std::time::{Duration, Instant};
|
|
|
19
19
|
|
|
20
20
|
use tauri::{Manager, RunEvent, WebviewUrl, WebviewWindowBuilder};
|
|
21
21
|
|
|
22
|
+
mod updater;
|
|
23
|
+
|
|
22
24
|
struct Sidecar(Mutex<Option<Child>>);
|
|
23
25
|
|
|
24
26
|
struct MenuEntry {
|
|
@@ -141,6 +143,16 @@ fn main() {
|
|
|
141
143
|
.plugin(tauri_plugin_clipboard_manager::init())
|
|
142
144
|
.invoke_handler(tauri::generate_handler![notify_command])
|
|
143
145
|
.setup(move |app| {
|
|
146
|
+
// A persisted user channel choice (Everywhere.updates.setChannel)
|
|
147
|
+
// overrides everywhere.yml's updates.channel. Fold it in before
|
|
148
|
+
// anything reads raw_json — the updater, the menu, and the
|
|
149
|
+
// __EVERYWHERE_CONFIG__ page injection all see the same channel.
|
|
150
|
+
let mut config = config;
|
|
151
|
+
if let Ok(mut value) = serde_json::from_str::<serde_json::Value>(&config.raw_json) {
|
|
152
|
+
updater::apply_channel_override(app.handle(), &mut value);
|
|
153
|
+
config.raw_json = value.to_string();
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
// Bridge channel: pages emit events (the sanctioned webview->shell
|
|
145
157
|
// path for remote-origin content in Tauri v2; invoke is reserved
|
|
146
158
|
// for local content). Fire-and-forget, Strada-style.
|
|
@@ -158,6 +170,7 @@ fn main() {
|
|
|
158
170
|
});
|
|
159
171
|
}
|
|
160
172
|
build_menu(app, &config)?;
|
|
173
|
+
updater::init(app, &config.raw_json);
|
|
161
174
|
|
|
162
175
|
// Dev mode (`every dev`): no sidecar, just point the webview at the
|
|
163
176
|
// developer's normal dev server.
|
|
@@ -295,6 +308,9 @@ fn main() {
|
|
|
295
308
|
.expect("error while building tauri application")
|
|
296
309
|
.run(|app, event| {
|
|
297
310
|
if let RunEvent::Exit = event {
|
|
311
|
+
// A fully staged silent update (updates.auto: install) swaps
|
|
312
|
+
// in now — two renames, no perceptible quit latency.
|
|
313
|
+
updater::install_pending_on_exit(app);
|
|
298
314
|
// No sidecar state in dev mode (NATIVE_DEV_URL).
|
|
299
315
|
if let Some(sidecar) = app.try_state::<Sidecar>() {
|
|
300
316
|
if let Some(mut child) = sidecar.0.lock().unwrap().take() {
|
|
@@ -332,6 +348,11 @@ fn build_menu(app: &tauri::App, config: &AppConfig) -> tauri::Result<()> {
|
|
|
332
348
|
.name(Some(config.name.clone()))
|
|
333
349
|
.build();
|
|
334
350
|
let mut app_menu = SubmenuBuilder::new(app, &config.name).about(Some(about));
|
|
351
|
+
if updater::enabled(&config.raw_json) {
|
|
352
|
+
app_menu = app_menu.separator().item(
|
|
353
|
+
&MenuItemBuilder::with_id("update-check", "Check for Updates…").build(app)?,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
335
356
|
if !config.menu.is_empty() {
|
|
336
357
|
app_menu = app_menu.separator();
|
|
337
358
|
for entry in &config.menu {
|
|
@@ -406,6 +427,11 @@ fn handle_menu_id(app: &tauri::AppHandle, id: &str) {
|
|
|
406
427
|
match id {
|
|
407
428
|
"quit" => app.exit(0),
|
|
408
429
|
"show" => focus_main(app),
|
|
430
|
+
// Blocking dialogs + network — never on the main thread.
|
|
431
|
+
"update-check" => {
|
|
432
|
+
let handle = app.clone();
|
|
433
|
+
std::thread::spawn(move || updater::interactive_check(&handle));
|
|
434
|
+
}
|
|
409
435
|
"reload" => {
|
|
410
436
|
if let Some(w) = app.get_webview_window("main") {
|
|
411
437
|
let _ = w.eval("location.reload()");
|