ruby_everywhere 0.6.0 → 0.8.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/exe/every +2 -2
  3. data/exe/rbe +2 -2
  4. data/lib/everywhere/agents_guide.rb +3 -1
  5. data/lib/everywhere/blake2b.rb +17 -1
  6. data/lib/everywhere/boot.rb +30 -6
  7. data/lib/everywhere/builders/android.rb +152 -64
  8. data/lib/everywhere/builders/base.rb +53 -0
  9. data/lib/everywhere/builders/desktop.rb +32 -40
  10. data/lib/everywhere/builders/ios.rb +268 -36
  11. data/lib/everywhere/builders/native_sources.rb +38 -0
  12. data/lib/everywhere/child_processes.rb +4 -4
  13. data/lib/everywhere/child_supervision.rb +172 -0
  14. data/lib/everywhere/cli.rb +2 -0
  15. data/lib/everywhere/clock.rb +12 -0
  16. data/lib/everywhere/commands/build.rb +132 -66
  17. data/lib/everywhere/commands/clean.rb +8 -3
  18. data/lib/everywhere/commands/dev.rb +91 -242
  19. data/lib/everywhere/commands/doctor.rb +138 -21
  20. data/lib/everywhere/commands/install.rb +49 -8
  21. data/lib/everywhere/commands/platform/auth_status.rb +1 -0
  22. data/lib/everywhere/commands/platform/build.rb +141 -33
  23. data/lib/everywhere/commands/platform/login.rb +20 -6
  24. data/lib/everywhere/commands/platform/logout.rb +1 -0
  25. data/lib/everywhere/commands/platform/runner.rb +170 -25
  26. data/lib/everywhere/commands/preview.rb +286 -0
  27. data/lib/everywhere/commands/publish.rb +22 -2
  28. data/lib/everywhere/commands/release.rb +155 -35
  29. data/lib/everywhere/commands/shell_dir.rb +4 -2
  30. data/lib/everywhere/commands/updates_keygen.rb +25 -1
  31. data/lib/everywhere/config/app.rb +126 -0
  32. data/lib/everywhere/config/auth.rb +108 -0
  33. data/lib/everywhere/config/data.rb +50 -0
  34. data/lib/everywhere/config/deep_linking.rb +107 -0
  35. data/lib/everywhere/config/desktop_ui.rb +153 -0
  36. data/lib/everywhere/config/mobile.rb +211 -0
  37. data/lib/everywhere/config/native_desktop.rb +168 -0
  38. data/lib/everywhere/config/native_mobile.rb +337 -0
  39. data/lib/everywhere/config/shell.rb +57 -0
  40. data/lib/everywhere/config/updates.rb +63 -0
  41. data/lib/everywhere/config.rb +59 -1367
  42. data/lib/everywhere/console.rb +2 -1
  43. data/lib/everywhere/desktop_dev_app.rb +138 -0
  44. data/lib/everywhere/dock/state.rb +3 -1
  45. data/lib/everywhere/engine.rb +24 -0
  46. data/lib/everywhere/entrypoint.rb +24 -0
  47. data/lib/everywhere/error.rb +8 -0
  48. data/lib/everywhere/framework.rb +23 -6
  49. data/lib/everywhere/host.rb +11 -0
  50. data/lib/everywhere/ignore.rb +13 -5
  51. data/lib/everywhere/jump.rb +121 -0
  52. data/lib/everywhere/line_pump.rb +3 -1
  53. data/lib/everywhere/minisign.rb +1 -0
  54. data/lib/everywhere/mobile_config_endpoint.rb +47 -0
  55. data/lib/everywhere/mobile_configs_controller.rb +46 -0
  56. data/lib/everywhere/native_platform.rb +44 -0
  57. data/lib/everywhere/paths.rb +36 -13
  58. data/lib/everywhere/platform/client.rb +46 -7
  59. data/lib/everywhere/platform/credentials.rb +18 -8
  60. data/lib/everywhere/platform/snapshot.rb +12 -0
  61. data/lib/everywhere/plist.rb +17 -0
  62. data/lib/everywhere/png.rb +1 -0
  63. data/lib/everywhere/raw_tty.rb +51 -0
  64. data/lib/everywhere/receipt.rb +55 -10
  65. data/lib/everywhere/s3.rb +1 -0
  66. data/lib/everywhere/shell_pages.rb +109 -0
  67. data/lib/everywhere/shellout.rb +19 -0
  68. data/lib/everywhere/simulator.rb +12 -1
  69. data/lib/everywhere/tab_filter.rb +36 -0
  70. data/lib/everywhere/task_pool.rb +3 -4
  71. data/lib/everywhere/ui.rb +6 -1
  72. data/lib/everywhere/version.rb +6 -4
  73. data/lib/everywhere.rb +1 -2
  74. data/support/mobile/android/app/build.gradle.kts +29 -1
  75. data/support/mobile/ios/App/EverywhereConfig.swift +17 -5
  76. data/support/mobile/ios/App/SceneDelegate.swift +75 -8
  77. data/support/mobile/ios/App.xcodeproj/project.pbxproj +2 -4
  78. data/support/mobile/ios/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -1
  79. data/support/mobile/ios/README.md +13 -6
  80. data/support/release/macos/notarize.sh +3 -0
  81. metadata +40 -1
