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
|
@@ -123,7 +123,7 @@ module Everywhere
|
|
|
123
123
|
config = Everywhere::Config.load(framework ? framework.root : Dir.pwd)
|
|
124
124
|
unless framework || config.remote?
|
|
125
125
|
UI.die!("no supported framework here (Rails, Hanami, Sinatra) and no remote.url in " \
|
|
126
|
-
"config/everywhere.yml
|
|
126
|
+
"config/everywhere.yml, so nothing to run")
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
# The bundle id keys every per-app dir under ~/.rubyeverywhere that the
|
|
@@ -145,7 +145,7 @@ module Everywhere
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
if port_open?(port)
|
|
148
|
-
UI.warn "something is ALREADY RUNNING on port #{port}
|
|
148
|
+
UI.warn "something is ALREADY RUNNING on port #{port}, attaching to it instead of starting"
|
|
149
149
|
UI.warn "#{UI.dim("your Procfile.dev/watchers are NOT running; stop that server or use --port if this isn't yours")}"
|
|
150
150
|
@dock.set(:web, :running, detail: "port #{port} (attached)")
|
|
151
151
|
else
|
|
@@ -161,7 +161,7 @@ module Everywhere
|
|
|
161
161
|
# change, where a full restart of the session would mean rebuilding the
|
|
162
162
|
# native shells for nothing.
|
|
163
163
|
def restart_server
|
|
164
|
-
return UI.warn("nothing to restart
|
|
164
|
+
return UI.warn("nothing to restart: this app runs in remote mode") unless @framework
|
|
165
165
|
|
|
166
166
|
@dock.set(:web, :booting, detail: "restarting")
|
|
167
167
|
UI.step("restarting the dev server")
|
|
@@ -186,7 +186,7 @@ module Everywhere
|
|
|
186
186
|
def background(name, &block)
|
|
187
187
|
case @pool.start(name, &block)
|
|
188
188
|
when :busy
|
|
189
|
-
UI.warn "#{Dock::State::LABELS[name]} is already working
|
|
189
|
+
UI.warn "#{Dock::State::LABELS[name]} is already working, #{UI.dim("give it a moment")}"
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
@@ -210,7 +210,7 @@ module Everywhere
|
|
|
210
210
|
# Checked before anything is staged: without cargo the build dies deep
|
|
211
211
|
# in a relayed `sh: cargo: command not found`, after a dock entry and a
|
|
212
212
|
# half-written dev bundle say a shell is on its way.
|
|
213
|
-
Shellout.ensure_tool!("cargo", "the desktop shell is built with Rust
|
|
213
|
+
Shellout.ensure_tool!("cargo", "the desktop shell is built with Rust; install it from " \
|
|
214
214
|
"https://rustup.rs, then press d again")
|
|
215
215
|
|
|
216
216
|
# A bad `window:` or a broken assets/ tree marks the desktop red and
|
|
@@ -285,7 +285,7 @@ module Everywhere
|
|
|
285
285
|
# produce a long build that fails at the first tool. Android stays
|
|
286
286
|
# available everywhere — its toolchain genuinely is cross-platform.
|
|
287
287
|
unless Host.darwin?
|
|
288
|
-
UI.warn("iOS builds need macOS and Xcode
|
|
288
|
+
UI.warn("iOS builds need macOS and Xcode, #{UI.dim("nothing to do on this platform")}")
|
|
289
289
|
@dock.set(:ios, :idle, detail: "needs macOS")
|
|
290
290
|
return
|
|
291
291
|
end
|
|
@@ -343,7 +343,7 @@ module Everywhere
|
|
|
343
343
|
# A headless Linux box (or WSL) has no xdg-open, and run? would just
|
|
344
344
|
# return nil — the key would look broken. Print the URL instead.
|
|
345
345
|
unless Shellout.tool?(opener)
|
|
346
|
-
UI.warn("couldn't find #{opener}
|
|
346
|
+
UI.warn("couldn't find #{opener}; open #{UI.cyan(@dev_url)} yourself")
|
|
347
347
|
return
|
|
348
348
|
end
|
|
349
349
|
|
|
@@ -362,7 +362,7 @@ module Everywhere
|
|
|
362
362
|
|
|
363
363
|
udid = Simulator.booted_udid
|
|
364
364
|
unless udid
|
|
365
|
-
UI.warn "no booted iOS Simulator
|
|
365
|
+
UI.warn "no booted iOS Simulator; press #{UI.bold("i")} first"
|
|
366
366
|
return
|
|
367
367
|
end
|
|
368
368
|
UI.step("watching iOS Simulator logs #{UI.dim("(press l again to stop)")}")
|
|
@@ -384,7 +384,7 @@ module Everywhere
|
|
|
384
384
|
# are worth naming.
|
|
385
385
|
def wait_for_port(port, pid: nil, timeout: 60,
|
|
386
386
|
dead_hint: " (did `bundle install` run? does bin/dev's foreman exist?)",
|
|
387
|
-
slow_hint: "
|
|
387
|
+
slow_hint: ", though it may just be slow to boot; check its output above")
|
|
388
388
|
super
|
|
389
389
|
end
|
|
390
390
|
|
|
@@ -425,7 +425,7 @@ module Everywhere
|
|
|
425
425
|
def run_headless
|
|
426
426
|
@pool.wait_all
|
|
427
427
|
failed = @pool.failed
|
|
428
|
-
UI.die!("#{failed.join(", ")} failed
|
|
428
|
+
UI.die!("#{failed.join(", ")} failed, see the log above") if failed.any?
|
|
429
429
|
|
|
430
430
|
watcher = @children.synchronize { @relays[:web] || @relays[:desktop] }
|
|
431
431
|
return watcher.join if watcher
|
|
@@ -448,7 +448,7 @@ module Everywhere
|
|
|
448
448
|
UI.bad("#{Dock::State::LABELS[name]} failed: #{error.message}") unless error.is_a?(Everywhere::Fatal)
|
|
449
449
|
return unless (key = KEY_FOR[name])
|
|
450
450
|
|
|
451
|
-
UI.note("everything else is still running
|
|
451
|
+
UI.note("everything else is still running; press #{UI.bold(key)} to try again",
|
|
452
452
|
marker: "⌨", color: :cyan)
|
|
453
453
|
end
|
|
454
454
|
|
|
@@ -39,7 +39,7 @@ module Everywhere
|
|
|
39
39
|
|
|
40
40
|
puts
|
|
41
41
|
if ok
|
|
42
|
-
note = @optional_gaps.any? ? " #{UI.dim("(#{@optional_gaps.join(" and ")} would need setup
|
|
42
|
+
note = @optional_gaps.any? ? " #{UI.dim("(#{@optional_gaps.join(" and ")} would need setup, see above)")}" : ""
|
|
43
43
|
UI.success("ready to build#{note}")
|
|
44
44
|
else
|
|
45
45
|
UI.bad("fix the items above (see https://rubyeverywhere.com/docs/diy/requirements)")
|
|
@@ -58,7 +58,7 @@ module Everywhere
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def section_label(name, required)
|
|
61
|
-
required ? name : "#{name} #{UI.dim("(not in build.targets
|
|
61
|
+
required ? name : "#{name} #{UI.dim("(not in build.targets, informational)")}"
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
|
|
@@ -82,7 +82,7 @@ module Everywhere
|
|
|
82
82
|
unless Host.darwin?
|
|
83
83
|
return fix_check("macOS", "desktop builds are macOS-only for now") { false } if required
|
|
84
84
|
|
|
85
|
-
UI.warn("desktop builds are macOS-only for now
|
|
85
|
+
UI.warn("desktop builds are macOS-only for now, so skipped")
|
|
86
86
|
return false
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -155,7 +155,7 @@ module Everywhere
|
|
|
155
155
|
unless Host.darwin?
|
|
156
156
|
return fix_check("macOS with Xcode", "iOS apps can only be built on a Mac") { false } if required
|
|
157
157
|
|
|
158
|
-
UI.warn("needs macOS and Xcode
|
|
158
|
+
UI.warn("needs macOS and Xcode, so skipped")
|
|
159
159
|
return false
|
|
160
160
|
end
|
|
161
161
|
|
|
@@ -223,7 +223,7 @@ module Everywhere
|
|
|
223
223
|
if sdk.cmdline_tools?
|
|
224
224
|
UI.ok(labelled("Android SDK command-line tools", UI.short_path(sdk.sdkmanager)))
|
|
225
225
|
else
|
|
226
|
-
UI.warn("Android SDK command-line tools missing
|
|
226
|
+
UI.warn("Android SDK command-line tools missing, and nothing below can be installed " \
|
|
227
227
|
"until they are: #{sdk.cmdline_tools_hint}")
|
|
228
228
|
end
|
|
229
229
|
|
|
@@ -260,10 +260,10 @@ module Everywhere
|
|
|
260
260
|
# turned out to be), so a failure appends the exact command to run.
|
|
261
261
|
def fix_check(label, fix)
|
|
262
262
|
passed = yield
|
|
263
|
-
passed ? UI.ok(label) : UI.bad("#{label}
|
|
263
|
+
passed ? UI.ok(label) : UI.bad("#{label}: #{fix}")
|
|
264
264
|
passed
|
|
265
265
|
rescue StandardError
|
|
266
|
-
UI.bad("#{label}
|
|
266
|
+
UI.bad("#{label}: #{fix}")
|
|
267
267
|
false
|
|
268
268
|
end
|
|
269
269
|
|
|
@@ -20,7 +20,7 @@ module Everywhere
|
|
|
20
20
|
def call(source: nil, root: ".", output: nil, **)
|
|
21
21
|
root_path = File.expand_path(root)
|
|
22
22
|
source ||= default_source(root_path)
|
|
23
|
-
UI.die!("no source PNG
|
|
23
|
+
UI.die!("no source PNG. Pass one or set app.icon in everywhere.yml") unless source
|
|
24
24
|
UI.die!("source PNG not found: #{source}") unless File.exist?(source)
|
|
25
25
|
|
|
26
26
|
out = File.expand_path(output || File.join(root_path, "dist", "icons"))
|
|
@@ -29,7 +29,7 @@ module Everywhere
|
|
|
29
29
|
|
|
30
30
|
master = File.join(out, "AppIcon-macos.png")
|
|
31
31
|
unless Everywhere::Icon.macos_master(source, master)
|
|
32
|
-
UI.die!("couldn't read #{File.basename(source)}
|
|
32
|
+
UI.die!("couldn't read #{File.basename(source)}: expected an 8-bit RGB/RGBA PNG")
|
|
33
33
|
end
|
|
34
34
|
UI.ok("macOS master #{rel(master, out)} #{UI.dim("(inset + squircle, 1024px)")}")
|
|
35
35
|
|
|
@@ -40,7 +40,7 @@ module Everywhere
|
|
|
40
40
|
UI.step(UI.dim("tip: `every install --agents` writes an AGENTS.md guide for AI coding agents"))
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
UI.success("installed
|
|
43
|
+
UI.success("installed. Run `every dev` to open the app, `every build` to package it")
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
private
|
|
@@ -107,14 +107,14 @@ module Everywhere
|
|
|
107
107
|
# real app legitimately ships in (API-only, DATABASE_URL, no
|
|
108
108
|
# config/puma.rb), so say what was skipped — never raise on it.
|
|
109
109
|
def skip(label, reason)
|
|
110
|
-
UI.step("#{label} #{UI.dim("(skipped
|
|
110
|
+
UI.step("#{label} #{UI.dim("(skipped: #{reason})")}")
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def add_gem
|
|
114
|
-
UI.die!("no Gemfile in #{@root}
|
|
114
|
+
UI.die!("no Gemfile in #{@root}. Run `every install` inside your app") unless File.exist?(app_file("Gemfile"))
|
|
115
115
|
|
|
116
116
|
add_gemfile_gem("ruby_everywhere",
|
|
117
|
-
comment: "# Desktop apps from this #{@framework.name.capitalize} app
|
|
117
|
+
comment: "# Desktop apps from this #{@framework.name.capitalize} app. https://rubyeverywhere.com")
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
# Packaged Rack apps serve through Puma at runtime (Framework#serve_rack
|
|
@@ -166,14 +166,14 @@ module Everywhere
|
|
|
166
166
|
light: "#FFFFFF"
|
|
167
167
|
dark: "#1C1B1A"
|
|
168
168
|
|
|
169
|
-
# Native permissions the app may request
|
|
169
|
+
# Native permissions the app may request. Only declared ones can prompt.
|
|
170
170
|
# camera/location values are the sentence iOS shows when asking (mandatory).
|
|
171
171
|
# permissions:
|
|
172
172
|
# notifications: true
|
|
173
173
|
# camera: "Scan QR codes to pair devices."
|
|
174
174
|
# location: "Find things near you."
|
|
175
175
|
|
|
176
|
-
# Native menu-bar items
|
|
176
|
+
# Native menu-bar items. Each one visits a route in your app (no native code).
|
|
177
177
|
# menu:
|
|
178
178
|
# - label: Settings…
|
|
179
179
|
# path: /settings
|
|
@@ -182,7 +182,7 @@ module Everywhere
|
|
|
182
182
|
# - label: Documentation
|
|
183
183
|
# path: /help
|
|
184
184
|
|
|
185
|
-
# System tray / menu-bar-extra items
|
|
185
|
+
# System tray / menu-bar-extra items. Same shape as menu, plus action: show | quit.
|
|
186
186
|
# tray:
|
|
187
187
|
# - label: Open #{app_name}
|
|
188
188
|
# action: show
|
|
@@ -204,10 +204,10 @@ module Everywhere
|
|
|
204
204
|
# min_size: [600, 400]
|
|
205
205
|
|
|
206
206
|
# Mobile tab bar (iOS today, Android soon). Served live from
|
|
207
|
-
# /everywhere/ios_v1.json too, so tab changes deploy with the app
|
|
208
|
-
#
|
|
207
|
+
# /everywhere/ios_v1.json too, so tab changes deploy with the app.
|
|
208
|
+
# No app-store release. Icons: ios = SF Symbol name.
|
|
209
209
|
#
|
|
210
|
-
# Vary the bar per request with Everywhere.tabs in an initializer
|
|
210
|
+
# Vary the bar per request with Everywhere.tabs in an initializer:
|
|
211
211
|
# signed-in only, feature flags, roles. visible: false declares a tab
|
|
212
212
|
# without shipping it, for that block to add back (ctx.tab("Admin")).
|
|
213
213
|
# tabs:
|
|
@@ -225,7 +225,7 @@ module Everywhere
|
|
|
225
225
|
# ios: shield
|
|
226
226
|
# visible: false
|
|
227
227
|
|
|
228
|
-
# iOS-specific knobs (and native Swift you compile into the shell
|
|
228
|
+
# iOS-specific knobs (and native Swift you compile into the shell;
|
|
229
229
|
# see /docs/diy/native-extensions).
|
|
230
230
|
# native:
|
|
231
231
|
# ios:
|
|
@@ -234,7 +234,7 @@ module Everywhere
|
|
|
234
234
|
#
|
|
235
235
|
# Rust you keep in native/desktop/ and compile into the desktop
|
|
236
236
|
# shell. Declaring anything here switches the desktop build to a
|
|
237
|
-
# per-app cargo compile
|
|
237
|
+
# per-app cargo compile. The first one takes minutes.
|
|
238
238
|
# desktop:
|
|
239
239
|
# commands: [scan_ports] # reachable as Everywhere.desktop.invoke("scan_ports")
|
|
240
240
|
# setup: true # run native/desktop/setup.rs at boot
|
|
@@ -244,7 +244,7 @@ module Everywhere
|
|
|
244
244
|
# Durable build knobs (CLI flags still override per-run).
|
|
245
245
|
# build:
|
|
246
246
|
# ruby: "4.0.6"
|
|
247
|
-
# targets: # the platforms you ship
|
|
247
|
+
# targets: # the platforms you ship: the CI matrix, expressed once
|
|
248
248
|
# - macos-arm64
|
|
249
249
|
# # - ios-arm64 # Hotwire Native shell; remote mode only. Local builds
|
|
250
250
|
# # # (`every build` / `every dev --ios`) and hosted
|
|
@@ -255,7 +255,7 @@ module Everywhere
|
|
|
255
255
|
|
|
256
256
|
# Per-platform overrides. Keys are platform names (macos / ios / android /
|
|
257
257
|
# windows / linux); anything here overrides the app: defaults above for that
|
|
258
|
-
# platform
|
|
258
|
+
# platform. Handy when a store needs a distinct bundle id or version.
|
|
259
259
|
# platforms:
|
|
260
260
|
# ios:
|
|
261
261
|
# bundle_id: com.example.#{slug}.ios
|
|
@@ -269,13 +269,13 @@ module Everywhere
|
|
|
269
269
|
def remote_section
|
|
270
270
|
if @mode == "remote"
|
|
271
271
|
<<~YAML
|
|
272
|
-
# Remote mode
|
|
272
|
+
# Remote mode: the deployed URL the native shell opens.
|
|
273
273
|
remote:
|
|
274
274
|
url: "https://example.com"
|
|
275
275
|
YAML
|
|
276
276
|
else
|
|
277
277
|
<<~YAML
|
|
278
|
-
# Remote mode only
|
|
278
|
+
# Remote mode only: the deployed URL the native shell opens.
|
|
279
279
|
# Set mode: remote above, then uncomment:
|
|
280
280
|
# remote:
|
|
281
281
|
# url: "https://example.com"
|
|
@@ -327,7 +327,7 @@ module Everywhere
|
|
|
327
327
|
if begins != 1 || ends != 1 ||
|
|
328
328
|
existing.index(AgentsGuide::BEGIN_MARKER) > existing.index(AgentsGuide::END_MARKER)
|
|
329
329
|
UI.warn("AGENTS.md has unbalanced #{AgentsGuide::BEGIN_MARKER} / #{AgentsGuide::END_MARKER} " \
|
|
330
|
-
"markers
|
|
330
|
+
"markers, so leaving it alone; fix them by hand and re-run")
|
|
331
331
|
return nil
|
|
332
332
|
end
|
|
333
333
|
|
|
@@ -438,7 +438,7 @@ module Everywhere
|
|
|
438
438
|
# RubyEverywhere desktop-runtime configuration (generated by `every install`).
|
|
439
439
|
if ENV["NATIVE_PACKAGED"]
|
|
440
440
|
# Large static files truncate when streamed from the packaged read-only
|
|
441
|
-
# filesystem; Everywhere.boot! copies public/ to app-data
|
|
441
|
+
# filesystem; Everywhere.boot! copies public/ to app-data, so serve from there.
|
|
442
442
|
Rails.application.config.paths["public"] = ENV["NATIVE_PUBLIC_DIR"] if ENV["NATIVE_PUBLIC_DIR"]
|
|
443
443
|
|
|
444
444
|
# Keep desktop users signed in across launches (session cookies are
|
|
@@ -458,7 +458,7 @@ module Everywhere
|
|
|
458
458
|
puma = app_file("config", "puma.rb")
|
|
459
459
|
unless File.exist?(puma)
|
|
460
460
|
return skip("disable puma keep-alives when packaged",
|
|
461
|
-
"no config/puma.rb
|
|
461
|
+
"no config/puma.rb. Add `enable_keep_alives false if ENV[\"NATIVE_PACKAGED\"]` to yours if you add one")
|
|
462
462
|
end
|
|
463
463
|
|
|
464
464
|
contents = File.read(puma)
|
|
@@ -478,7 +478,7 @@ module Everywhere
|
|
|
478
478
|
db = app_file("config", "database.yml")
|
|
479
479
|
unless File.exist?(db)
|
|
480
480
|
return skip("point production SQLite at app-data dir",
|
|
481
|
-
"no config/database.yml
|
|
481
|
+
"no config/database.yml. Point the packaged database at ENV[\"NATIVE_STORAGE_DIR\"] yourself")
|
|
482
482
|
end
|
|
483
483
|
|
|
484
484
|
contents = File.read(db)
|
|
@@ -492,7 +492,7 @@ module Everywhere
|
|
|
492
492
|
File.write(db, contents) if made
|
|
493
493
|
|
|
494
494
|
unless made || contents.include?("NATIVE_STORAGE_DIR")
|
|
495
|
-
UI.warn "couldn't rewrite config/database.yml automatically
|
|
495
|
+
UI.warn "couldn't rewrite config/database.yml automatically. Point production " \
|
|
496
496
|
"sqlite paths at <%= ENV.fetch(\"NATIVE_STORAGE_DIR\", \"storage\") %>/..."
|
|
497
497
|
return
|
|
498
498
|
end
|
|
@@ -62,8 +62,8 @@ module Everywhere
|
|
|
62
62
|
VERBOSE_FORMAT = "threadtime"
|
|
63
63
|
|
|
64
64
|
def call(ios: false, android: false, last: nil, udid: nil, serial: nil, app_id: nil, verbose: false, **)
|
|
65
|
-
UI.die!("pick a platform
|
|
66
|
-
UI.die!("--ios and --android are two separate log streams
|
|
65
|
+
UI.die!("pick a platform: run every logs --ios, or every logs --android") unless ios || android
|
|
66
|
+
UI.die!("--ios and --android are two separate log streams; pick one") if ios && android
|
|
67
67
|
|
|
68
68
|
if android
|
|
69
69
|
android_logs(serial: serial, app_id: app_id, last: last, verbose: verbose)
|
|
@@ -76,7 +76,7 @@ module Everywhere
|
|
|
76
76
|
|
|
77
77
|
def ios_logs(udid: nil, last: nil, verbose: false)
|
|
78
78
|
udid ||= Everywhere::Simulator.booted_udid
|
|
79
|
-
UI.die!("no booted iOS Simulator
|
|
79
|
+
UI.die!("no booted iOS Simulator. Start one with `every dev --ios`") unless udid
|
|
80
80
|
|
|
81
81
|
UI.step(last ? "iOS Simulator logs from the last #{UI.bold(last)}"
|
|
82
82
|
: "watching iOS Simulator logs #{UI.dim("(Ctrl-C to stop)")}")
|
|
@@ -110,12 +110,12 @@ module Everywhere
|
|
|
110
110
|
# template (it's the applicationId), so the config supplies it.
|
|
111
111
|
def android_logs(serial: nil, app_id: nil, last: nil, verbose: false)
|
|
112
112
|
serial ||= Everywhere::Emulator.booted_serial
|
|
113
|
-
UI.die!("no Android emulator or device
|
|
113
|
+
UI.die!("no Android emulator or device. Start one with `every dev --android`") unless serial
|
|
114
114
|
|
|
115
115
|
application_id = app_id || Everywhere::Config.load(Dir.pwd).bundle_id(target: "android-arm64")
|
|
116
116
|
pid = Everywhere::Emulator.pid_of(serial, application_id)
|
|
117
117
|
unless pid
|
|
118
|
-
UI.die!("#{application_id} isn't running on #{serial}
|
|
118
|
+
UI.die!("#{application_id} isn't running on #{serial}. Start it with `every dev --android` " \
|
|
119
119
|
"(or pass --app-id if that isn't this app's applicationId)")
|
|
120
120
|
end
|
|
121
121
|
|
|
@@ -155,7 +155,7 @@ module Everywhere
|
|
|
155
155
|
return (Time.now - seconds).strftime(LOGCAT_TIME)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
-
UI.die!(%(--last takes a line count ("200") or a duration ("30s", "5m", "2h")
|
|
158
|
+
UI.die!(%(--last takes a line count ("200") or a duration ("30s", "5m", "2h"); got #{last.inspect}))
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
# "200" reads as a count, everything else as a duration.
|
|
@@ -18,7 +18,7 @@ module Everywhere
|
|
|
18
18
|
def call(url: nil, **)
|
|
19
19
|
base = Everywhere::Platform::Client.base_url(url)
|
|
20
20
|
creds = Everywhere::Platform::Credentials.load.for(base)
|
|
21
|
-
return UI.warn("not logged in to #{base}
|
|
21
|
+
return UI.warn("not logged in to #{base}. Run `every platform login`") unless creds
|
|
22
22
|
|
|
23
23
|
res = Everywhere::Platform::Client.new(base, token: creds["token"]).get("/cli/token")
|
|
24
24
|
if res.ok?
|
|
@@ -31,9 +31,9 @@ module Everywhere
|
|
|
31
31
|
last = tok["last_used_at"] ? " · last used #{tok["last_used_at"]}" : ""
|
|
32
32
|
UI.step("token: #{tok["hint"]}#{UI.dim(last)}")
|
|
33
33
|
elsif res.unauthorized?
|
|
34
|
-
UI.bad("saved token is invalid or revoked
|
|
34
|
+
UI.bad("saved token is invalid or revoked. Run `every platform login`")
|
|
35
35
|
else
|
|
36
|
-
UI.bad("couldn't check status
|
|
36
|
+
UI.bad("couldn't check status: HTTP #{res.status}")
|
|
37
37
|
end
|
|
38
38
|
rescue Everywhere::Error => e
|
|
39
39
|
UI.bad(e.message)
|
|
@@ -35,7 +35,7 @@ module Everywhere
|
|
|
35
35
|
config = Everywhere::Config.load(root_path)
|
|
36
36
|
base = Everywhere::Platform::Client.base_url(url)
|
|
37
37
|
creds = Everywhere::Platform::Credentials.load.for(base)
|
|
38
|
-
UI.die!("not logged in to #{base}
|
|
38
|
+
UI.die!("not logged in to #{base}. Run `every platform login`") unless creds
|
|
39
39
|
|
|
40
40
|
client = Everywhere::Platform::Client.new(base, token: creds["token"])
|
|
41
41
|
framework = config.remote? ? nil : Everywhere::Framework.detect(root_path)
|
|
@@ -129,7 +129,7 @@ module Everywhere
|
|
|
129
129
|
|
|
130
130
|
case body["error"]
|
|
131
131
|
when "missing_credentials"
|
|
132
|
-
UI.bad("can't build yet
|
|
132
|
+
UI.bad("can't build yet, missing signing credentials:")
|
|
133
133
|
Array(body["targets"]).each do |t|
|
|
134
134
|
UI.step("#{server_target_label(t["target"])}: add #{Array(t["missing"]).join(", ")}")
|
|
135
135
|
end
|
|
@@ -154,7 +154,7 @@ module Everywhere
|
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
def die_unauthorized!
|
|
157
|
-
UI.die!("token expired or revoked
|
|
157
|
+
UI.die!("token expired or revoked. Run `every platform login`")
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
# How often we ask the Platform for new logs. A build spends most of its
|
|
@@ -185,7 +185,7 @@ module Everywhere
|
|
|
185
185
|
errors += 1
|
|
186
186
|
status.clear
|
|
187
187
|
detach!(build_id, build_url, e) if errors >= MAX_POLL_ERRORS
|
|
188
|
-
UI.warn("lost contact with the Platform (#{e.message})
|
|
188
|
+
UI.warn("lost contact with the Platform (#{e.message}), retrying #{errors}/#{MAX_POLL_ERRORS}")
|
|
189
189
|
spin(status, [], started, IDLE_POLL)
|
|
190
190
|
next
|
|
191
191
|
end
|
|
@@ -210,7 +210,7 @@ module Everywhere
|
|
|
210
210
|
# We stopped watching; the build didn't stop. Leave the user everything
|
|
211
211
|
# they need to re-attach or grab the artifacts later.
|
|
212
212
|
def detach!(build_id, build_url, error)
|
|
213
|
-
UI.bad("gave up tailing #{UI.bold(build_id)} after #{MAX_POLL_ERRORS} failed polls
|
|
213
|
+
UI.bad("gave up tailing #{UI.bold(build_id)} after #{MAX_POLL_ERRORS} failed polls: #{error.message}")
|
|
214
214
|
UI.step("the build is still running on the Platform")
|
|
215
215
|
UI.step("watch it → #{UI.cyan(build_url)}") if build_url
|
|
216
216
|
UI.die!("re-attach with `every platform build --wait` once you're back online, " \
|
|
@@ -276,7 +276,7 @@ module Everywhere
|
|
|
276
276
|
if artifact["status"] == "succeeded"
|
|
277
277
|
report_success(client, artifact, out_dir, download)
|
|
278
278
|
else
|
|
279
|
-
UI.bad("#{label(artifact["target"])}
|
|
279
|
+
UI.bad("#{label(artifact["target"])}: #{artifact["error"] || "failed"}")
|
|
280
280
|
end
|
|
281
281
|
end
|
|
282
282
|
|
|
@@ -313,7 +313,7 @@ module Everywhere
|
|
|
313
313
|
def parse_target_spec(token, default_channel)
|
|
314
314
|
os_arch, channel, extra = token.split(":", -1)
|
|
315
315
|
if !extra.nil? || os_arch.to_s.empty? || (!channel.nil? && channel.empty?)
|
|
316
|
-
UI.die!("invalid target #{token.inspect}
|
|
316
|
+
UI.die!("invalid target #{token.inspect}: expected os-arch[:channel], e.g. ios-arm64:testflight")
|
|
317
317
|
end
|
|
318
318
|
|
|
319
319
|
os, arch = os_arch.split("-", 2)
|
|
@@ -23,13 +23,13 @@ module Everywhere
|
|
|
23
23
|
client = Everywhere::Platform::Client.new(base)
|
|
24
24
|
|
|
25
25
|
start = client.post("/cli/device_authorizations", client_name: client_name)
|
|
26
|
-
UI.die!("couldn't start login
|
|
26
|
+
UI.die!("couldn't start login: HTTP #{start.status}") unless start.ok?
|
|
27
27
|
|
|
28
28
|
# A captive portal or a misconfigured proxy answers 200 with HTML, which
|
|
29
29
|
# parses to {} — without this we'd print an empty code and poll nothing
|
|
30
30
|
# for ten minutes.
|
|
31
31
|
unless start.body["user_code"] && start.body["device_code"]
|
|
32
|
-
UI.die!("#{base} answered without a device code
|
|
32
|
+
UI.die!("#{base} answered without a device code. Check EVERYWHERE_PLATFORM_URL " \
|
|
33
33
|
"(a captive portal or proxy may be intercepting the request)")
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -56,7 +56,7 @@ module Everywhere
|
|
|
56
56
|
deadline = Clock.monotonic + (expires_in.positive? ? expires_in : 600)
|
|
57
57
|
loop do
|
|
58
58
|
sleep(interval)
|
|
59
|
-
UI.die!("login timed out
|
|
59
|
+
UI.die!("login timed out. Run `every platform login` again") if Clock.monotonic > deadline
|
|
60
60
|
|
|
61
61
|
# The user is mid-approval in a browser; a dropped connection here must
|
|
62
62
|
# not kill the login. Keep polling until the deadline instead.
|
|
@@ -65,14 +65,14 @@ module Everywhere
|
|
|
65
65
|
rescue Everywhere::Error
|
|
66
66
|
next
|
|
67
67
|
end
|
|
68
|
-
UI.die!("login failed
|
|
68
|
+
UI.die!("login failed: #{res.body["error"] || "invalid request"}") if res.status == 400
|
|
69
69
|
next unless res.ok? # transient 5xx: keep polling until the deadline
|
|
70
70
|
|
|
71
71
|
case res.body["status"]
|
|
72
72
|
when "approved" then return res.body
|
|
73
73
|
when "pending" then interval = [res.body["interval"].to_i, interval].max
|
|
74
74
|
when "denied" then UI.die!("request denied in the browser")
|
|
75
|
-
when "expired" then UI.die!("the code expired
|
|
75
|
+
when "expired" then UI.die!("the code expired. Run `every platform login` again")
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -24,7 +24,7 @@ module Everywhere
|
|
|
24
24
|
begin
|
|
25
25
|
Everywhere::Platform::Client.new(base, token: creds["token"]).delete("/cli/token")
|
|
26
26
|
rescue Everywhere::Error => e
|
|
27
|
-
UI.warn("couldn't reach the Platform to revoke (#{e.message})
|
|
27
|
+
UI.warn("couldn't reach the Platform to revoke (#{e.message}), removing local credential anyway")
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
store.delete(base)
|
|
@@ -38,9 +38,9 @@ module Everywhere
|
|
|
38
38
|
".aab" => "application/octet-stream"
|
|
39
39
|
}.freeze
|
|
40
40
|
RELEASE_NARRATION = {
|
|
41
|
-
"macos" => "every release
|
|
42
|
-
"ios" => "every release
|
|
43
|
-
"android" => "every release
|
|
41
|
+
"macos" => "every release: press → sign → notarize → staple",
|
|
42
|
+
"ios" => "every release: build → archive → export .ipa",
|
|
43
|
+
"android" => "every release: assemble → sign → bundle"
|
|
44
44
|
}.freeze
|
|
45
45
|
|
|
46
46
|
# A .mobileprovision has to be in both: Xcode reads the UserData copy,
|
|
@@ -82,7 +82,7 @@ module Everywhere
|
|
|
82
82
|
if status == "succeeded" && built
|
|
83
83
|
complete_success(built, release_json)
|
|
84
84
|
else
|
|
85
|
-
complete_failure("build failed
|
|
85
|
+
complete_failure("build failed, see log")
|
|
86
86
|
end
|
|
87
87
|
ensure
|
|
88
88
|
restore_keychains
|
|
@@ -162,7 +162,7 @@ module Everywhere
|
|
|
162
162
|
if Host.darwin?
|
|
163
163
|
env["APPLE_SIGNING_IDENTITY"] = install_certificate(path, meta, work)
|
|
164
164
|
else
|
|
165
|
-
remote_log("skipping #{cred["kind"]}
|
|
165
|
+
remote_log("skipping #{cred["kind"]}: keychain import needs macOS")
|
|
166
166
|
end
|
|
167
167
|
when "apple_asc_api_key"
|
|
168
168
|
env["NOTARY_KEY"] = path
|
|
@@ -290,7 +290,7 @@ module Everywhere
|
|
|
290
290
|
signed_id = upload_artifact(built)
|
|
291
291
|
post!("/runner/artifacts/#{@artifact}/complete",
|
|
292
292
|
status: "succeeded", file_signed_id: signed_id, release_json: release_json)
|
|
293
|
-
UI.success("build succeeded
|
|
293
|
+
UI.success("build succeeded: #{File.basename(built)}")
|
|
294
294
|
end
|
|
295
295
|
|
|
296
296
|
# Best-effort: if the thing that failed WAS connectivity, reporting the
|