ruby_everywhere 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6445e84081e08a1d5c545054c0a7dc6ef2e4b05338aff54ea68dc4c48d6b6c60
4
- data.tar.gz: 2a057925b0ee3266c294046b0be24cb1c0d3fa4b8a82e179b88425cd6afa7e91
3
+ metadata.gz: e8f07eb6ae976f35ea7ccda33b87bfefd7c2d0ffd39c1b7aa1f7001bedbd482b
4
+ data.tar.gz: b0b9afc49cdc99e731c5bc7a742f8a90c4a5c94d496749131fa37b403567f91c
5
5
  SHA512:
6
- metadata.gz: 1e8528cf2ac3b62191795709e23ee708d1c706672b8fc5d5f2beec6c3455d1a092b71498a086d67506e9c8175efcad1ad2f5c22df0b5d7dbe0f130a3aba8600e
7
- data.tar.gz: ff1a73eeaabf7d7b839b092b3482c4a06511a6793133b6133f9671ad17d883092877b608fa8db04c9709a419d38c73f905e44dca32d952c992121a18c9e05aa1
6
+ metadata.gz: f13c6059ba2dae4dbe87c21a376265ae7cef7f84a890492897694dde101e9b94bc4990df82b6efc64f278e1ed35d3d921e8bfd7d5c131763cc79589d096d4211
7
+ data.tar.gz: 741d54c87c703df48024662485d03edcc15546c20f3fbe6f6d52b2b3c6056658c8aff7f2e73eb558d7f31961aa1c65c686a656bfc5e9bf9c2d900a3c633057aa
@@ -5,6 +5,7 @@ require "fileutils"
5
5
  require "tmpdir"
6
6
  require "shellwords"
7
7
  require_relative "../shellout"
8
+ require_relative "../log_filter"
8
9
  require_relative "../png"
9
10
  require_relative "../icon"
10
11
  require_relative "../paths"
@@ -29,20 +30,23 @@ module Everywhere
29
30
  option :ruby, default: "4.0.6", desc: "Ruby version to package"
30
31
  option :entry, default: "native_boot.rb", desc: "Entry point script relative to root"
31
32
  option :app_name, default: nil, desc: "Bundle name (default: capitalized app dir)"
33
+ option :target, default: "macos-arm64", desc: "Build target (os-arch) — selects any platforms: overrides"
32
34
  option :shell_dir, default: nil, desc: "Path to the shell's src-tauri directory"
33
35
  option :skip_press, type: :boolean, default: false, desc: "Reuse the existing pressed binary, only rebuild the .app"
34
36
 
35
37
  def call(root: ".", output: nil, ruby: "4.0.6", entry: "native_boot.rb",
36
- app_name: nil, shell_dir: nil, skip_press: false, **)
38
+ app_name: nil, target: "macos-arm64", shell_dir: nil, skip_press: false, **)
37
39
  root_path = File.expand_path(root)
38
40
  config = Everywhere::Config.load(root_path)
39
- app_name ||= config.name
41
+ # The os of `target` selects any per-platform overrides (bundle id,
42
+ # version, name) — local builds always target the host, macOS today.
43
+ app_name ||= config.name(target: target)
40
44
 
41
45
  # Remote mode: thin shell around an already-deployed app. No Rails app
42
46
  # required locally, nothing to press — just shell + config in a bundle.
43
47
  if config.remote?
44
48
  UI.step("#{UI.bold("remote")} mode → #{UI.cyan(config.remote_url)} #{UI.dim("(no local server packaged)")}")
45
- app_dir = bundle_app(nil, app_name, shell_dir, config, dist_dir: File.expand_path("dist", root_path))
49
+ app_dir = bundle_app(nil, app_name, shell_dir, config, target: target, dist_dir: File.expand_path("dist", root_path))
46
50
  return
47
51
  end
48
52
 
@@ -64,7 +68,7 @@ module Everywhere
64
68
  press!(framework, entry, ruby, output_path)
65
69
  end
66
70
 
67
- bundle_app(output_path, app_name, shell_dir, config) if RUBY_PLATFORM.include?("darwin")
71
+ bundle_app(output_path, app_name, shell_dir, config, target: target) if RUBY_PLATFORM.include?("darwin")
68
72
 
69
73
  UI.success("built #{output} (#{(File.size(output_path) / 1024.0 / 1024.0).round}MB)")
70
74
  end
@@ -86,11 +90,11 @@ module Everywhere
86
90
  staging = File.join(Dir.mktmpdir("everywhere-press"), File.basename(output_path))
87
91
  log = "#{output_path}.build.log"
88
92
 
89
- UI.step("rbe-tebako press (Ruby #{ruby}) #{UI.dim("— grab a coffee on first run")}")
90
- UI.step(UI.dim("full build log: #{log}"))
93
+ UI.step("rbe-tebako press #{UI.dim("(Ruby #{ruby} — grab a coffee on first run)")}")
94
+ UI.detail("full build log: #{UI.short_path(log)}")
91
95
  Shellout.run_logged!(press_env, ["rbe-tebako", "press",
92
96
  "--root=#{framework.root}", "--entry-point=#{entry}",
93
- "--Ruby=#{ruby}", "--output=#{staging}"], log: log)
97
+ "--Ruby=#{ruby}", "--output=#{staging}"], log: log, filter: LogFilter.for(:tebako))
94
98
 
95
99
  # Tebako's final strip step only warns (and still exits 0) if it can't
96
100
  # write the output, so verify the artifact instead of trusting exit codes.
@@ -112,7 +116,7 @@ module Everywhere
112
116
  # Assemble dist/<App Name>.app: the release shell binary + the pressed
113
117
  # Rails binary (as "app-server") + Info.plist + icon. An .app bundle is
114
118
  # just a directory layout, so we build it by hand — no tauri-cli needed.
115
- def bundle_app(server_binary, app_name, shell_dir, config, dist_dir: nil)
119
+ def bundle_app(server_binary, app_name, shell_dir, config, target: "macos-arm64", dist_dir: nil)
116
120
  shell_dir ||= Everywhere::Paths.shell_dir!
117
121
  dist_dir ||= File.dirname(server_binary)
118
122
 
@@ -188,7 +192,7 @@ module Everywhere
188
192
  FileUtils.cp(shell_bin, File.join(macos, exe_name))
189
193
  FileUtils.cp(server_binary, File.join(macos, "app-server")) if server_binary