@@ -40,9 +40,27 @@ module Everywhere
40
40
 
41
41
  def run!(env, *cmd, chdir: Dir.pwd)
42
42
  success = system(child_env(env), *cmd, chdir: chdir)
43
+ # nil means the command never ran (missing binary), false that it failed.
44
+ UI.die!("#{cmd.first} not found — is it installed and on your PATH?") if success.nil?
43
45
  UI.die!("command failed: #{cmd.join(" ")}") unless success
44
46
  end
45
47
 
48
+ # Whether `name` resolves as an executable on the PATH children get (the
49
+ # unbundled one — a Bundler binstub visible to the CLI doesn't count).
50
+ def tool?(name)
51
+ return File.executable?(name) && !File.directory?(name) if name.include?("/")
52
+
53
+ path = (unbundled_base["PATH"] || ENV["PATH"]).to_s
54
+ exts = Gem.win_platform? ? [".exe", ".bat", ".cmd", ""] : [""]
55
+ path.split(File::PATH_SEPARATOR).any? do |dir|
56
+ exts.any? { |ext| File.executable?(File.join(dir, name + ext)) && !File.directory?(File.join(dir, name + ext)) }
57
+ end
58
+ end
59
+
60
+ def ensure_tool!(name, hint)
61
+ tool?(name) or UI.die!("#{name} not found — #{hint}")
62
+ end
63
+
46
64
  # quiet also detaches stdin. A quiet command is by definition one that has
47
65
  # nothing to say to the terminal, and `every dev` reads single keystrokes
48
66
  # from that same terminal — a child left holding stdin silently eats the
@@ -78,6 +96,7 @@ module Everywhere
78
96
  # ChildProcesses instead.
79
97
  def run_logged!(env, cmd, log:, filter: nil)
80
98
  require "open3"
99
+ ensure_tool!(cmd.first, "is it installed and on your PATH?")
81
100
  success = false
82
101
  File.open(log, "w") do |logf|
83
102
  # Inside an `every dev` worker the build leads its own process group,
@@ -56,11 +56,22 @@ module Everywhere
56
56
  out, status = Shellout.capture("xcrun", "simctl", "list", "devices", "available", "-j")
57
57
  return [] unless status&.success?
58
58
 
59
- JSON.parse(out).fetch("devices", {}).sort.flat_map { |_runtime, list| list }
59
+ JSON.parse(out).fetch("devices", {})
60
+ .sort_by { |runtime, _list| runtime_order(runtime) }
61
+ .flat_map { |_runtime, list| list }
60
62
  rescue JSON::ParserError
61
63
  []
62
64
  end
63
65
 
