ruby_everywhere 0.12.0 → 0.14.0
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/README.md +35 -33
- data/bridge/README.md +23 -23
- data/lib/everywhere/android_resources.rb +9 -9
- data/lib/everywhere/android_sdk.rb +9 -9
- data/lib/everywhere/asset_catalog.rb +7 -7
- data/lib/everywhere/blake2b.rb +1 -1
- data/lib/everywhere/boot.rb +1 -1
- data/lib/everywhere/builders/android.rb +25 -25
- data/lib/everywhere/builders/desktop.rb +6 -6
- data/lib/everywhere/builders/ios.rb +14 -14
- data/lib/everywhere/builders/native_sources.rb +1 -1
- data/lib/everywhere/child_supervision.rb +1 -1
- data/lib/everywhere/commands/build.rb +54 -24
- data/lib/everywhere/commands/clean.rb +1 -1
- data/lib/everywhere/commands/dev.rb +11 -11
- data/lib/everywhere/commands/doctor.rb +7 -7
- data/lib/everywhere/commands/icon.rb +2 -2
- data/lib/everywhere/commands/install.rb +21 -21
- data/lib/everywhere/commands/logs.rb +6 -6
- data/lib/everywhere/commands/platform/auth_status.rb +3 -3
- data/lib/everywhere/commands/platform/build.rb +7 -7
- data/lib/everywhere/commands/platform/login.rb +5 -5
- data/lib/everywhere/commands/platform/logout.rb +1 -1
- data/lib/everywhere/commands/platform/runner.rb +6 -6
- data/lib/everywhere/commands/preview.rb +14 -14
- data/lib/everywhere/commands/publish.rb +14 -14
- data/lib/everywhere/commands/release.rb +13 -9
- data/lib/everywhere/commands/updates_keygen.rb +4 -4
- data/lib/everywhere/config/app.rb +3 -3
- data/lib/everywhere/config/mobile.rb +2 -2
- data/lib/everywhere/config/native_desktop.rb +2 -2
- data/lib/everywhere/config/native_mobile.rb +3 -3
- data/lib/everywhere/config/updates.rb +11 -1
- data/lib/everywhere/database.rb +3 -3
- data/lib/everywhere/desktop_assets.rb +5 -5
- data/lib/everywhere/desktop_dev_app.rb +1 -1
- data/lib/everywhere/emulator.rb +5 -5
- data/lib/everywhere/log_filter.rb +1 -1
- data/lib/everywhere/omniauth.rb +1 -1
- data/lib/everywhere/paths.rb +1 -1
- data/lib/everywhere/platform/credentials.rb +1 -1
- data/lib/everywhere/request_context.rb +2 -2
- data/lib/everywhere/shellout.rb +2 -2
- data/lib/everywhere/simulator.rb +1 -1
- data/lib/everywhere/tab_filter.rb +2 -2
- data/lib/everywhere/version.rb +1 -1
- data/support/agents/AGENTS.md +7 -7
- data/support/agents/frameworks/hanami-views.md +8 -8
- data/support/agents/frameworks/hanami.md +2 -2
- data/support/agents/frameworks/rails-views.md +7 -7
- data/support/agents/frameworks/rails.md +8 -8
- data/support/agents/frameworks/sinatra-views.md +7 -7
- data/support/agents/frameworks/sinatra.md +2 -2
- data/support/agents/modes/local.md +2 -2
- data/support/agents/modes/remote.md +2 -2
- data/support/desktop/src-tauri/src/updater.rs +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8b84ded1729bc011133caca7923d38ef48fc832193bd960228332b04d299717
|
|
4
|
+
data.tar.gz: 52164a6c960be3ff61ebd969ca037df1c23c8843fa00f89a570f043814f68805
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37ee8e1843a575bc4678fd47c56d23d8108dd33af708a06e8317b32179837bb951e8cd396dfca8acb6ec3b84942bee9bd9b5089a8d853c57150fcabe1a651633
|
|
7
|
+
data.tar.gz: 442223b8697ab79c0bb7f0d63379cef3bc8453203707d2d4f7c835c85f60253049fb68d54f499c0f1d2b87dda4b3988b5042ef3b40457c0f31de380da1f5db8d
|
data/README.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
RubyEverywhere takes a Rails (or Sinatra, or Hanami) app and gives it a native
|
|
8
8
|
life on the platforms your users are already on:
|
|
9
9
|
|
|
10
|
-
- **Desktop
|
|
10
|
+
- **Desktop**: a [Tauri](https://tauri.app)-based shell. In *local* mode your
|
|
11
11
|
whole app is pressed into a single-file binary with
|
|
12
12
|
[Tebako](https://github.com/tamatebako/tebako) and runs on-device, no Ruby
|
|
13
13
|
install required; in *remote* mode the shell is a thin native wrapper around
|
|
14
14
|
your deployed app.
|
|
15
|
-
- **iOS
|
|
15
|
+
- **iOS**: a [Hotwire Native](https://native.hotwired.dev) shell with a real
|
|
16
16
|
tab bar, nav-bar buttons, menus, action sheets, notifications, haptics,
|
|
17
17
|
biometrics, deep links, and native sign-in.
|
|
18
|
-
- **Android
|
|
18
|
+
- **Android**: a feature-for-feature Hotwire Native Android port of the same
|
|
19
19
|
shell, stamped from the same `config/everywhere.yml`.
|
|
20
20
|
|
|
21
21
|
One config file, one CLI, one JavaScript API. The web app stays the source of
|
|
22
|
-
truth
|
|
22
|
+
truth, so most of what you'd normally rebuild natively (tabs, routes, menus,
|
|
23
23
|
badges) deploys with your Rails app rather than through an app-store review.
|
|
24
24
|
|
|
25
25
|
## Install
|
|
@@ -28,7 +28,7 @@ badges) deploys with your Rails app rather than through an app-store review.
|
|
|
28
28
|
gem install ruby_everywhere
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
This gives you two equivalent executables
|
|
31
|
+
This gives you two equivalent executables, `every` and `rbe`, plus the
|
|
32
32
|
packaging toolchain (the `rbe-tebako` fork) as a dependency.
|
|
33
33
|
|
|
34
34
|
Or add it to your app's Gemfile, which also wires up the Rails engine (bridge
|
|
@@ -40,13 +40,13 @@ gem "ruby_everywhere"
|
|
|
40
40
|
|
|
41
41
|
Requires Ruby >= 3.2.
|
|
42
42
|
|
|
43
|
-
**The gem is free, and it always will be.** Everything here
|
|
44
|
-
desktop shell, both mobile shells, the bridge
|
|
43
|
+
**The gem is free, and it always will be.** Everything here (the CLI, the
|
|
44
|
+
desktop shell, both mobile shells, the bridge) is MIT licensed and yours to
|
|
45
45
|
use, forever, with no account and no key. Local builds run on a Mac today
|
|
46
46
|
(pressing, code signing, Xcode and the Android SDK all want a macOS host).
|
|
47
47
|
If you don't own one, or you'd rather not keep a build toolchain healthy, the
|
|
48
48
|
[RubyEverywhere Platform](https://rubyeverywhere.com) is a paid service coming
|
|
49
|
-
soon that runs the exact same builds for you
|
|
49
|
+
soon that runs the exact same builds for you. See
|
|
50
50
|
[Hosted builds](#hosted-builds) below. It's a convenience, never a gate: nothing
|
|
51
51
|
in this gem is held back for it.
|
|
52
52
|
|
|
@@ -71,14 +71,14 @@ every build --ios # build the iOS shell
|
|
|
71
71
|
every build --android # build the APK/AAB
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
`every install` is idempotent
|
|
74
|
+
`every install` is idempotent: it adds `config/everywhere.yml`, a boot stub,
|
|
75
75
|
and the framework-specific config tweaks it needs, and skips anything already
|
|
76
76
|
in place. For Rails it does the surgery for you; for Sinatra and Hanami it
|
|
77
77
|
vendors the bridge and prints the remaining wiring rather than guessing at your
|
|
78
78
|
boot files.
|
|
79
79
|
|
|
80
|
-
Working with an AI coding agent? `--agents` writes an `AGENTS.md
|
|
81
|
-
Claude Code, Cursor, Copilot and Codex all read
|
|
80
|
+
Working with an AI coding agent? `--agents` writes an `AGENTS.md`, the format
|
|
81
|
+
Claude Code, Cursor, Copilot and Codex all read, covering the view helpers
|
|
82
82
|
(`native_app?`, `everywhere_nav_button`, `everywhere_fab`, the menus and badges),
|
|
83
83
|
`config/everywhere.yml`, the bridge API, and the `every` commands, so an agent
|
|
84
84
|
reaches for the right helper instead of hand-rolling markup. It's framework- and
|
|
@@ -91,7 +91,7 @@ re-running refreshes our section and leaves your own notes alone.
|
|
|
91
91
|
the native shells at it, so the edit-refresh loop is untouched Rails.
|
|
92
92
|
|
|
93
93
|
It keeps a dock pinned to the bottom of the terminal showing what each target is
|
|
94
|
-
doing, while the logs scroll normally above it
|
|
94
|
+
doing, while the logs scroll normally above it: ordinary scrollback, so you can
|
|
95
95
|
still scroll up and search a backtrace:
|
|
96
96
|
|
|
97
97
|
```
|
|
@@ -104,7 +104,7 @@ ios │ ▸ compiling Swift sources
|
|
|
104
104
|
|
|
105
105
|
Every target runs on its own thread, so builds happen in parallel and the keys
|
|
106
106
|
keep working while one is going. A build that fails marks that target red and
|
|
107
|
-
leaves everything else
|
|
107
|
+
leaves everything else running, the dev server included; fix the error and
|
|
108
108
|
press the key again. `r` restarts just the dev server, `?` expands the full key
|
|
109
109
|
list, and each line is tagged with the source that printed it once more than one
|
|
110
110
|
thing is talking.
|
|
@@ -115,7 +115,7 @@ lines instead.
|
|
|
115
115
|
## Configuration
|
|
116
116
|
|
|
117
117
|
Everything per-app lives in `config/everywhere.yml`. The native shell templates
|
|
118
|
-
are frozen
|
|
118
|
+
are frozen: the CLI stamps a copy per app and only ever writes the identity,
|
|
119
119
|
config, icon, and color files, so a shell upgrade never collides with your app.
|
|
120
120
|
|
|
121
121
|
```yaml
|
|
@@ -175,9 +175,9 @@ updates: # self-hosted, signed auto-updates
|
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
Also supported: `auth:` (native OAuth sign-in with a callback scheme),
|
|
178
|
-
`deep_linking:` (universal links / App Links
|
|
178
|
+
`deep_linking:` (universal links / App Links, where the gem serves the
|
|
179
179
|
`apple-app-site-association` and `assetlinks.json` files for you), and
|
|
180
|
-
`native:` for "supernative" extensions
|
|
180
|
+
`native:` for "supernative" extensions: Swift you keep in your app repo under
|
|
181
181
|
`native/ios/` that `every build` compiles into the shell, including SwiftUI
|
|
182
182
|
screens, bridge components, a custom splash, and pinned SPM packages.
|
|
183
183
|
|
|
@@ -213,11 +213,11 @@ Images the desktop shell itself draws live in `native/desktop/assets/`, using th
|
|
|
213
213
|
same `<name>[@2x|@3x][~dark].png` filenames the mobile assets folders use.
|
|
214
214
|
Name one `tray` (or `tray-template` for a macOS template image) and it becomes
|
|
215
215
|
the menu-bar icon; a custom `splash:` can reference the rest as
|
|
216
|
-
`assets/logo.png`. These are for the shell, not your pages
|
|
216
|
+
`assets/logo.png`. These are for the shell, not your pages. Images your HTML
|
|
217
217
|
references should keep coming from the asset pipeline.
|
|
218
218
|
|
|
219
219
|
`window:` shapes the desktop frame. `title_bar: overlay` is the macOS look where
|
|
220
|
-
the traffic lights float over your page and there's no title text
|
|
220
|
+
the traffic lights float over your page and there's no title text; pair it with
|
|
221
221
|
the `.everywhere-titlebar-inset` class from `everywhere/native.css`, which
|
|
222
222
|
reserves exactly the space they sit in. `frameless` removes the system chrome
|
|
223
223
|
altogether; the page then draws its own bar and drives the buttons through
|
|
@@ -231,7 +231,7 @@ there keep their clicks. `window.drag_height: 0` hands that back to you.
|
|
|
231
231
|
Tabs and path rules ship two ways from that one source: baked into the app
|
|
232
232
|
bundle for offline/first launch, and served live from
|
|
233
233
|
`/everywhere/{ios,android}_v1.json`, so changing them deploys with your web app.
|
|
234
|
-
`Everywhere.tabs` lets you build the tab bar per request
|
|
234
|
+
`Everywhere.tabs` lets you build the tab bar per request: feature flags, plan
|
|
235
235
|
tiers, roles, anything your app can answer while serving a request:
|
|
236
236
|
|
|
237
237
|
```ruby
|
|
@@ -251,12 +251,12 @@ end
|
|
|
251
251
|
|
|
252
252
|
The context carries `platform` (`"ios"`/`"android"`), `current_user`, and
|
|
253
253
|
`session`/`cookies` normalized across Rails and Rack. `ctx.tab("Admin")` pulls in
|
|
254
|
-
a tab declared `visible: false` in `everywhere.yml
|
|
254
|
+
a tab declared `visible: false` in `everywhere.yml`, declared so the build
|
|
255
255
|
validates its icon, hidden so nothing ships it until this block says otherwise.
|
|
256
256
|
|
|
257
257
|
## The JavaScript bridge
|
|
258
258
|
|
|
259
|
-
The gem ships `@rubyeverywhere/bridge
|
|
259
|
+
The gem ships `@rubyeverywhere/bridge`: one API for the browser, the desktop
|
|
260
260
|
shell, and the mobile shells. Rails apps get it served and importmap-pinned
|
|
261
261
|
automatically; Sinatra and Hanami get it vendored to `public/bridge.js`.
|
|
262
262
|
|
|
@@ -276,7 +276,7 @@ Everywhere.updates.check()
|
|
|
276
276
|
Everywhere.window.close() // desktop, for a page-drawn title bar
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
Everything degrades gracefully
|
|
279
|
+
Everything degrades gracefully, and the same page works in a plain browser tab and
|
|
280
280
|
gains native powers inside the app. See
|
|
281
281
|
[`bridge/README.md`](bridge/README.md) for the full surface.
|
|
282
282
|
|
|
@@ -300,11 +300,11 @@ Plus `native_app?`, `native_platform`, `everywhere_badge`,
|
|
|
300
300
|
|
|
301
301
|
| Command | What it does |
|
|
302
302
|
| --- | --- |
|
|
303
|
-
| `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks)
|
|
304
|
-
| `every doctor` | Check the toolchain
|
|
303
|
+
| `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks); `--local`, `--agents` |
|
|
304
|
+
| `every doctor` | Check the toolchain: Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
|
|
305
305
|
| `every dev` | Dev server with native shells on demand (`--desktop --ios --android --mobile --browser`) |
|
|
306
306
|
| `every logs` | Stream shell logs from the Simulator or Android device |
|
|
307
|
-
| `every build` | Package natively
|
|
307
|
+
| `every build` | Package natively: desktop, `--ios`, or `--android` |
|
|
308
308
|
| `every icon` | Generate `.icns` / `.ico` / Linux icons from a source PNG |
|
|
309
309
|
| `every release` | Sign, notarize, staple, and emit a `release.json` receipt |
|
|
310
310
|
| `every updates keygen` | Generate a minisign-compatible update-signing keypair |
|
|
@@ -324,17 +324,19 @@ the middle:
|
|
|
324
324
|
```sh
|
|
325
325
|
every updates keygen # once, per app
|
|
326
326
|
every release # build, sign, notarize, staple
|
|
327
|
-
every publish --notes-file NOTES.md # sign the
|
|
327
|
+
every publish --notes-file NOTES.md # sign the artifact, upload, flip latest.json
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
`every publish` signs the artifact and publishes that signature in the update
|
|
331
|
+
manifest, which the app checks the download against before installing. The
|
|
332
|
+
public key ships in the app; the secret key never leaves your machine. Apps can
|
|
333
|
+
check, install, and switch channels at runtime via `Everywhere.updates`.
|
|
332
334
|
|
|
333
335
|
## Hosted builds
|
|
334
336
|
|
|
335
337
|
Don't have a Mac, or want iOS and Android built without babysitting Xcode, the
|
|
336
|
-
Android SDK, and a signing setup? The **RubyEverywhere Platform
|
|
337
|
-
service
|
|
338
|
+
Android SDK, and a signing setup? The **RubyEverywhere Platform**, a paid
|
|
339
|
+
service coming soon, runs the same builds on managed machines and hands back
|
|
338
340
|
the artifacts. The CLI already speaks to it:
|
|
339
341
|
|
|
340
342
|
```sh
|
|
@@ -342,7 +344,7 @@ every platform login
|
|
|
342
344
|
every platform build --target ios-arm64,android-arm64
|
|
343
345
|
```
|
|
344
346
|
|
|
345
|
-
Same gem, same `config/everywhere.yml`, same output
|
|
347
|
+
Same gem, same `config/everywhere.yml`, same output. You're paying for the
|
|
346
348
|
build machines and the toolchain upkeep, not for features. The gem stays free
|
|
347
349
|
and complete on its own.
|
|
348
350
|
|
|
@@ -351,14 +353,14 @@ and complete on its own.
|
|
|
351
353
|
```
|
|
352
354
|
lib/everywhere/ the CLI, config, builders, and Rails engine
|
|
353
355
|
exe/every, exe/rbe the executables
|
|
354
|
-
bridge/ @rubyeverywhere/bridge
|
|
356
|
+
bridge/ @rubyeverywhere/bridge, the JS half
|
|
355
357
|
support/desktop/ the Tauri desktop shell template
|
|
356
358
|
support/mobile/ios/ the Hotwire Native iOS shell template
|
|
357
359
|
support/mobile/android/ the Hotwire Native Android shell template
|
|
358
360
|
support/agents/ the AGENTS.md guide `every install --agents` writes
|
|
359
361
|
```
|
|
360
362
|
|
|
361
|
-
The shell templates have their own READMEs documenting the freeze rules
|
|
363
|
+
The shell templates have their own READMEs documenting the freeze rules: what
|
|
362
364
|
the CLI stamps per app and what it must never touch:
|
|
363
365
|
[iOS](support/mobile/ios/README.md) · [Android](support/mobile/android/README.md).
|
|
364
366
|
|
data/bridge/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @rubyeverywhere/bridge
|
|
2
2
|
|
|
3
|
-
One API for the browser, the RubyEverywhere desktop shell, and
|
|
4
|
-
Native
|
|
3
|
+
One API for the browser, the RubyEverywhere desktop shell, and the Hotwire
|
|
4
|
+
Native iOS and Android shells: the JavaScript half of [RubyEverywhere](https://rubyeverywhere.com).
|
|
5
5
|
|
|
6
6
|
App code writes to this surface only; platform differences live in adapters
|
|
7
7
|
inside the package. Everything degrades gracefully: the same page works in a
|
|
@@ -33,7 +33,7 @@ Everywhere.confirm("Sure?") // Promise<boolean>, native dialog when pos
|
|
|
33
33
|
Everywhere.on("menu", handler) // shell events; returns an unsubscribe fn
|
|
34
34
|
Everywhere.visit("/settings") // Turbo.visit with location fallback
|
|
35
35
|
|
|
36
|
-
Everywhere.menu({ title, items }) // Promise<item | null
|
|
36
|
+
Everywhere.menu({ title, items }) // Promise<item | null>, native action
|
|
37
37
|
// sheet in the shell, a styled bottom
|
|
38
38
|
// sheet everywhere else. items:
|
|
39
39
|
// [{ title, id?, style?, disabled? }]
|
|
@@ -55,25 +55,25 @@ Everywhere.permissions.request("camera") // prompts when possible
|
|
|
55
55
|
Everywhere.permissions.openSettings() // for the denied dead-end (iOS asks once)
|
|
56
56
|
// status: "granted" | "denied" | "prompt" | "undeclared" | "unsupported"
|
|
57
57
|
// mobile permissions must be declared in everywhere.yml (camera/location
|
|
58
|
-
// declarations carry the usage string iOS shows)
|
|
58
|
+
// declarations carry the usage string iOS shows); undeclared never prompts
|
|
59
59
|
|
|
60
|
-
Everywhere.storage.get("key") // Promise<any | null
|
|
60
|
+
Everywhere.storage.get("key") // Promise<any | null>, device-local settings
|
|
61
61
|
Everywhere.storage.set("key", value) // Promise<boolean>; any JSON value round-trips
|
|
62
62
|
Everywhere.storage.remove("key") // Promise<boolean>
|
|
63
|
-
Everywhere.storage.clear() // Promise<boolean
|
|
63
|
+
Everywhere.storage.clear() // Promise<boolean>, clears app keys only
|
|
64
64
|
// UserDefaults in the mobile shell (survives web view resets and cache
|
|
65
65
|
// clears, unlike localStorage); localStorage-backed in browsers and the
|
|
66
|
-
// desktop shell. Settings, not secrets
|
|
66
|
+
// desktop shell. Settings, not secrets: tokens go in biometrics.credential.
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
### Native chrome (nav bar buttons, menus, action sheets)
|
|
70
70
|
|
|
71
71
|
Tag ordinary markup, and inside the mobile shell the bridge lifts it into real
|
|
72
|
-
native controls
|
|
72
|
+
native controls: a nav-bar button, a pull-down / overflow menu, or an action
|
|
73
73
|
sheet. Tapping the native control just `.click()`s the element it mirrors, so a
|
|
74
74
|
link navigates, a submit submits, and behavior stays defined once in the DOM. In
|
|
75
75
|
a browser the same markup is the plain HTML it already is. No JavaScript, no
|
|
76
|
-
Stimulus
|
|
76
|
+
Stimulus, so it clears CSP.
|
|
77
77
|
|
|
78
78
|
```html
|
|
79
79
|
<!-- becomes a nav-bar button (right side); icons are per-platform like tabs -->
|
|
@@ -103,7 +103,7 @@ Stimulus — CSP-safe.
|
|
|
103
103
|
</div>
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Rails ships helpers that emit exactly this
|
|
106
|
+
Rails ships helpers that emit exactly this: `everywhere_nav_button`,
|
|
107
107
|
`everywhere_submit_button`, `everywhere_nav_menu` / `everywhere_menu_item`,
|
|
108
108
|
`everywhere_menu`, and `everywhere_fab` (a floating action button). Include
|
|
109
109
|
`everywhere/native.css` for the FAB, the action-sheet fallback, and the
|
|
@@ -111,7 +111,7 @@ safe-area utilities.
|
|
|
111
111
|
|
|
112
112
|
### Multi-instance apps
|
|
113
113
|
|
|
114
|
-
Apps whose users pick a server
|
|
114
|
+
Apps whose users pick a server, one hosted platform with many instances, opt in
|
|
115
115
|
with `remote.instances: true` in `everywhere.yml`. The mobile shell then boots
|
|
116
116
|
into `remote.url` (your hosted picker page) until that page picks an instance:
|
|
117
117
|
|
|
@@ -120,13 +120,13 @@ Everywhere.instance.supported // true in a shell built with remote.instan
|
|
|
120
120
|
Everywhere.instance.current // active instance URL, or null (= built-in root)
|
|
121
121
|
|
|
122
122
|
Everywhere.instance.set("https://acme.example.com")
|
|
123
|
-
// validates, persists, and fully resets the app onto the instance
|
|
123
|
+
// validates, persists, and fully resets the app onto the instance. Every
|
|
124
124
|
// later launch boots straight there. {to: "/path"} lands somewhere specific.
|
|
125
125
|
|
|
126
126
|
Everywhere.instance.clear() // back to the picker (and resets again)
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
In a plain browser `set()`
|
|
129
|
+
In a plain browser `set()` navigates to the URL: going there is what
|
|
130
130
|
picking an instance means on the web.
|
|
131
131
|
|
|
132
132
|
Declarative forms need no JS: elements with `data-everywhere-haptic`
|
|
@@ -150,18 +150,18 @@ The payload arrives in Rust as a `serde_json::Value` and whatever the command
|
|
|
150
150
|
returns comes back as the resolved value; a command that returns `Err` rejects
|
|
151
151
|
with its own message.
|
|
152
152
|
|
|
153
|
-
Unlike the rest of the bridge this does **not** degrade
|
|
153
|
+
Unlike the rest of the bridge this does **not** degrade; there's no web
|
|
154
154
|
equivalent for "talk to a serial port", so off the desktop it rejects rather
|
|
155
155
|
than resolving something untrue. Gate on `Everywhere.desktop.supported`.
|
|
156
156
|
|
|
157
157
|
Under the hood this is an event round-trip, not Tauri's `invoke`: a
|
|
158
158
|
RubyEverywhere page is always served over http, and Tauri v2 refuses `invoke`
|
|
159
|
-
from any http origin
|
|
159
|
+
from any http origin, including the packaged app's own localhost server.
|
|
160
160
|
|
|
161
161
|
### Window controls
|
|
162
162
|
|
|
163
|
-
Desktop apps that draw their own title bar
|
|
164
|
-
`frameless` in `everywhere.yml`
|
|
163
|
+
Desktop apps that draw their own title bar (`window.title_bar: overlay` or
|
|
164
|
+
`frameless` in `everywhere.yml`) drive the window from the page:
|
|
165
165
|
|
|
166
166
|
```js
|
|
167
167
|
Everywhere.window.supported // true in the desktop shell
|
|
@@ -181,7 +181,7 @@ supplies its own.
|
|
|
181
181
|
|
|
182
182
|
**Dragging works out of the box.** With no system title bar left to grab, the
|
|
183
183
|
shell makes the top 28px of the page drag the window, and double-click there
|
|
184
|
-
zooms it
|
|
184
|
+
zooms it, with no markup required. Buttons, links and inputs in that strip keep
|
|
185
185
|
their clicks:
|
|
186
186
|
|
|
187
187
|
```html
|
|
@@ -232,16 +232,16 @@ In a plain browser tab the updates API is inert: `check()` resolves
|
|
|
232
232
|
|
|
233
233
|
## Platforms
|
|
234
234
|
|
|
235
|
-
- **desktop
|
|
235
|
+
- **desktop**: the Tauri-based RubyEverywhere shell. Notifications, dialogs,
|
|
236
236
|
clipboard, shell events, window controls, and auto-updates are native.
|
|
237
|
-
- **browser
|
|
237
|
+
- **browser**: honest web fallbacks: the Notification API, `window.confirm`,
|
|
238
238
|
`navigator.clipboard`, DOM CustomEvents.
|
|
239
|
-
- **mobile
|
|
239
|
+
- **mobile**: the Hotwire Native iOS and Android shells. `notify()` delivers a native local
|
|
240
240
|
notification via the shell's `everywhere--notification` bridge component
|
|
241
241
|
(web-bridge handshake is installed automatically unless the app already
|
|
242
|
-
loads `@hotwired/hotwire-native-bridge
|
|
242
|
+
loads `@hotwired/hotwire-native-bridge`; first one wins, like the official
|
|
243
243
|
package). `confirm()`/clipboard use the browser fallbacks, which are native
|
|
244
|
-
in
|
|
244
|
+
in the web view anyway. Everything degrades to browser behavior outside the
|
|
245
245
|
shell.
|
|
246
246
|
|
|
247
247
|
## License
|
|
@@ -100,10 +100,10 @@ module Everywhere
|
|
|
100
100
|
relative = relative(path, source)
|
|
101
101
|
case File.extname(path).downcase
|
|
102
102
|
when ".svg", ".pdf"
|
|
103
|
-
"#{relative}: Android has no runtime SVG or PDF renderer
|
|
103
|
+
"#{relative}: Android has no runtime SVG or PDF renderer. Export a PNG, or convert it to " \
|
|
104
104
|
"a VectorDrawable (Android Studio: right-click res → New → Vector Asset) and ship the .xml"
|
|
105
105
|
else
|
|
106
|
-
"#{relative}: unsupported image type
|
|
106
|
+
"#{relative}: unsupported image type. Use #{EXTENSIONS.join(", ")}"
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
@@ -116,7 +116,7 @@ module Everywhere
|
|
|
116
116
|
# resource name may — so it would otherwise fail with a naming error
|
|
117
117
|
# that says nothing about what the author actually tried to do.
|
|
118
118
|
if stem.end_with?(".9")
|
|
119
|
-
errors << "#{relative}: nine-patch drawables aren't supported yet
|
|
119
|
+
errors << "#{relative}: nine-patch drawables aren't supported yet. Ship a plain #{extension} " \
|
|
120
120
|
"(or a VectorDrawable .xml, which stretches without one)"
|
|
121
121
|
return nil
|
|
122
122
|
end
|
|
@@ -124,23 +124,23 @@ module Everywhere
|
|
|
124
124
|
name, scale, dark = parse(stem)
|
|
125
125
|
|
|
126
126
|
unless name.match?(VALID_NAME)
|
|
127
|
-
errors << "#{relative}: #{name.inspect} isn't a usable Android resource name
|
|
127
|
+
errors << "#{relative}: #{name.inspect} isn't a usable Android resource name. Use lowercase " \
|
|
128
128
|
"letters, digits and _, starting with a letter (#{suggestion(name).inspect} would " \
|
|
129
129
|
"work). Unlike iOS, Android has no room for capitals, hyphens or dots here"
|
|
130
130
|
return nil
|
|
131
131
|
end
|
|
132
132
|
if JAVA_KEYWORDS.include?(name)
|
|
133
|
-
errors << "#{relative}: #{name.inspect} is a Java keyword, so it can't name a field on R
|
|
133
|
+
errors << "#{relative}: #{name.inspect} is a Java keyword, so it can't name a field on R. " \
|
|
134
134
|
"rename it (#{"#{name}_image".inspect})"
|
|
135
135
|
return nil
|
|
136
136
|
end
|
|
137
137
|
if RESERVED.include?(name)
|
|
138
|
-
errors << "#{relative}: #{name} is reserved by the shell (launcher icon and tab placeholder)
|
|
138
|
+
errors << "#{relative}: #{name} is reserved by the shell (launcher icon and tab placeholder). " \
|
|
139
139
|
"rename it"
|
|
140
140
|
return nil
|
|
141
141
|
end
|
|
142
142
|
if scale && VECTOR.include?(extension)
|
|
143
|
-
errors << "#{relative}: an XML drawable is scale-free
|
|
143
|
+
errors << "#{relative}: an XML drawable is scale-free. Drop the @#{scale} suffix"
|
|
144
144
|
return nil
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -190,11 +190,11 @@ module Everywhere
|
|
|
190
190
|
dirs = group.map { |variant| variant[:source_dir] }.uniq
|
|
191
191
|
if dirs.length > 1
|
|
192
192
|
listed = dirs.map { |dir| dir == "." ? "assets/" : "assets/#{dir}/" }.join(" and ")
|
|
193
|
-
errors << "#{name}: same asset name in #{listed}
|
|
193
|
+
errors << "#{name}: same asset name in #{listed}. res/ is flat, so both would become " \
|
|
194
194
|
"R.drawable.#{name}. Rename one"
|
|
195
195
|
end
|
|
196
196
|
if group.map { |variant| variant[:vector] }.uniq.length > 1
|
|
197
|
-
errors << "#{name}: mixes an XML drawable with bitmaps
|
|
197
|
+
errors << "#{name}: mixes an XML drawable with bitmaps. Pick one per asset"
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
# Same destination directory and same name: aapt2 sees two files
|
|
@@ -98,7 +98,7 @@ module Everywhere
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def sdk_root!
|
|
101
|
-
sdk_root or UI.die!("no Android SDK
|
|
101
|
+
sdk_root or UI.die!("no Android SDK. Install Android Studio (it provisions the SDK, adb, " \
|
|
102
102
|
"the emulator and a JDK in one go), or point ANDROID_HOME at an " \
|
|
103
103
|
"existing SDK; looked in #{expected_sdk_root}")
|
|
104
104
|
end
|
|
@@ -115,22 +115,22 @@ module Everywhere
|
|
|
115
115
|
def emulator = sdk_tool("emulator", "emulator")
|
|
116
116
|
|
|
117
117
|
def adb!
|
|
118
|
-
adb or UI.die!("no adb under #{sdk_root!}
|
|
118
|
+
adb or UI.die!("no adb under #{sdk_root!}: #{install_hint("platform-tools")}")
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def emulator!
|
|
122
|
-
emulator or UI.die!("no Android emulator under #{sdk_root!}
|
|
122
|
+
emulator or UI.die!("no Android emulator under #{sdk_root!}: #{install_hint("emulator")}")
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
def sdkmanager = cmdline_tool("sdkmanager")
|
|
126
126
|
def avdmanager = cmdline_tool("avdmanager")
|
|
127
127
|
|
|
128
128
|
def sdkmanager!
|
|
129
|
-
sdkmanager or UI.die!("no sdkmanager under #{sdk_root!}
|
|
129
|
+
sdkmanager or UI.die!("no sdkmanager under #{sdk_root!}: #{cmdline_tools_hint}")
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def avdmanager!
|
|
133
|
-
avdmanager or UI.die!("no avdmanager under #{sdk_root!}
|
|
133
|
+
avdmanager or UI.die!("no avdmanager under #{sdk_root!}: #{cmdline_tools_hint}")
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
# Whether this SDK can install anything at all. Worth asking on its own,
|
|
@@ -237,12 +237,12 @@ module Everywhere
|
|
|
237
237
|
# where the message can name the cause.
|
|
238
238
|
def java_home!(minimum: MINIMUM_JDK)
|
|
239
239
|
home = java_home or
|
|
240
|
-
UI.die!("no JDK
|
|
240
|
+
UI.die!("no JDK. Install Android Studio (it bundles one at #{STUDIO_JBR.first}) " \
|
|
241
241
|
"or set JAVA_HOME to a JDK #{minimum} or newer")
|
|
242
242
|
version = java_version
|
|
243
243
|
return home if version.nil? || version >= minimum
|
|
244
244
|
|
|
245
|
-
UI.die!("#{home} is JDK #{version}; the Android Gradle Plugin needs #{minimum} or newer
|
|
245
|
+
UI.die!("#{home} is JDK #{version}; the Android Gradle Plugin needs #{minimum} or newer. " \
|
|
246
246
|
"set JAVA_HOME to a JDK #{minimum}+, or install Android Studio and use its bundled runtime")
|
|
247
247
|
end
|
|
248
248
|
|
|
@@ -321,7 +321,7 @@ module Everywhere
|
|
|
321
321
|
|
|
322
322
|
package = "platforms;#{platform_name(api)}"
|
|
323
323
|
UI.die!("the Android SDK has no #{package} (installed: " \
|
|
324
|
-
"#{installed_platforms.empty? ? "none" : installed_platforms.join(", ")})
|
|
324
|
+
"#{installed_platforms.empty? ? "none" : installed_platforms.join(", ")}). " \
|
|
325
325
|
"#{install_hint(package)}")
|
|
326
326
|
end
|
|
327
327
|
|
|
@@ -347,7 +347,7 @@ module Everywhere
|
|
|
347
347
|
found = avds
|
|
348
348
|
return found if found.any?
|
|
349
349
|
|
|
350
|
-
UI.die!("no Android virtual devices
|
|
350
|
+
UI.die!("no Android virtual devices. #{create_avd_hint}")
|
|
351
351
|
end
|
|
352
352
|
|
|
353
353
|
# How to get a bootable AVD. The GUI route comes first because a machine
|
|
@@ -57,15 +57,15 @@ module Everywhere
|
|
|
57
57
|
images, other = files.partition { |path| EXTENSIONS.include?(File.extname(path).downcase) }
|
|
58
58
|
|
|
59
59
|
other.each do |path|
|
|
60
|
-
errors << "#{relative(path, source)}: unsupported image type
|
|
60
|
+
errors << "#{relative(path, source)}: unsupported image type. Use #{EXTENSIONS.join(", ")}"
|
|
61
61
|
end
|
|
62
62
|
variants = images.filter_map { |path| variant(path, source, errors) }
|
|
63
63
|
|
|
64
64
|
sets = variants.group_by { |v| v[:name] }.map do |name, group|
|
|
65
65
|
dirs = group.map { |v| v[:dir] }.uniq
|
|
66
66
|
if dirs.length > 1
|
|
67
|
-
errors << "#{name}: same asset name in #{dirs.map { |d| d == "." ? "assets/" : "assets/#{d}/" }.join(" and ")} " \
|
|
68
|
-
"
|
|
67
|
+
errors << "#{name}: same asset name in #{dirs.map { |d| d == "." ? "assets/" : "assets/#{d}/" }.join(" and ")}. " \
|
|
68
|
+
"Names are global in the catalog, so rename one"
|
|
69
69
|
end
|
|
70
70
|
errors.concat(slot_errors(name, group))
|
|
71
71
|
{ name: name, dir: dirs.first, variants: group }
|
|
@@ -80,16 +80,16 @@ module Everywhere
|
|
|
80
80
|
name, scale, dark = parse(File.basename(path, File.extname(path)))
|
|
81
81
|
|
|
82
82
|
unless name.match?(VALID_NAME)
|
|
83
|
-
errors << "#{relative}: #{name.inspect} isn't a usable asset name
|
|
83
|
+
errors << "#{relative}: #{name.inspect} isn't a usable asset name. " \
|
|
84
84
|
"use letters, digits, _, - and ."
|
|
85
85
|
return nil
|
|
86
86
|
end
|
|
87
87
|
if RESERVED.include?(name)
|
|
88
|
-
errors << "#{relative}: #{name} is reserved (stamped from config/everywhere.yml)
|
|
88
|
+
errors << "#{relative}: #{name} is reserved (stamped from config/everywhere.yml). Rename it"
|
|
89
89
|
return nil
|
|
90
90
|
end
|
|
91
91
|
if scale && VECTOR.include?(extension)
|
|
92
|
-
errors << "#{relative}: vector assets are scale-free
|
|
92
|
+
errors << "#{relative}: vector assets are scale-free. Drop the @#{scale} suffix"
|
|
93
93
|
return nil
|
|
94
94
|
end
|
|
95
95
|
|
|
@@ -117,7 +117,7 @@ module Everywhere
|
|
|
117
117
|
def slot_errors(name, group)
|
|
118
118
|
errors = []
|
|
119
119
|
if group.map { |v| v[:vector] }.uniq.length > 1
|
|
120
|
-
errors << "#{name}: mixes vector and raster files
|
|
120
|
+
errors << "#{name}: mixes vector and raster files. Pick one per asset"
|
|
121
121
|
end
|
|
122
122
|
group.group_by { |v| [v[:scale], v[:dark]] }.each_value do |clash|
|
|
123
123
|
next if clash.length < 2
|
data/lib/everywhere/blake2b.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Everywhere
|
|
|
90
90
|
@warned_fallback = true
|
|
91
91
|
require_relative "ui"
|
|
92
92
|
mb = (File.size(path) / 1024.0 / 1024.0).round(1)
|
|
93
|
-
Everywhere::UI.warn("this Ruby's OpenSSL has no BLAKE2b512
|
|
93
|
+
Everywhere::UI.warn("this Ruby's OpenSSL has no BLAKE2b512, so hashing #{mb}MB in pure Ruby " \
|
|
94
94
|
"(slow, and the file is read into memory)")
|
|
95
95
|
end
|
|
96
96
|
|
data/lib/everywhere/boot.rb
CHANGED
|
@@ -88,7 +88,7 @@ module Everywhere
|
|
|
88
88
|
ENV[key] or raise Error, app_data_hint("#{key} is unset")
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
def app_data_hint(reason) = "can't work out where to keep app data (#{reason})
|
|
91
|
+
def app_data_hint(reason) = "can't work out where to keep app data (#{reason}). " \
|
|
92
92
|
"set NATIVE_APPDATA (or HOME / APPDATA)"
|
|
93
93
|
|
|
94
94
|
def prepare_app_data
|