190
194
 
191
- File.write(File.join(resources, "everywhere.json"), config.to_shell_json)
195
+ File.write(File.join(resources, "everywhere.json"), config.to_shell_json(target: target))
192
196
  if (splash = config.splash)
193
197
  if File.exist?(splash)
194
198
  FileUtils.cp(splash, File.join(resources, "splash.html"))
@@ -199,7 +203,7 @@ module Everywhere
199
203
  end
200
204
  icns = make_icns(macos_icon || File.join(shell_dir, "icons", "icon.png"), resources)
201
205
  File.write(File.join(app_dir, "Contents", "Info.plist"),
202
- info_plist(app_name, exe_name, icns, config.bundle_id, config.version))
206
+ info_plist(app_name, exe_name, icns, config.bundle_id(target: target), config.version(target: target)))
203
207
 
204
208
  UI.step("signing #{File.basename(app_dir)}")
205
209
  Shellout.run!({}, "codesign", "-s", "-", "-f", File.join(macos, "app-server")) if server_binary
@@ -102,17 +102,26 @@ module Everywhere
102
102
  return change("create config/everywhere.yml", false) if File.exist?(config)
103
103
 
104
104
  app_name = File.basename(@root).split(/[-_]/).map(&:capitalize).join(" ")
105
+ slug = app_name.downcase.gsub(/[^a-z0-9]+/, "-")
105
106
  File.write(config, <<~YAML)
106
- # RubyEverywhere app configuration — https://rubyeverywhere.com
107
+ # RubyEverywhere app configuration.
108
+ # Full reference: https://rubyeverywhere.com/docs/shared/everywhere-yml
107
109
  app:
108
110
  name: #{app_name}
109
111
  # Reverse-DNS app identifier: names the app-data directory on every
110
112
  # platform (and the macOS bundle id). Set once, never change it.
111
- bundle_id: com.example.#{app_name.downcase.gsub(/[^a-z0-9]+/, "-")}
113
+ bundle_id: com.example.#{slug}
114
+ version: "0.1.0" # marketing version (CFBundleShortVersionString)
115
+ mode: local # "local" (compile & ship the app) or "remote" (native shell around a deployed site)
112
116
  entry_path: / # initial url on app launch
113
117
  # icon: icon.png # PNG used to generate app icons (defaults to icon.png at the app root if present)
114
118
  # splash: splash.html # custom boot splash (HTML); window.__EVERYWHERE_CONFIG__ carries name/colors
115
119
 
120
+ # Remote mode only — the deployed URL the native shell opens.
121
+ # Set mode: remote above, then uncomment:
122
+ # remote:
123
+ # url: https://example.com
124
+
116
125
  appearance:
117
126
  # Colors take a hex string, or split by system theme:
118
127
  # tint_color:
@@ -122,6 +131,42 @@ module Everywhere
122
131
  background_color:
123
132
  light: "#FFFFFF"
124
133
  dark: "#1C1B1A"
134
+
135
+ # Native menu-bar items — each one visits a route in your app (no native code).
136
+ # menu:
137
+ # - label: Settings…
138
+ # path: /settings
139
+ # accelerator: CmdOrCtrl+,
140
+ # - separator
141
+ # - label: Documentation
142
+ # path: /help
143
+
144
+ # System tray / menu-bar-extra items — same shape as menu, plus action: show | quit.
145
+ # tray:
146
+ # - label: Open #{app_name}
147
+ # action: show
148
+ # - label: New…
149
+ # path: /new
150
+ # - separator
151
+ # - label: Quit
152
+ # action: quit
153
+
154
+ # Durable build knobs (CLI flags still override per-run).
155
+ # build:
156
+ # ruby: "4.0.6"
157
+ # targets: # the platforms you ship — the CI matrix, expressed once
158
+ # - macos-arm64
159
+ # # - ios-arm64 # future — mobile is remote mode only
160
+ # # - android-arm64 # future — mobile is remote mode only
161
+ # permissions: [notifications, filesystem:read, clipboard]
162
+
163
+ # Per-platform overrides. Keys are platform names (macos / ios / android /
164
+ # windows / linux); anything here overrides the app: defaults above for that
165
+ # platform — handy when a store needs a distinct bundle id or version.
166
+ # platforms:
167
+ # ios:
168
+ # bundle_id: com.example.#{slug}.ios
169
+ # version: "0.1.1"
125
170
  YAML
126
171
  change("create config/everywhere.yml", true)
127
172
  end
@@ -50,6 +50,9 @@ module Everywhere
50
50
 
51
51
  build = submit(client, targets, channel, signed_id, manifest)
52
52
  UI.success("submitted #{UI.bold(build["id"])} for #{targets.join(", ")}")
53
+ if (build_url = build["url"] || "#{client.base_url}/builds/#{build["id"]}")
54
+ UI.substep("watch it live → #{UI.cyan(build_url)}")
55
+ end
53
56
 
54
57
  return unless wait
55
58
 
@@ -31,13 +31,18 @@ module Everywhere
31
31
  option :shell_dir, default: nil, desc: "Path to the generic Tauri shell (src-tauri)"
32
32
 
33
33
  def call(artifact:, token:, url: nil, shell_dir: nil, **)
34
+ # We run headless on a build runner (a pipe, not a TTY) and stream into
35
+ # the Platform web log, which renders ANSI. Force color on so the hosted
36
+ # log reads like a local run.
37
+ ENV["CLICOLOR_FORCE"] ||= "1" unless ENV["NO_COLOR"]
38
+
34
39
  base = Everywhere::Platform::Client.base_url(url)
35
40
  @client = Everywhere::Platform::Client.new(base, token: token)
36
41
  @artifact = artifact
37
42
 
38
43
  job = get!("/runner/artifacts/#{artifact}")
39
44
  patch!("/runner/artifacts/#{artifact}", status: "running")