66
+ # Runtimes are keyed by identifier ("com.apple.CoreSimulator.SimRuntime.iOS-17-0"),
67
+ # so a plain string sort puts iOS-9-3 AFTER iOS-17-0 and "newest last" hands
68
+ # back the oldest runtime installed. Compare the version numerically, with
69
+ # the family (iOS/watchOS/…) as the primary key so the grouping is stable.
70
+ def runtime_order(runtime)
71
+ family, *version = runtime.to_s.split(".").last.to_s.split("-")
72
+ [family.to_s.downcase, version.map(&:to_i)]
73
+ end
74
+
64
75
  def wait_until_booted(udid, timeout: 60)
65
76
  deadline = Time.now + timeout
66
77
  until Time.now > deadline
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Everywhere
4
+ class << self
5
+ # Register a per-request filter for the mobile tab bar. The block receives
6
+ # the resolved tab list (`[{ "title" =>, "path" =>, "icon" => }, …]`) and
7
+ # the current request, and returns the subset to show — return `[]` to hide
8
+ # the tab bar entirely (the shell falls back to single-screen navigation).
9
+ #
10
+ # It runs inside the mobile config endpoint, which shares the app's session
11
+ # and cookies, so it can branch on auth or any request state:
12
+ #
13
+ # # config/initializers/everywhere.rb
14
+ # Everywhere.filter_tabs do |tabs, request|
15
+ # request.session[:user_id] ? tabs : []
16
+ # end
17
+ #
18
+ # Live like the rest of the config: the shell re-reads it on launch and on
19
+ # every foreground, so a sign-in shows the tabs on next foreground — no
20
+ # rebuild, no app-store release.
21
+ def filter_tabs(&block)
22
+ @tabs_filter = block
23
+ end
24
+
25
+ # Apply the registered filter (identity when none is set). Always returns
26
+ # an Array so a stray nil/scalar from a block can't break serialization.
27
+ def resolve_tabs(tabs, request)
28
+ return tabs unless @tabs_filter
29
+
30
+ Array(@tabs_filter.call(tabs, request))
31
+ end
32
+
33
+ # Test/reset hook.
34
+ attr_writer :tabs_filter
35
+ end
36
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "clock"
3
4
  require_relative "console"
4
5
  require_relative "child_processes"
5
6
 
@@ -78,8 +79,8 @@ module Everywhere
78
79
  return if live.empty?
79
80
 
80
81
  live.each { |thread| ChildProcesses.terminate(thread, grace: grace) }
81
- deadline = now + grace
82
- live.each { |thread| thread.join([deadline - now, 0].max) }
82
+ deadline = Clock.monotonic + grace
83
+ live.each { |thread| thread.join([deadline - Clock.monotonic, 0].max) }
83
84
  live.select(&:alive?).each(&:kill)
84
85
  Console.multiplexed = false
85
86
  end
@@ -117,7 +118,5 @@ module Everywhere
117
118
  live = @threads.each_value.count { |t| t.alive? && t != Thread.current }
118
119
  Console.multiplexed = live > 1
119
120
  end
120
-
121
- def now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
122
121
  end
123
122
  end
data/lib/everywhere/ui.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "clock"
3
4
  require_relative "fatal"
4
5
  require_relative "console"
5
6
 
@@ -164,7 +165,7 @@ module Everywhere
164
165
  private
165
166
 
166
167
  def headless(text)
167
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
168
+ now = Clock.monotonic
168
169
  return if @last_headless && (now - @last_headless) < HEADLESS_INTERVAL
169
170
 
170
171
  @last_headless = now
@@ -217,5 +218,9 @@ module Everywhere
217
218
  s = s.gsub("#{home}/", "~/") if home && !home.empty?
218
219
  s
219
220
  end
221
+
222
+ # short_path for a path we know the root of: drop the project root (or the
223
+ # cwd, when the command runs from somewhere else) before shortening.
224
+ def rel_path(path, root) = short_path(path.to_s.sub("#{root}/", "").sub("#{Dir.pwd}/", ""))
220
225
  end
221
226
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Everywhere
4
- VERSION = "0.6.0"
6
+ VERSION = "0.8.0"
5
7
 
6
8
  # Version of the @rubyeverywhere/bridge JS this gem ships. bridge/ in the
7
9
  # gem IS the npm package (served to Rails apps by Everywhere::Engine,
8
10
  # vendored to public/ for Sinatra/Hanami), so its package.json is the single
9
11
  # source of truth. The build receipt records it; it versions independently
10
12
  # of the CLI.
11
- BRIDGE_VERSION = File.read(
12
- File.expand_path("../../bridge/package.json", __dir__)
13
- )[/"version":\s*"([^"]+)"/, 1]
13
+ BRIDGE_VERSION = JSON.parse(
14
+ File.read(File.expand_path("../../bridge/package.json", __dir__))
15
+ )["version"]
14
16
  end
data/lib/everywhere.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # Runtime entry point: what an app gets from `require "ruby_everywhere"`.
4
4
  # The CLI lives in everywhere/cli and is only loaded by the every/rbe executables.
5
5
  require_relative "everywhere/version"
6
+ require_relative "everywhere/error"
6
7
  require_relative "everywhere/config"
7
8
  require_relative "everywhere/framework"
8
9
  require_relative "everywhere/database"
@@ -12,8 +13,6 @@ require_relative "everywhere/boot"
12
13
  require_relative "everywhere/engine" if defined?(::Rails::Engine)
13
14
 
14
15
  module Everywhere
15
- class Error < StandardError; end
16
-
17
16
  # App-facing runtime configuration. Apps register overrides at load time:
18
17
  #
19
18
  # # config/initializers/everywhere.rb (Rails) or anywhere loaded by config.ru