40
- remote_log("runner: picked up #{job.dig("target", "os")}-#{job.dig("target", "arch")} for #{job.dig("app", "name")}")
45
+ remote_log("picked up #{UI.cyan("#{job.dig("target", "os")}-#{job.dig("target", "arch")}")} for #{UI.bold(job.dig("app", "name"))}")
41
46
 
42
47
  Dir.mktmpdir("every-runner") do |work|
43
48
  src = fetch_snapshot(work, job)
@@ -63,7 +68,7 @@ module Everywhere
63
68
  # ---- pipeline steps ------------------------------------------------------
64
69
 
65
70
  def fetch_snapshot(work, job)
66
- remote_log("runner: downloading source snapshot")
71
+ remote_log("downloading source snapshot")
67
72
  tarball = File.join(work, "snapshot.tar.gz")
68
73
  @client.download(job["snapshot_url"], tarball)
69
74
  src = File.join(work, "src")
@@ -73,7 +78,7 @@ module Everywhere
73
78
  end
74
79
 
75
80
  def pull_credentials(work)
76
- remote_log("runner: pulling signing credentials")
81
+ remote_log("pulling signing credentials")
77
82
  creds = get!("/runner/artifacts/#{@artifact}/credentials")["credentials"] || []
78
83
  env = {}
79
84
  creds.each do |cred|
@@ -113,19 +118,23 @@ module Everywhere
113
118
  listing = `security find-identity -p codesigning #{@keychain.shellescape} 2>/dev/null`
114
119
  sha = listing[/\b([0-9A-F]{40})\b/, 1]
115
120
  UI.die!("no codesigning identity in the delivered .p12") unless sha
116
- remote_log("runner: signing identity #{listing[/"(.+)"/, 1]} (#{sha[0, 10]}…)")
121
+ remote_log("signing identity #{listing[/"(.+)"/, 1]} #{UI.dim("(#{sha[0, 10]}…)")}")
117
122
  sha
118
123
  end
119
124
 
120
125
  def run_release(src, job, signing_env, shell_dir)
121
- remote_log("runner: every release (press → sign → notarize → staple)")
126
+ remote_log("every release press → sign → notarize → staple")
122
127
  cmd = [RbConfig.ruby, "-I", lib_dir, every_exe, "release",
123
128
  "--root", src,
124
129
  "--target", "#{job.dig("target", "os")}-#{job.dig("target", "arch")}",
125
130
  "--channel", job.dig("target", "distribution_channel").to_s]
126
131
  cmd += ["--shell-dir", shell_dir] if shell_dir
127
132
 
128
- success = stream(signing_env, cmd)
133
+ # The web UI renders ANSI, but `every release` runs here as a pipe (not a
134
+ # TTY) so it would otherwise strip all color. Force it on — for our own
135
+ # output and for cargo — so the hosted log looks like the local one.
136
+ forced = { "CLICOLOR_FORCE" => "1", "CARGO_TERM_COLOR" => "always" }
137
+ success = stream(forced.merge(signing_env), cmd)
129
138
 
130
139
  dist = File.join(src, "dist")
131
140
  release_json = JSON.parse(File.read(File.join(dist, "release.json"))) rescue nil
@@ -134,7 +143,7 @@ module Everywhere
134
143
  end
135
144
 
136
145
  def complete_success(zip, release_json)
137
- remote_log("runner: uploading artifact (#{(File.size(zip) / 1024.0 / 1024.0).round(1)}MB)")
146
+ remote_log("uploading artifact #{UI.dim("(#{(File.size(zip) / 1024.0 / 1024.0).round(1)}MB)")}")
138
147
  signed_id = upload_artifact(zip)
139
148
  post!("/runner/artifacts/#{@artifact}/complete",
140
149
  status: "succeeded", file_signed_id: signed_id, release_json: release_json)
@@ -185,9 +194,13 @@ module Everywhere
185
194
  nil # never let a log hiccup kill the build
186
195
  end
187
196
 
197
+ # A runner-orchestration line: distinct from the `every release` steps it
198
+ # brackets (tagged "runner"), rendered once and forwarded verbatim — color
199
+ # and all — to the web log.
188
200
  def remote_log(message)
189
- UI.step(message)
190
- flush_log("#{message}\n")
201
+ line = "#{UI.gray("runner")} #{UI.cyan("→")} #{message}"
202
+ puts line
203
+ flush_log("#{line}\n")
191
204
  end
192
205
 
193
206
  # ---- helpers -------------------------------------------------------------
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "dry/cli"
4
4
  require_relative "../shellout"
5
+ require_relative "../log_filter"
5
6
  require_relative "../receipt"
6
7
  require_relative "../paths"
7
8
  require_relative "build"
@@ -37,7 +38,7 @@ module Everywhere
37
38
  app_name: nil, shell_dir: nil, env_file: nil, skip_build: false, **)
38
39
  root_path = File.expand_path(root)
39
40
  config = Everywhere::Config.load(root_path)
40
- app_name ||= config.name
41
+ app_name ||= config.name(target: target)
41
42
  ruby ||= config.build_ruby || "4.0.6"
42
43
 
43
44
  preflight!(target)
@@ -48,8 +49,8 @@ module Everywhere
48
49
  # 1. Produce (or reuse) the signed-for-dev .app via the build path. The
49
50
  # notarize step below re-signs it inside-out with the Developer ID.
50
51
  unless skip_build
51
- UI.step("building #{UI.bold(app_name)} for #{UI.cyan(target)}")
52
- Build.new.call(root: root, ruby: ruby, entry: entry, app_name: app_name, shell_dir: shell_dir, **)
52
+ UI.phase("Building #{app_name} for #{target}")
53
+ Build.new.call(root: root, ruby: ruby, entry: entry, app_name: app_name, target: target, shell_dir: shell_dir, **)
53
54
  end
54
55
 
55
56
  app_dir = Dir[File.join(dist_dir, "*.app")].max_by { |d| File.mtime(d) }
@@ -58,10 +59,11 @@ module Everywhere
58
59
  # 2. Sign (Developer ID + hardened runtime) → notarize → staple → verify.
59
60
  env_file ||= discover_env_file(root_path)
60
61
  env = env_file ? { "ENV_FILE" => env_file } : {}
61
- UI.step("signing + notarizing #{File.basename(app_dir)}#{env_file ? " #{UI.dim("(#{rel(env_file, root_path)})")}" : ""}")
62
+ UI.phase("Signing & notarizing #{File.basename(app_dir)}")
63
+ UI.step("preparing #{File.basename(app_dir)}#{env_file ? " #{UI.dim("(env: #{rel(env_file, root_path)})")}" : ""}")
62
64
  UI.warn("no signing env found — set APPLE_* or pass --env-file (notarize.sh will fail without it)") unless env_file || ENV["APPLE_SIGNING_IDENTITY"]
63
65
  log = "#{app_dir}.notarize.log"
64
- Shellout.run_logged!(env, ["/bin/bash", NOTARIZE, app_dir], log: log)
66
+ Shellout.run_logged!(env, ["/bin/bash", NOTARIZE, app_dir], log: log, filter: LogFilter.for(:notarize))
65
67
 
66
68
  # 3. Gather signing facts from the bundle itself, then emit the receipt.
67
69
  artifact = "#{app_dir.sub(/\.app\z/, "")}.zip" # notarize.sh re-zips the stapled app
@@ -125,18 +127,16 @@ module Everywhere
125
127
  end
126
128
 
127
129
  def summarize(out, artifact, signing, root_path)
128
- UI.success("release ready")
129
- UI.step("artifact: #{rel(artifact, root_path)} (#{(File.size(artifact) / 1024.0 / 1024.0).round}MB)")
130
- UI.step("signed by #{signing["signed_by"] || "?"} · " \
131
- "notarized #{yn(signing.dig("platform", "notarized"))} · " \
132
- "stapled #{yn(signing.dig("platform", "stapled"))} · " \
133
- "gatekeeper #{yn(signing["verified"])}")
134
- UI.step("receipt: #{rel(out, root_path)}")
130
+ UI.phase("Release ready 🎉")
131
+ UI.success("#{rel(artifact, root_path)} #{UI.dim("(#{(File.size(artifact) / 1024.0 / 1024.0).round}MB — send this to your friends)")}")
132
+ UI.substep("signed by #{UI.bold(signing["signed_by"] || "?")}")
133
+ UI.substep("notarized #{UI.yn(signing.dig("platform", "notarized"))} · " \
134
+ "stapled #{UI.yn(signing.dig("platform", "stapled"))} · " \
135
+ "gatekeeper #{UI.yn(signing["verified"])}")
136
+ UI.substep("receipt #{UI.dim(rel(out, root_path))}")
135
137
  end
136
138
 
137
- def yn(bool) = bool ? UI.green("yes") : UI.red("no")
138
-
139
- def rel(path, root_path) = path.sub("#{root_path}/", "").sub("#{Dir.pwd}/", "")
139
+ def rel(path, root_path) = UI.short_path(path.sub("#{root_path}/", "").sub("#{Dir.pwd}/", ""))
140
140
  end
141
141
  end
142
142
  end
@@ -31,15 +31,23 @@ module Everywhere
31
31
  @root = root
32
32
  end
33
33
 
34
- def name
35
- app["name"] || File.basename(File.expand_path(root)).split(/[-_]/).map(&:capitalize).join(" ")
34
+ # Every target (os-arch string) resolves to a single platform (its os), and
35
+ # a platform may override the app-level identity/version keys — see
36
+ # `platforms:` below. Pass `target:` to any of these accessors to apply the
37
+ # matching override; omit it for the shared app-level value.
38
+ def self.os_of(target) = target.to_s.split("-", 2).first
39
+
40
+ def name(target: nil)
41
+ resolved(target)["name"] || File.basename(File.expand_path(root)).split(/[-_]/).map(&:capitalize).join(" ")
36
42
  end
37
43
 
38
44
  # Reverse-DNS identifier: macOS bundle id, and the name of the per-user
39
45
  # app-data directory on every platform. Set it explicitly and never change
40
- # it — renaming moves users' data.
41
- def bundle_id
42
- app["bundle_id"] || "com.rubyeverywhere.#{name.downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")}"
46
+ # it — renaming moves users' data. A platform may override it (e.g. the App
47
+ # Store often wants `com.example.app.ios`).
48
+ def bundle_id(target: nil)
49
+ resolved(target)["bundle_id"] ||
50
+ "com.rubyeverywhere.#{name(target: target).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")}"
43
51
  end
44
52
 
45
53
  # App icon source: a PNG (ideally square, 1024px+). Explicit `app.icon`
@@ -67,9 +75,11 @@ module Everywhere
67
75
  end
68
76
 
69
77
  # Marketing/display version of the app (CFBundleShortVersionString, and the
70
- # `version` recorded in the build receipt). Defaults conservatively.
71
- def version
72
- app["version"] || "0.1.0"
78
+ # `version` recorded in the build receipt). One shared `app.version` is the
79
+ # default for every target; a platform may override it when a store forces a
80
+ # different number. Defaults conservatively.
81
+ def version(target: nil)
82
+ resolved(target)["version"] || "0.1.0"
73
83
  end
74
84
 
75
85
  # The `build:` section — durable build knobs that were once `every build`
@@ -149,11 +159,13 @@ module Everywhere
149
159
  end
150
160
 
151
161
  # The subset the shell needs, shipped as JSON (env var in dev,
152
- # Resources/everywhere.json inside a bundled .app).
153
- def to_shell_hash
162
+ # Resources/everywhere.json inside a bundled .app). Pass `target:` so the
163
+ # packaged config reflects the platform being built (its bundle id / name).
164
+ def to_shell_hash(target: nil)
154
165
  {
155
- "name" => name,
156
- "bundle_id" => bundle_id,
166
+ "name" => name(target: target),
167
+ "bundle_id" => bundle_id(target: target),
168
+ "version" => version(target: target),
157
169
  "mode" => mode,
158
170
  "remote_url" => remote_url,
159
171
  "entry_path" => entry_path,
@@ -164,9 +176,9 @@ module Everywhere
164
176
  }.compact
165
177
  end
166
178
 
167
- def to_shell_json
179
+ def to_shell_json(target: nil)
168
180
  require "json"
169
- JSON.generate(to_shell_hash)
181
+ JSON.generate(to_shell_hash(target: target))
170
182
  end
171
183
 
172
184
  private
@@ -174,6 +186,19 @@ module Everywhere
174
186
  def app = @data.fetch("app", nil) || {}
175
187
  def appearance = @data.fetch("appearance", nil) || {}
176
188
 
189
+ # Per-platform overrides, keyed by os (macos / ios / android / windows /
190
+ # linux). Each value overrides the matching `app:` keys for that platform.
191
+ def platforms = @data.fetch("platforms", nil) || {}
192
+
193
+ # The app-level keys with the target's platform override merged on top.
194
+ # Blank override values are dropped (`.compact`) so they don't erase a
195
+ # default. Without a target — or without an override for that os — this is
196
+ # just the plain `app:` hash.
197
+ def resolved(target)
198
+ override = target && platforms[self.class.os_of(target)]
199
+ override.is_a?(Hash) ? app.merge(override.compact) : app
200
+ end
201
+
177
202
  def normalize_color(value)
178
203
  case value
179
204
  when String
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ui"
4
+
5
+ module Everywhere
6
+ # Turns the raw, chatty output of the external build tools (tebako, the
7
+ # notarize script and the Apple tools it drives) into clean, indented, colored
8
+ # lines for the console and the Platform web log.
9
+ #
10
+ # A filter is a small state machine fed one raw line at a time. It returns:
11
+ # * a String -> print this (already colored/indented) instead of the raw line
12
+ # * :drop -> swallow the line (pure noise: repeated status, temp paths…)
13
+ # * nil -> not recognized; caller shows the raw line dimmed as a detail
14
+ #
15
+ # The FULL raw stream is always still written to the on-disk log, so nothing is
16
+ # lost — this only governs what a human sees scroll by.
17
+ class LogFilter
18
+ def self.for(profile) = new(profile)
19
+
20
+ def initialize(profile)
21
+ @profile = profile
22
+ @seen = {}
23
+ end
24
+
25
+ def call(raw)
26
+ line = raw.to_s.rstrip
27
+ return :drop if line.empty?
28
+
29
+ case @profile
30
+ when :notarize then notarize(line)
31
+ when :tebako then tebako(line)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # --- `every release` signing pipeline (notarize.sh + Apple tools) ---------
38
+
39
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
40
+ def notarize(line)
41
+ case line
42
+ # notarize.sh's own "==> phase" markers become substeps.
43
+ when /\A==> signing with:\s*(.+)/
44
+ UI.substep_line("signing bundle #{UI.dim("(#{short_identity(Regexp.last_match(1))})")}")
45
+ when /\A==> verifying signature/
46
+ UI.substep_line("verifying signature")
47
+ when /\A==> zipping for notarization:\s*(.+)/
48
+ UI.substep_line("zipping for notarization #{UI.dim("→ #{File.basename(Regexp.last_match(1))}")}")
49
+ when /\A==> submitting to Apple notary service/
50
+ UI.substep_line("submitting to Apple notary service #{UI.dim("(this can take a few minutes)")}")
51
+ when /\A==> stapling ticket/
52
+ UI.substep_line("stapling notarization ticket")
53
+ when /\A==> Gatekeeper assessment/
54
+ UI.substep_line("Gatekeeper assessment")
55
+ when /\A==> re-zipping/
56
+ UI.substep_line("re-zipping stapled app for distribution")
57
+
58
+ # Per-binary signing chatter -> one dim line each (basename only).
59
+ when /\A\s+sign (.+)/
60
+ UI.note_line("signed #{File.basename(Regexp.last_match(1))}")
61
+ when /: replacing existing signature/
62
+ :drop
63
+
64
+ # codesign --verify / stapler / spctl outcomes.
65
+ when /: valid on disk/
66
+ UI.note_line("valid on disk", marker: "✓", color: :green)
67
+ when /: satisfies its Designated Requirement/
68
+ :drop
69
+ when /The staple and validate action worked!/
70
+ UI.note_line("stapled and validated", marker: "✓", color: :green)
71
+ when /The validate action worked!/
72
+ :drop
73
+ when /\A(.+): accepted/
74
+ UI.note_line("Gatekeeper accepted", marker: "✓", color: :green)
75
+ when /\Asource=/, /\Aorigin=/
76
+ UI.note_line(line)
77
+
78
+ # notarytool --wait progress. Collapse the repeated dotted "In Progress"
79
+ # lines into a single heartbeat, and celebrate the terminal states.
80
+ when /Current status:\s*Accepted/
81
+ UI.note_line("notarization accepted", marker: "✓", color: :green)
82
+ when /Current status:\s*(Invalid|Rejected)/
83
+ UI.note_line("notarization #{Regexp.last_match(1).downcase}", marker: "✗", color: :red)
84
+ when /Current status:\s*In Progress/
85
+ once(:progress) { UI.note_line("waiting on Apple…", marker: "⋯") }
86
+ when /\A\s*id:\s*([0-9a-fA-F-]{36})/
87
+ once(:submission) { UI.note_line("submission #{Regexp.last_match(1)[0, 8]}…") }
88
+
89
+ # Pure noise from notarytool's own prose / the final banner (release.rb
90
+ # prints its own summary, so drop notarize.sh's).
91
+ when /\AConducting pre-submission checks/,
92
+ /\ASubmission ID received/,
93
+ /\ASuccessfully uploaded file/,
94
+ /\A\s*path:/,
95
+ /\A\s*status:/,
96
+ /\AWaiting for processing/,
97
+ /\AProcessing:/,
98
+ /Processing complete/,
99
+ /\A✅ Done\./,
100
+ /Send this to your friend/,
101
+ /Stapled, so it works even offline/,
102
+ /zipping preserves the signature/
103
+ :drop
104
+
105
+ else
106
+ nil # unrecognized -> caller shows it dimmed
107
+ end
108
+ end
109
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
110
+
111
+ # --- tebako press ---------------------------------------------------------
112
+ #
113
+ # Thousands of lines of C++/Ruby build output. We can't restructure it, but
114
+ # we can keep errors/warnings legible and dim the rest into the background so
115
+ # the important lines (and our own steps around it) stand out.
116
+ def tebako(line)
117
+ short = UI.short_path(line)
118
+ case line
119
+ when /\b(error|fatal|undefined reference|cannot find)\b/i
120
+ " #{UI.red(short)}"
121
+ when /\b(warning|deprecated)\b/i
122
+ " #{UI.yellow(short)}"
123
+ when /\A-- /, /\bBuilding\b/, /\bConfiguring\b/, /\bInstalling\b/
124
+ UI.note_line(short.sub(/\A-- /, ""))
125
+ else
126
+ " #{UI.gray(short)}"
127
+ end
128
+ end
129
+
130
+ # Show a line only the first time its key is seen; :drop the repeats. Keeps a
131
+ # long notarization from scrolling a wall of identical "waiting" lines (and
132
+ # notarytool's thrice-echoed submission id from showing three times).
133
+ def once(key)
134
+ return :drop if @seen[key]
135
+
136
+ @seen[key] = true
137
+ yield
138
+ end
139
+
140
+ # "Developer ID Application: Andrea Fomera (TU4485Z3KR)" or a bare SHA-1.
141
+ def short_identity(id)
142
+ id = id.strip
143
+ return "#{id[0, 10]}…" if id.match?(/\A[0-9A-F]{40}\z/i)
144
+
145
+ id[/\(([^)]+)\)\s*\z/, 1] || id
146
+ end
147
+ end
148
+ end
@@ -27,6 +27,7 @@ module Everywhere
27
27
  @config = config
28
28
  @framework = framework
29
29
  @ruby = ruby
30
+ @target_str = target # raw "os-arch" — selects platforms: overrides
30
31
  @target = parse_target(target, channel)
31
32
  @shell_dir = shell_dir
32
33
  end
@@ -35,9 +36,9 @@ module Everywhere
35
36
  def manifest
36
37
  {
37
38
  "app" => {
38
- "name" => @config.name,
39
- "bundle_id" => @config.bundle_id,
40
- "version" => @config.version
39
+ "name" => @config.name(target: @target_str),
40
+ "bundle_id" => @config.bundle_id(target: @target_str),
41
+ "version" => @config.version(target: @target_str)
41
42
  },
42
43
  "framework" => framework_info,
43
44
  "ruby" => @ruby,
@@ -31,14 +31,45 @@ module Everywhere
31
31
  ["", nil]
32
32
  end
33
33
 
34
- # Run a command streaming to the console while also capturing to a log file.
35
- def run_logged!(env, cmd, log:)
36
- require "shellwords"
37
- line = "#{cmd.shelljoin} 2>&1 | tee #{log.shellescape}"
38
- success = unbundled { system(env, "/bin/bash", "-o", "pipefail", "-c", line) }
34
+ # Run a command, capturing its full combined output to a log file while
35
+ # showing a (optionally filtered) view on the console.
36
+ #
37
+ # Pass filter: a LogFilter (or any #call(line) -> String | :drop | nil) to
38
+ # prettify what a human sees; the on-disk log always gets the raw stream, so
39
+ # nothing is lost. Unrecognized lines (filter returns nil) are shown dimmed
40
+ # and indented so they read as background detail under the current step.
41
+ def run_logged!(env, cmd, log:, filter: nil)
42
+ require "open3"
43
+ success = false
44
+ unbundled do
45
+ File.open(log, "w") do |logf|
46
+ Open3.popen2e(env, *cmd) do |stdin, out, wait|
47
+ stdin.close
48
+ out.each_line do |raw|
49
+ logf.write(raw)
50
+ emit(raw, filter)
51
+ end
52
+ success = wait.value.success?
53
+ end
54
+ end
55
+ end
39
56
  UI.die!("command failed: #{cmd.join(" ")} (full log: #{log})") unless success
40
57
  end
41
58
 
59
+ # Route one raw line to the console: verbatim when there's no filter, else
60
+ # the filter's rendition (:drop swallows it, nil falls back to dim detail).
61
+ def emit(raw, filter)
62
+ return $stdout.print(raw) unless filter
63
+
64
+ shown = filter.call(raw)
65
+ case shown
66
+ when :drop, false then nil
67
+ when nil then $stdout.puts(" #{UI.gray(UI.short_path(raw.rstrip))}")
68
+ else $stdout.puts(shown)
69
+ end
70
+ end
71
+ private_class_method :emit
72
+
42
73
  def spawn(env, cmd, chdir:)
43
74
  unbundled { Process.spawn(env, cmd, chdir: chdir) }
44
75
  end
data/lib/everywhere/ui.rb CHANGED
@@ -3,6 +3,10 @@
3
3
  module Everywhere
4
4
  # Terminal output helpers. Colors follow the informal standard:
5
5
  # on when stdout is a TTY or CLICOLOR_FORCE=1, always off when NO_COLOR is set.
6
+ #
7
+ # The hosted runner streams `every release` through a pipe (not a TTY) into the
8
+ # Platform web log, so it exports CLICOLOR_FORCE=1 to keep our color — the web
9
+ # UI renders ANSI. That's why we force rather than sniff the TTY there.
6
10
  module UI
7
11
  module_function
8
12
 
@@ -17,21 +21,76 @@ module Everywhere
17
21
  color? ? "\e[#{codes.join(";")}m#{text}\e[0m" : text.to_s
18
22
  end
19
23
 
20
- def bold(t) = paint(t, 1)
21
- def dim(t) = paint(t, 2)
22
- def red(t) = paint(t, 31)
23
- def green(t) = paint(t, 32)
24
- def yellow(t) = paint(t, 33)
25
- def cyan(t) = paint(t, 36)
24
+ def bold(t) = paint(t, 1)
25
+ def dim(t) = paint(t, 2)
26
+ def red(t) = paint(t, 31)
27
+ def green(t) = paint(t, 32)
28
+ def yellow(t) = paint(t, 33)
29
+ def blue(t) = paint(t, 34)
30
+ def magenta(t) = paint(t, 35)
31
+ def cyan(t) = paint(t, 36)
32
+ def gray(t) = paint(t, 90) # bright black — dimmer than dim() on most themes
26
33
 
27
- def step(msg) = puts("#{cyan("→")} #{msg}")
28
- def ok(msg) = puts("#{green("✓")} #{msg}")
29
- def bad(msg) = puts("#{red("✗")} #{msg}")
30
- def warn(msg) = puts("#{yellow("!")} #{msg}")
31
- def success(msg) = puts("#{green("✓")} #{bold(msg)}")
34
+ # --- output levels --------------------------------------------------------
35
+ #
36
+ # phase ● a major phase boundary (build / sign / notarize)
37
+ # step → a top-level action inside a phase
38
+ # substep ▸ a sub-action under a step (indented)
39
+ # detail · raw-ish detail under a substep (indented + dim)
40
+ #
41
+ # Every level has a *_line string builder (used by LogFilter, which returns
42
+ # lines for someone else to print) and a printer that puts it. ok / success /
43
+ # warn / bad / die! carry outcomes at any level.
44
+
45
+ def phase_line(msg) = "\n#{bold(cyan("●"))} #{bold(msg)}"
46
+ def step_line(msg) = "#{cyan("→")} #{msg}"
47
+ def substep_line(msg) = " #{cyan("▸")} #{msg}"
48
+ def detail_line(msg) = " #{gray("·")} #{gray(msg)}"
49
+ def ok_line(msg) = "#{green("✓")} #{msg}"
50
+ def bad_line(msg) = "#{red("✗")} #{msg}"
51
+ def warn_line(msg) = "#{yellow("!")} #{msg}"
52
+ def success_line(msg) = "#{green("✓")} #{bold(msg)}"
53
+
54
+ def phase(msg) = puts(phase_line(msg))
55
+ def step(msg) = puts(step_line(msg))
56
+ def substep(msg) = puts(substep_line(msg))
57
+ def detail(msg) = puts(detail_line(msg))
58
+ def ok(msg) = puts(ok_line(msg))
59
+ def bad(msg) = puts(bad_line(msg))
60
+ def warn(msg) = puts(warn_line(msg))
61
+ def success(msg) = puts(success_line(msg))
62
+
63
+ # An indented, dim outcome under a substep — e.g. "accepted", "valid on disk".
64
+ def note_line(msg, marker: "·", color: :gray)
65
+ tint = respond_to?(color) ? method(color) : method(:gray)
66
+ " #{tint.call(marker)} #{tint.call(msg)}"
67
+ end
68
+
69
+ def note(msg, marker: "·", color: :gray) = puts(note_line(msg, marker: marker, color: color))
32
70
 
33
71
  def die!(msg)
34
72
  abort "#{red("✗")} #{red(msg)}"
35
73
  end
74
+
75
+ # A yes/no fact, colored by truthiness (for signing receipts).
76
+ def yn(bool) = bool ? green("yes") : red("no")
77
+
78
+ # --- path shortening ------------------------------------------------------
79
+
80
+ # Collapse the noisy machine paths that external tools echo (codesign,
81
+ # notarytool, stapler) down to something a human can scan. In particular the
82
+ # runner works out of a deep `/var/folders/…/every-runner…/src/` sandbox — we
83
+ # strip that prefix so only the meaningful `dist/App.app` tail remains. Then
84
+ # $HOME → ~ and the cwd → a leading (dropped) prefix.
85
+ def short_path(str)
86
+ s = str.to_s.dup
87
+ # Runner/local temp sandboxes: keep only the part after the extracted src/.
88
+ s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/every-[a-z]+[0-9A-Za-z_-]+/src/}, "")
89
+ # Any other everywhere-* scratch dir: keep the basename tail.
90
+ s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/everywhere-[a-z]+[0-9A-Za-z_.-]*/}, "")
91
+ home = ENV["HOME"]
92
+ s = s.gsub("#{home}/", "~/") if home && !home.empty?
93
+ s
94
+ end
36
95
  end
37
96
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Everywhere
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
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
@@ -993,6 +993,9 @@ dependencies = [
993
993
  name = "example-shell"
994
994
  version = "0.1.0"
995
995
  dependencies = [
996
+ "objc2",
997
+ "objc2-foundation",
998
+ "objc2-web-kit",
996
999
  "serde_json",
997
1000
  "tauri",
998
1001
  "tauri-build",
@@ -2366,6 +2369,16 @@ dependencies = [
2366
2369
  "objc2-core-foundation",
2367
2370
  ]
2368
2371
 
2372
+ [[package]]
2373
+ name = "objc2-javascript-core"
2374
+ version = "0.3.2"
2375
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2376
+ checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586"
2377
+ dependencies = [
2378
+ "objc2",
2379
+ "objc2-core-foundation",
2380
+ ]
2381
+
2369
2382
  [[package]]
2370
2383
  name = "objc2-quartz-core"
2371
2384
  version = "0.3.2"
@@ -2378,6 +2391,17 @@ dependencies = [
2378
2391
  "objc2-foundation",
2379
2392
  ]
2380
2393
 
2394
+ [[package]]
2395
+ name = "objc2-security"
2396
+ version = "0.3.2"
2397
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2398
+ checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a"
2399
+ dependencies = [
2400
+ "bitflags 2.13.0",
2401
+ "objc2",
2402
+ "objc2-core-foundation",
2403
+ ]
2404
+
2381
2405
  [[package]]
2382
2406
  name = "objc2-ui-kit"
2383
2407
  version = "0.3.2"
@@ -2421,6 +2445,8 @@ dependencies = [
2421
2445
  "objc2-app-kit",
2422
2446
  "objc2-core-foundation",
2423
2447
  "objc2-foundation",
2448
+ "objc2-javascript-core",
2449
+ "objc2-security",
2424
2450
  ]
2425
2451
 
2426
2452
  [[package]]
@@ -14,6 +14,15 @@ tauri-plugin-notification = "2"
14
14
  tauri-plugin-dialog = "2"
15
15
  tauri-plugin-clipboard-manager = "2"
16
16
 
17
+ # macOS: set the WebView's `applicationNameForUserAgent`, which WKWebView
18
+ # natively APPENDS to its real default UA — so we get the true system UA plus
19
+ # our marker without ever reading or hardcoding the base string. Versions are
20
+ # pinned to match what wry 0.55 already resolves, so no duplicate objc2 trees.
21
+ [target.'cfg(target_os = "macos")'.dependencies]
22
+ objc2 = "0.6"
23
+ objc2-foundation = { version = "0.3", features = ["NSString", "NSUUID", "NSProcessInfo"] }
24
+ objc2-web-kit = { version = "0.3", features = ["WKWebViewConfiguration", "WKWebsiteDataStore"] }
25
+
17
26
  [profile.release]
18
27
  strip = true
19
28
  lto = true
@@ -32,6 +32,7 @@ struct AppConfig {
32
32
  raw_json: String,
33
33
  name: String,
34
34
  bundle_id: Option<String>,
35
+ version: Option<String>,
35
36
  entry_path: String,
36
37
  remote_url: Option<String>,
37
38
  background_light: Option<(u8, u8, u8)>,
@@ -71,6 +72,7 @@ fn load_config() -> AppConfig {
71
72
  raw_json: if value.is_null() { "{}".to_string() } else { value.to_string() },
72
73
  name: value["name"].as_str().unwrap_or("RubyEverywhere App").to_string(),
73
74
  bundle_id: value["bundle_id"].as_str().map(str::to_string),
75
+ version: value["version"].as_str().map(str::to_string),
74
76
  entry_path: value["entry_path"].as_str().unwrap_or("/").to_string(),
75
77
  remote_url: (value["mode"].as_str() == Some("remote"))
76
78
  .then(|| value["remote_url"].as_str().map(str::to_string))
@@ -549,12 +551,72 @@ const INIT_SCRIPT: &str = r#"
549
551
  })();
550
552
  "#;
551
553
 
554
+ // The marker we append to the WebView's own user agent. We never build or
555
+ // hardcode the browser part of the UA — the WebView keeps producing its real
556
+ // default and this is added on the end, so anything the Ruby side sniffs (the
557
+ // `browser` gem, `request.variant`) still sees a genuine desktop browser. The
558
+ // contract with the app: `request.user_agent =~ %r{RubyEverywhere/([\d.]+)}`
559
+ // tells it the request came from the native shell and which app version (from
560
+ // everywhere.yml → the submitted build) made it. Version is absent only in bare
561
+ // `cargo run` dev where no config was passed.
562
+ fn ua_marker(config: &AppConfig) -> String {
563
+ let os = std::env::consts::OS;
564
+ match &config.version {
565
+ Some(v) => format!("RubyEverywhere/{v} ({os})"),
566
+ None => format!("RubyEverywhere ({os})"),
567
+ }
568
+ }
569
+
570
+ // macOS: build the WKWebViewConfiguration ourselves so we can set
571
+ // `applicationNameForUserAgent` — WebKit appends it to the real default UA (the
572
+ // one clean, no-read way to *append* rather than replace). Because wry uses the
573
+ // config's own `websiteDataStore` when we supply a configuration (and ignores
574
+ // Tauri's `data_store_identifier`), we must reproduce the per-app persistent
575
+ // store here too, or logins would leak into the shared default store. Mirrors
576
+ // wry's own logic: identified store on macOS 14+, default otherwise / in dev.
577
+ #[cfg(target_os = "macos")]
578
+ fn macos_webview_configuration(
579
+ config: &AppConfig,
580
+ dev_mode: bool,
581
+ ) -> objc2::rc::Retained<objc2_web_kit::WKWebViewConfiguration> {
582
+ use objc2::MainThreadMarker;
583
+ use objc2_foundation::{NSProcessInfo, NSString, NSUUID, NSOperatingSystemVersion};
584
+ use objc2_web_kit::{WKWebViewConfiguration, WKWebsiteDataStore};
585
+
586
+ let mtm = MainThreadMarker::new().expect("webview configuration must be built on the main thread");
587
+ let cfg = unsafe { WKWebViewConfiguration::new(mtm) };
588
+
589
+ unsafe {
590
+ cfg.setApplicationNameForUserAgent(Some(&NSString::from_str(&ua_marker(config))));
591
+ }
592
+
593
+ // Cookie / session persistence, keyed by bundle_id so each app is isolated.
594
+ // Dev mode keeps the default store (matches the previous ephemeral-ish dev
595
+ // behaviour). dataStoreForIdentifier needs macOS 14+.
596
+ if !dev_mode {
597
+ let os14 = NSProcessInfo::processInfo().isOperatingSystemAtLeastVersion(
598
+ NSOperatingSystemVersion { majorVersion: 14, minorVersion: 0, patchVersion: 0 },
599
+ );
600
+ if os14 {
601
+ let id = config.bundle_id.as_deref().unwrap_or("com.rubyeverywhere.app");
602
+ let store = unsafe {
603
+ WKWebsiteDataStore::dataStoreForIdentifier(&NSUUID::from_bytes(data_store_id(id)), mtm)
604
+ };
605
+ unsafe { cfg.setWebsiteDataStore(&store) };
606
+ }
607
+ }
608
+
609
+ cfg
610
+ }
611
+
552
612
  fn build_window(
553
613
  app: &tauri::App,
554
614
  config: &AppConfig,
555
615
  title: &str,
556
616
  url: WebviewUrl,
557
617
  ) -> tauri::Result<tauri::WebviewWindow> {
618
+ let dev_mode = std::env::var("NATIVE_DEV_URL").is_ok();
619
+
558
620
  let mut builder = WebviewWindowBuilder::new(app, "main", url)
559
621
  .title(title)
560
622
  .inner_size(1100.0, 750.0)
@@ -566,23 +628,19 @@ fn build_window(
566
628
  }
567
629
 
568
630
  // Cookie / session persistence: give the webview a stable data store so
569
- // logins survive relaunches. macOS wants a 16-byte identifier (derived from
570
- // bundle_id); Windows/Linux want an explicit directory. Dev mode stays
571
- // ephemeral — a persistent cache would keep serving stale dev assets.
572
- let dev_mode = std::env::var("NATIVE_DEV_URL").is_ok();
631
+ // logins survive relaunches. macOS carries it (and the UA marker) on a
632
+ // custom WKWebViewConfiguration; Windows/Linux want an explicit directory.
633
+ // Dev mode stays ephemeral — a persistent cache would keep serving stale
634
+ // dev assets.
635
+ #[cfg(target_os = "macos")]
636
+ {
637
+ builder = builder.with_webview_configuration(macos_webview_configuration(config, dev_mode));
638
+ }
639
+ #[cfg(any(windows, target_os = "linux"))]
573
640
  if !dev_mode {
574
- #[cfg(target_os = "macos")]
575
- {
576
- builder = builder.data_store_identifier(data_store_id(
577
- config.bundle_id.as_deref().unwrap_or("com.rubyeverywhere.app"),
578
- ));
579
- }
580
- #[cfg(any(windows, target_os = "linux"))]
581
- {
582
- if let Ok(dir) = app.path().data_dir() {
583
- let id = config.bundle_id.clone().unwrap_or("com.rubyeverywhere.app".into());
584
- builder = builder.data_directory(dir.join(id).join("webview"));
585
- }
641
+ if let Ok(dir) = app.path().data_dir() {
642
+ let id = config.bundle_id.clone().unwrap_or("com.rubyeverywhere.app".into());
643
+ builder = builder.data_directory(dir.join(id).join("webview"));
586
644
  }
587
645
  }
588
646
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_everywhere
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
@@ -69,6 +69,7 @@ files:
69
69
  - lib/everywhere/icon.rb
70
70
  - lib/everywhere/ignore.rb
71
71
  - lib/everywhere/javascript/bridge.js
72
+ - lib/everywhere/log_filter.rb
72
73
  - lib/everywhere/paths.rb
73
74
  - lib/everywhere/platform/client.rb
74
75
  - lib/everywhere/platform/credentials.rb