@@ -18,6 +18,15 @@ val everywhere = Properties().apply {
18
18
  fun stamped(key: String, default: String): String =
19
19
  everywhere.getProperty("everywhere.$key")?.takeIf { it.isNotBlank() } ?: default
20
20
 
21
+ // Release signing and the versionCode override arrive as environment, never as
22
+ // stamped properties: everywhere.properties lives in the work dir for the life
23
+ // of the app, and a keystore password written there outlives the build that
24
+ // needed it. `every build` resolves and passes these; a direct Gradle
25
+ // invocation can export them itself.
26
+ fun env(name: String): String? = System.getenv("EVERY_ANDROID_$name")?.takeIf { it.isNotBlank() }
27
+
28
+ val releaseKeystore = env("KEYSTORE")?.let { file(it) }
29
+
21
30
  android {
22
31
  // The namespace is the R class / BuildConfig package and is frozen. It is
23
32
  // deliberately NOT the applicationId: apps rename their bundle id freely,
@@ -28,7 +37,7 @@ android {
28
37
  defaultConfig {
29
38
  applicationId = stamped("applicationId", "com.rubyeverywhere.app")
30
39
  versionName = stamped("versionName", "0.1.0")
31
- versionCode = stamped("versionCode", "1").toInt()
40
+ versionCode = (env("VERSION_CODE") ?: stamped("versionCode", "1")).toInt()
32
41
 
33
42
  // minSdk 28 is Hotwire Native Android's floor and is not negotiable.
34
43
  // It also clears API 26 for Typeface.Builder.setFontVariationSettings,
@@ -63,11 +72,30 @@ android {
63
72
  getByName("main") { res.srcDir("src/stamped/res") }
64
73
  }
65
74
 
75
+ signingConfigs {
76
+ // Created only when a keystore is in the environment: the default build
77
+ // is an unsigned release, and a signingConfig with a null storeFile is
78
+ // a configuration-time failure rather than an unsigned APK.
79
+ releaseKeystore?.let { keystore ->
80
+ create("release") {
81
+ storeFile = keystore
82
+ storePassword = env("KEYSTORE_PASSWORD")
83
+ keyAlias = env("KEY_ALIAS")
84
+ // keytool -genkeypair without -keypass gives the key the store's
85
+ // password; the CLI resolves the same fallback before it gets here.
86
+ keyPassword = env("KEY_PASSWORD") ?: env("KEYSTORE_PASSWORD")
87
+ }
88
+ }
89
+ }
90
+
66
91
  buildTypes {
67
92
  getByName("debug") {
68
93
  isDebuggable = true
69
94
  }
70
95
  release {
96
+ if (releaseKeystore != null) {
97
+ signingConfig = signingConfigs.getByName("release")
98
+ }
71
99
  isMinifyEnabled = false
72
100
  proguardFiles(
73
101
  getDefaultProguardFile("proguard-android-optimize.txt"),
@@ -145,20 +145,31 @@ struct EverywhereConfig: Decodable {
145
145
  /// relaunches; release builds never read it.
146
146
  private static let devOverrideURL: URL? = {
147
147
  let key = "EverywhereDevURL"
148
- if let dev = ProcessInfo.processInfo.environment["EVERYWHERE_DEV_URL"], let url = URL(string: dev) {
148
+ if let dev = ProcessInfo.processInfo.environment["EVERYWHERE_DEV_URL"], let url = rootWorthy(dev) {
149
149
  #if DEBUG
150
- UserDefaults.standard.set(dev, forKey: key)
150
+ UserDefaults.standard.set(url.absoluteString, forKey: key)
151
151
  #endif
152
152
  return url
153
153
  }
154
154
  #if DEBUG
155
- if let saved = UserDefaults.standard.string(forKey: key), let url = URL(string: saved) {
155
+ if let saved = UserDefaults.standard.string(forKey: key), let url = rootWorthy(saved) {
156
156
  return url
157
157
  }
158
158
  #endif
159
159
  return nil
160
160
  }()
161
161
 
162
+ /// A string as an app ROOT: everything derived from a root (path config,
163
+ /// tab URLs) is built by appending paths, so a root carrying a query or
164
+ /// fragment would smear them onto every derived URL. Paths are kept —
165
+ /// dev URLs legitimately carry entry_path.
166
+ private static func rootWorthy(_ string: String) -> URL? {
167
+ guard var components = URLComponents(string: string) else { return nil }
168
+ components.query = nil
169
+ components.fragment = nil
170
+ return components.url
171
+ }
172
+
162
173
  // MARK: Instance override (multi-instance apps)
163
174
 
164
175
  /// UserDefaults key holding the user-chosen instance root. Multi-instance
@@ -186,9 +197,10 @@ struct EverywhereConfig: Decodable {
186
197
  return true
187
198
  }
188
199
  guard let scheme = url.scheme?.lowercased(), ["https", "http"].contains(scheme),
189
- url.host != nil
200
+ url.host != nil,
201
+ let root = rootWorthy(url.absoluteString)
190
202
  else { return false }
191
- UserDefaults.standard.set(url.absoluteString, forKey: instanceKey)
203
+ UserDefaults.standard.set(root.absoluteString, forKey: instanceKey)
192
204
  return true
193
205
  }
194
206
 
@@ -1,5 +1,6 @@
1
1
  import AuthenticationServices
2
2
  import HotwireNative
3
+ import SwiftUI
3
4
  import UIKit
4
5
  import WebKit
5
6
 
@@ -369,7 +370,15 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
369
370
  nav.start()
370
371
  }
371
372
  } else {
372
- let controller = HotwireTabBarController(navigatorDelegate: self, lazyLoadTabs: config.lazyLoadsTabs)
373
+ let controller = EverywhereTabBarController(navigatorDelegate: self, lazyLoadTabs: config.lazyLoadsTabs)
374
+ if config.remoteInstances == true {
375
+ controller.leaveTabIndex = entries.firstIndex { $0.path == EverywhereTabBarController.jumpLeavePath }
376
+ controller.onLeaveSelected = { [weak self] in
377
+ guard let self else { return }
378
+ EverywhereConfig.setInstanceURL(nil)
379
+ self.resetApp(to: self.config.startURL)
380
+ }
381
+ }
373
382
  tabBarController = controller
374
383
  navigator = nil
375
384
  window?.rootViewController = controller
@@ -543,13 +552,23 @@ extension SceneDelegate: NavigatorDelegate {
543
552
  // App-provided native screens: a path rule's `view_controller`
544
553
  // identifier resolves through the extension registry (everywhere.yml
545
554
  // native.ios.screens). Unknown identifiers fall through to the web.
546
- if let identifier = proposal.properties["view_controller"] as? String,
547
- let screen = EverywhereExtensions.screen(for: identifier, url: proposal.url) {
548
- // A native screen IS first content: without this, an app whose
549
- // entry path is native never finishes a request and the splash
550
- // sits until its safety timeout.
551
- dismissSplash()
552
- return .acceptCustom(screen)
555
+ if let identifier = proposal.properties["view_controller"] as? String {
556
+ if let screen = EverywhereExtensions.screen(for: identifier, url: proposal.url) {
557
+ // A native screen IS first content: without this, an app whose
558
+ // entry path is native never finishes a request and the splash
559
+ // sits until its safety timeout.
560
+ dismissSplash()
561
+ return .acceptCustom(screen)
562
+ }
563
+ // Previewing someone else's app (a picked instance): its rules
564
+ // name native screens compiled into ITS build, not this shell.
565
+ // Say so, instead of falling through to a web 404. Outside a
566
+ // preview an unknown id keeps falling through — there it's a
567
+ // typo, and the web response is the honest answer.
568
+ if config.remoteInstances == true, EverywhereConfig.instanceURL != nil {
569
+ dismissSplash()
570
+ return .acceptCustom(everywhereHost(MissingExtensionScreen(identifier: identifier)))
571
+ }
553
572
  }
554
573
  return .accept
555
574
  }
@@ -585,3 +604,51 @@ extension SceneDelegate: NavigatorDelegate {
585
604
  activeNavigator.activeNavigationController.present(errorViewController, animated: true)
586
605
  }
587
606
  }
607
+
608
+ /// Tab bar that treats the Jump "leave preview" tab as a native action.
609
+ /// The page behind that tab is a gem-served browser fallback with no Turbo,
610
+ /// so letting it become a web visit can only end at Hotwire's "Turbo is not
611
+ /// ready" screen — selecting it must clear the picked instance directly,
612
+ /// and the tap never reaches the web layer.
613
+ final class EverywhereTabBarController: HotwireTabBarController {
614
+ static let jumpLeavePath = "/everywhere/jump/leave"
615
+
616
+ var leaveTabIndex: Int?
617
+ var onLeaveSelected: (() -> Void)?
618
+
619
+ func tabBarController(_ tabBarController: UITabBarController,
620
+ shouldSelect viewController: UIViewController) -> Bool {
621
+ guard let leaveTabIndex,
622
+ tabBarController.viewControllers?.firstIndex(of: viewController) == leaveTabIndex
623
+ else { return true }
624
+
625
+ onLeaveSelected?()
626
+ return false
627
+ }
628
+ }
629
+
630
+ /// Shown while PREVIEWING an app whose path rules route here natively: the
631
+ /// screen exists as Swift in that app's native/ios, compiled into its real
632
+ /// build — this shell only carries its own extensions. Honest placeholder
633
+ /// beats a mystery web 404.
634
+ struct MissingExtensionScreen: View {
635
+ let identifier: String
636
+
637
+ var body: some View {
638
+ VStack(spacing: 14) {
639
+ Image(systemName: "puzzlepiece.extension")
640
+ .font(.system(size: 44))
641
+ .foregroundStyle(.secondary)
642
+ Text("This screen is native")
643
+ .font(.title3.bold())
644
+ Text("In the full build, “\(identifier)” is a native screen compiled " +
645
+ "into the app. Jump can't run another app's native code — " +
646
+ "everything else in this preview works.")
647
+ .font(.callout)
648
+ .foregroundStyle(.secondary)
649
+ .multilineTextAlignment(.center)
650
+ }
651
+ .padding(32)
652
+ .navigationTitle("Preview")
653
+ }
654
+ }
@@ -361,7 +361,6 @@
361
361
  buildSettings = {
362
362
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
363
363
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
364
- CODE_SIGN_STYLE = Automatic;
365
364
  GENERATE_INFOPLIST_FILE = YES;
366
365
  INFOPLIST_FILE = App/Info.plist;
367
366
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -386,7 +385,6 @@
386
385
  buildSettings = {
387
386
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
388
387
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
389
- CODE_SIGN_STYLE = Automatic;
390
388
  GENERATE_INFOPLIST_FILE = YES;
391
389
  INFOPLIST_FILE = App/Info.plist;
392
390
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -440,8 +438,8 @@
440
438
  isa = XCRemoteSwiftPackageReference;
441
439
  repositoryURL = "https://github.com/afomera/hotwire-native-ios";
442
440
  requirement = {
443
- branch = "fix/more-menu-navigator";
444
- kind = branch;
441
+ kind = revision;
442
+ revision = 915194a338d0a4c686ee43ace7450b3aa0e885c3;
445
443
  };
446
444
  };
447
445
  /* End XCRemoteSwiftPackageReference section */
@@ -5,7 +5,6 @@
5
5
  "kind" : "remoteSourceControl",
6
6
  "location" : "https://github.com/afomera/hotwire-native-ios",
7
7
  "state" : {
8
- "branch" : "fix/more-menu-navigator",
9
8
  "revision" : "915194a338d0a4c686ee43ace7450b3aa0e885c3"
10
9
  }
11
10
  }
@@ -42,12 +42,19 @@ recommitting — never in a stamped copy.
42
42
 
43
43
  ## SPM pin policy
44
44
 
45
- The project depends on one package:
46
- `https://github.com/hotwired/hotwire-native-ios`, `upToNextMajorVersion` from
47
- `1.3.0`. The committed
48
- `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved` pins
49
- the exact 1.3.x version used for builds; regenerating it (a deliberate act) is
50
- how the pin moves.
45
+ The project depends on one remote package: the fork
46
+ `https://github.com/afomera/hotwire-native-ios`, pinned to an **exact revision**
47
+ (`kind = revision` in the pbxproj `XCRemoteSwiftPackageReference` requirement).
48
+ A revision pin resolves offline from an existing SPM clone; a branch pin makes
49
+ every build re-check the remote, which is why the template does not use one.
50
+
51
+ Moving the pin is a deliberate two-file edit, and both files must carry the
52
+ same SHA:
53
+
54
+ 1. `App.xcodeproj/project.pbxproj` — the `requirement` dict (`kind = revision;`
55
+ / `revision = <40-char sha>;`)
56
+ 2. `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved` —
57
+ the pin's `state.revision` (no `branch` key)
51
58
 
52
59
  ## Building standalone
53
60
 
@@ -94,6 +94,9 @@ if [ -n "${NOTARY_KEY:-}" ]; then
94
94
  elif [ -n "${NOTARY_PROFILE:-}" ]; then
95
95
  xcrun notarytool submit "$ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
96
96
  else
97
+ # Last resort. --password puts the app-specific password on the command line,
98
+ # where any process on the machine can read it out of `ps` — prefer NOTARY_KEY
99
+ # (API key, above) or NOTARY_PROFILE, neither of which passes a secret in argv.
97
100
  # Accept APPLE_APP_SPECIFIC_PASSWORD as an alias for APPLE_PASSWORD.
98
101
  APPLE_PASSWORD="${APPLE_PASSWORD:-${APPLE_APP_SPECIFIC_PASSWORD:-}}"
99
102
  : "${APPLE_ID:?set APPLE_ID or NOTARY_PROFILE}"
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.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0.15'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rqrcode
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
68
82
  description: RubyEverywhere gives a Rails (or Sinatra, or Hanami) app a native life
69
83
  on desktop, iOS, and Android. Desktop is a Tauri shell — in local mode the whole
70
84
  app is pressed into a single-file binary with Tebako and runs on-device with no
@@ -100,10 +114,14 @@ files:
100
114
  - lib/everywhere/blake2b.rb
101
115
  - lib/everywhere/boot.rb
102
116
  - lib/everywhere/builders/android.rb
117
+ - lib/everywhere/builders/base.rb
103
118
  - lib/everywhere/builders/desktop.rb
104
119
  - lib/everywhere/builders/ios.rb
120
+ - lib/everywhere/builders/native_sources.rb
105
121
  - lib/everywhere/child_processes.rb
122
+ - lib/everywhere/child_supervision.rb
106
123
  - lib/everywhere/cli.rb
124
+ - lib/everywhere/clock.rb
107
125
  - lib/everywhere/commands/build.rb
108
126
  - lib/everywhere/commands/clean.rb
109
127
  - lib/everywhere/commands/dev.rb
@@ -116,42 +134,63 @@ files:
116
134
  - lib/everywhere/commands/platform/login.rb
117
135
  - lib/everywhere/commands/platform/logout.rb
118
136
  - lib/everywhere/commands/platform/runner.rb
137
+ - lib/everywhere/commands/preview.rb
119
138
  - lib/everywhere/commands/publish.rb
120
139
  - lib/everywhere/commands/release.rb
121
140
  - lib/everywhere/commands/shell_dir.rb
122
141
  - lib/everywhere/commands/updates_keygen.rb
123
142
  - lib/everywhere/config.rb
143
+ - lib/everywhere/config/app.rb
144
+ - lib/everywhere/config/auth.rb
145
+ - lib/everywhere/config/data.rb
146
+ - lib/everywhere/config/deep_linking.rb
147
+ - lib/everywhere/config/desktop_ui.rb
148
+ - lib/everywhere/config/mobile.rb
149
+ - lib/everywhere/config/native_desktop.rb
150
+ - lib/everywhere/config/native_mobile.rb
151
+ - lib/everywhere/config/shell.rb
152
+ - lib/everywhere/config/updates.rb
124
153
  - lib/everywhere/console.rb
125
154
  - lib/everywhere/database.rb
126
155
  - lib/everywhere/desktop_assets.rb
156
+ - lib/everywhere/desktop_dev_app.rb
127
157
  - lib/everywhere/dock.rb
128
158
  - lib/everywhere/dock/footer.rb
129
159
  - lib/everywhere/dock/screen.rb
130
160
  - lib/everywhere/dock/state.rb
131
161
  - lib/everywhere/emulator.rb
132
162
  - lib/everywhere/engine.rb
163
+ - lib/everywhere/entrypoint.rb
164
+ - lib/everywhere/error.rb
133
165
  - lib/everywhere/fatal.rb
134
166
  - lib/everywhere/framework.rb
167
+ - lib/everywhere/host.rb
135
168
  - lib/everywhere/icon.rb
136
169
  - lib/everywhere/ignore.rb
170
+ - lib/everywhere/jump.rb
137
171
  - lib/everywhere/line_pump.rb
138
172
  - lib/everywhere/log_filter.rb
139
173
  - lib/everywhere/minisign.rb
140
174
  - lib/everywhere/mobile_config_endpoint.rb
141
175
  - lib/everywhere/mobile_configs_controller.rb
142
176
  - lib/everywhere/native_helper.rb
177
+ - lib/everywhere/native_platform.rb
143
178
  - lib/everywhere/omniauth.rb
144
179
  - lib/everywhere/paths.rb
145
180
  - lib/everywhere/platform/client.rb
146
181
  - lib/everywhere/platform/credentials.rb
147
182
  - lib/everywhere/platform/snapshot.rb
183
+ - lib/everywhere/plist.rb
148
184
  - lib/everywhere/png.rb
149
185
  - lib/everywhere/raster.rb
186
+ - lib/everywhere/raw_tty.rb
150
187
  - lib/everywhere/receipt.rb
151
188
  - lib/everywhere/relay.rb
152
189
  - lib/everywhere/s3.rb
190
+ - lib/everywhere/shell_pages.rb
153
191
  - lib/everywhere/shellout.rb
154
192
  - lib/everywhere/simulator.rb
193
+ - lib/everywhere/tab_filter.rb
155
194
  - lib/everywhere/task_pool.rb
156
195
  - lib/everywhere/ui.rb
157
196
  - lib/everywhere/update_manifest.rb