Class: Everywhere::Config
- Inherits:
-
Object
- Object
- Everywhere::Config
- Defined in:
- lib/everywhere/config.rb
Overview
Loads config/everywhere.yml:
app:
name: My Really Awesome App
entry_path: /dashboard # initial url on app launch
appearance:
tint_color: "#CC342D" # a hex string...
background_color: # ...or split by system theme
light: "#FAF9F7"
dark: "#1C1B1A"
Colors always normalize to { "light" => ..., "dark" => ... }.
Constant Summary collapse
- FILE =
File.join("config", "everywhere.yml")
- DEFAULT_TAB_ICONS =
{ "ios" => "circle", "android" => "circle" }.freeze
- MOBILE_PATH_RULES =
The Hotwire Native path-configuration rules every RubyEverywhere mobile shell starts from. Single source of truth: the builder bakes this into the app bundle and MobileConfigEndpoint serves it live.
[ { "patterns" => [".*"], "properties" => { "context" => "default", "pull_to_refresh_enabled" => true } }, { "patterns" => ["/new$", "/edit$"], "properties" => { "context" => "modal", "pull_to_refresh_enabled" => false } } ].freeze
- MOBILE_PERMISSIONS =
Native permissions the app declares (top-level
permissions:). Only declared permissions can be requested at runtime — the shell answersundeclaredfor everything else, so an app that doesn't need a permission can never prompt for it. For camera and location the value is the user-facing usage string iOS shows in the permission prompt (the "why"); it's mandatory there because requesting without one crashes the app. Notifications need no string — declare withtrue.permissions: notifications: true camera: "Scan QR codes to pair devices." location: ios: "Find build agents near you." biometrics: "Unlock your account with Face ID."Returns { name => { "ios" => usage-or-nil, ... } }.
%w[notifications camera location biometrics].freeze
- IOS_USAGE_KEYS =
Info.plist usage-description keys per permission. Presence here makes the usage string mandatory on iOS.
{ "camera" => "NSCameraUsageDescription", "location" => "NSLocationWhenInUseUsageDescription", "biometrics" => "NSFaceIDUsageDescription" }.freeze
- PACKAGE_REQUIREMENT_KEYS =
Third-party Swift packages this app pins for its native/ios code. Each entry is one SPM dependency;
every buildwrites them into the shell's local NativeExtensions package and re-exports every product, so native/ios code reaches them with a singleimport NativeExtensions.packages: - url: https://github.com/simibac/ConfettiSwiftUI from: "1.1.0" # or exact: / branch: / revision: products: [ConfettiSwiftUI] # optional; defaults to the repo nameReturns normalized entries: { "url", "requirement" => { "kind", "value" }, "products" => [...] }. Assumes the config validated clean (see native_ios_package_errors) — the requirement is always present here.
%w[from exact branch revision].freeze
- SWIFT_TYPE_NAME =
Everything declared here is interpolated into generated Swift, so names are validated hard: type names must be plain Swift identifiers, screen identifiers must be safe inside a Swift string literal.
/\A[A-Za-z_][A-Za-z0-9_]*\z/- SCREEN_IDENTIFIER =
/\A[A-Za-z0-9_-]+\z/- PACKAGE_URL =
A URL SPM accepts (https for the common case, git@ for SSH remotes) and a loose semver for from:/exact: — enough to catch typos without reimplementing SPM's resolver, which reports anything subtler at build time.
%r{\A(https?://|git@).+}- PACKAGE_VERSION =
/\A\d+(\.\d+){0,2}(-[0-9A-Za-z.-]+)?\z/
Instance Attribute Summary collapse
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
- .load(root = Dir.pwd) ⇒ Object
-
.os_of(target) ⇒ Object
Every target (os-arch string) resolves to a single platform (its os), and a platform may override the app-level identity/version keys — see
platforms:below.
Instance Method Summary collapse
-
#apple_app_site_association ⇒ Object
The apple-app-site-association document (a Hash), or nil when no Apple app ids are configured.
- #apple_app_site_association_json ⇒ Object
-
#asset_links ⇒ Object
The Digital Asset Links document (an Array), or nil without an Android app.
- #asset_links_json ⇒ Object
- #background_color ⇒ Object
-
#build ⇒ Object
The
build:section — durable build knobs that were onceevery buildflags (ruby, targets, permissions). -
#build_ruby ⇒ Object
Ruby version to package.
-
#bundle_id(target: nil) ⇒ Object
Reverse-DNS identifier: macOS bundle id, and the name of the per-user app-data directory on every platform.
-
#deep_linking ⇒ Object
Deep linking / universal links.
- #deep_linking? ⇒ Boolean
- #deep_linking_android ⇒ Object
- #deep_linking_android? ⇒ Boolean
- #deep_linking_android_fingerprints ⇒ Object
- #deep_linking_android_package ⇒ Object
- #deep_linking_apple? ⇒ Boolean
-
#deep_linking_apple_app_ids ⇒ Object
The iOS/tvOS/...
-
#deep_linking_domains ⇒ Object
Domains for the iOS Associated Domains entitlement (applinks:
) and the shell's universal-link host allowlist: the remote host plus any extra domains:. -
#deep_linking_paths ⇒ Object
URL path patterns that open the app.
- #entry_path ⇒ Object
-
#icon ⇒ Object
App icon source: a PNG (ideally square, 1024px+).
-
#initialize(data, root) ⇒ Config
constructor
A new instance of Config.
-
#menu ⇒ Object
Custom native menu items (macOS app menu).
-
#mobile_rules ⇒ Object
App-declared Hotwire Native path-configuration rules (everywhere.yml top-level
rules:), appended after MOBILE_PATH_RULES — later rules win per property, so apps can make routes modal, disable pull-to-refresh, or set any other Hotwire path property without touching native code:. -
#mode ⇒ Object
"local" — the app is tebako-pressed and runs on-device (default) "remote" — thin shell around an already-deployed app: no press, no sidecar.
- #name(target: nil) ⇒ Object
-
#native_ios ⇒ Object
Native code extensions ("supernative"): Swift the app repo carries in native/ios/ that
every buildcompiles into the shell, plus this declaration of what to hook up. - #native_ios? ⇒ Boolean
- #native_ios_components ⇒ Object
- #native_ios_errors ⇒ Object
-
#native_ios_lazy_load_tabs ⇒ Object
Whether the native tab bar defers each non-selected tab's initial visit until the tab is first selected (Hotwire Native
lazyLoadTabs). - #native_ios_package_errors ⇒ Object
- #native_ios_packages ⇒ Object
- #native_ios_packages? ⇒ Boolean
- #native_ios_screens ⇒ Object
- #native_ios_splash ⇒ Object
-
#native_ios_splash_min_seconds ⇒ Object
Minimum seconds a custom splash stays on screen (default 1.0 shell-side; a fast server would otherwise dismiss a branded splash in a ~50ms flash).
-
#package_identity(url) ⇒ Object
SPM's package identity: the URL's last path segment without a .git suffix (github.com/simibac/ConfettiSwiftUI → "ConfettiSwiftUI").
-
#path_configuration_hash(os, tabs: nil) ⇒ Object
The full path-configuration document for one mobile platform — rules plus our settings (tabs live in settings, per Hotwire Native convention).
- #path_configuration_json(os, tabs: nil) ⇒ Object
-
#permission_errors(os) ⇒ Object
Problems with the permissions declaration for one platform, as human-readable strings — unknown names, and camera/location missing the mandatory usage string.
-
#permissions ⇒ Object
OS-integration permissions the app declares.
- #remote? ⇒ Boolean
-
#remote_instances? ⇒ Boolean
Multi-instance apps (
remote.instances: true): the mobile shell boots into remote.url — a hosted instance-picker page — and lets that page re-root the app onto a chosen instance via Everywhere.instance.set, persisted across launches until Everywhere.instance.clear. - #remote_url ⇒ Object
-
#splash ⇒ Object
Optional custom splash page (HTML file, path relative to the app root).
-
#tabs ⇒ Object
Mobile tab bar, platform-neutral.
-
#tabs_for(os) ⇒ Object
Tabs with the icon resolved for one platform: icons.
, then the shared icon, then a safe default. -
#targets ⇒ Object
Build targets (os-arch strings) — the CI matrix, expressed once.
- #tint_color ⇒ Object
-
#to_shell_hash(target: nil) ⇒ Object
The subset the shell needs, shipped as JSON (env var in dev, Resources/everywhere.json inside a bundled .app).
- #to_shell_json(target: nil) ⇒ Object
-
#tray ⇒ Object
System tray: same entry shape as
menu:plusaction: quit | show. -
#updates ⇒ Object
The
updates:section — self-hosted auto-updates. -
#updates_auto ⇒ Object
off — never check; check — check + notify (default); download — also fetch/verify in the background; install — silent auto-update.
- #updates_channel ⇒ Object
- #updates_interval ⇒ Object
- #updates_public_key ⇒ Object
- #updates_s3 ⇒ Object
-
#updates_shell_hash ⇒ Object
The client subset for the shell; nil (and thus absent from everywhere.json) until both url and public_key are configured — an unsigned update feed is not a thing.
- #updates_url ⇒ Object
-
#version(target: nil) ⇒ Object
Marketing/display version of the app (CFBundleShortVersionString, and the
versionrecorded in the build receipt).
Constructor Details
Instance Attribute Details
#root ⇒ Object (readonly)
Returns the value of attribute root.
83 84 85 |
# File 'lib/everywhere/config.rb', line 83 def root @root end |
Class Method Details
.load(root = Dir.pwd) ⇒ Object
77 78 79 80 81 |
# File 'lib/everywhere/config.rb', line 77 def self.load(root = Dir.pwd) path = File.join(root, FILE) data = File.exist?(path) ? YAML.safe_load_file(path, aliases: true) || {} : {} new(data, root) end |
.os_of(target) ⇒ Object
Every target (os-arch string) resolves to a single platform (its os), and
a platform may override the app-level identity/version keys — see
platforms: below. Pass target: to any of these accessors to apply the
matching override; omit it for the shared app-level value.
94 |
# File 'lib/everywhere/config.rb', line 94 def self.os_of(target) = target.to_s.split("-", 2).first |
Instance Method Details
#apple_app_site_association ⇒ Object
The apple-app-site-association document (a Hash), or nil when no Apple app
ids are configured. applinks for universal links; webcredentials so
associated-domains password autofill / Face ID credentials work too.
626 627 628 629 630 631 632 633 634 |
# File 'lib/everywhere/config.rb', line 626 def apple_app_site_association ids = deep_linking_apple_app_ids return nil if ids.empty? { "applinks" => { "details" => [{ "appIDs" => ids, "components" => deep_linking_paths.map { |p| { "/" => p } } }] }, "webcredentials" => { "apps" => ids } } end |
#apple_app_site_association_json ⇒ Object
636 637 638 639 640 |
# File 'lib/everywhere/config.rb', line 636 def apple_app_site_association_json require "json" doc = apple_app_site_association or return nil JSON.generate(doc) end |
#asset_links ⇒ Object
The Digital Asset Links document (an Array), or nil without an Android app.
643 644 645 646 647 648 649 650 |
# File 'lib/everywhere/config.rb', line 643 def asset_links return nil unless deep_linking_android? [{ "relation" => ["delegate_permission/common.handle_all_urls"], "target" => { "namespace" => "android_app", "package_name" => deep_linking_android_package, "sha256_cert_fingerprints" => deep_linking_android_fingerprints } }] end |
#asset_links_json ⇒ Object
652 653 654 655 656 |
# File 'lib/everywhere/config.rb', line 652 def asset_links_json require "json" doc = asset_links or return nil JSON.generate(doc) end |
#background_color ⇒ Object
218 219 220 |
# File 'lib/everywhere/config.rb', line 218 def background_color normalize_color(appearance["background_color"]) end |
#build ⇒ Object
The build: section — durable build knobs that were once every build
flags (ruby, targets, permissions). CLI flags still override per-run.
See platform/docs/build-engine.md §2.
144 |
# File 'lib/everywhere/config.rb', line 144 def build = @data.fetch("build", nil) || {} |
#build_ruby ⇒ Object
Ruby version to package. nil here means "let the CLI decide its default".
147 |
# File 'lib/everywhere/config.rb', line 147 def build_ruby = build["ruby"] |
#bundle_id(target: nil) ⇒ Object
Reverse-DNS identifier: macOS bundle id, and the name of the per-user
app-data directory on every platform. Set it explicitly and never change
it — renaming moves users' data. A platform may override it (e.g. the App
Store often wants com.example.app.ios).
104 105 106 107 |
# File 'lib/everywhere/config.rb', line 104 def bundle_id(target: nil) resolved(target)["bundle_id"] || "com.rubyeverywhere.#{name(target: target).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")}" end |
#deep_linking ⇒ Object
Deep linking / universal links. Declares the app's association with its web domains so the OS can hand matching URLs to the app instead of the browser. The gem serves the two well-known association files this needs — /.well-known/apple-app-site-association and /.well-known/assetlinks.json — generated from this config (Everywhere::Engine in Rails; MobileConfigEndpoint for Sinatra/Hanami). The iOS builder stamps the matching Associated Domains entitlement, and the shell routes an incoming link to its path.
deep_linking:
team_id: ABCDE12345 # derives the iOS app id from bundle_id
# or set app ids explicitly:
# apple_app_id: ABCDE12345.com.example.app
# apple_app_ids: ["ABCDE12345.com.example.app"]
paths: ["/*"] # which paths open the app (default: all)
domains: ["www.example.com"] # extra applinks: domains (remote host is implicit)
android:
package: com.example.app
sha256_cert_fingerprints: ["AB:CD:EF:..."]
576 |
# File 'lib/everywhere/config.rb', line 576 def deep_linking = @data.fetch("deep_linking", nil) || {} |
#deep_linking? ⇒ Boolean
606 |
# File 'lib/everywhere/config.rb', line 606 def deep_linking? = deep_linking_apple? || deep_linking_android? |
#deep_linking_android ⇒ Object
595 |
# File 'lib/everywhere/config.rb', line 595 def deep_linking_android = deep_linking["android"].is_a?(Hash) ? deep_linking["android"] : {} |
#deep_linking_android? ⇒ Boolean
602 |
# File 'lib/everywhere/config.rb', line 602 def deep_linking_android? = !deep_linking_android_package.to_s.empty? && !deep_linking_android_fingerprints.empty? |
#deep_linking_android_fingerprints ⇒ Object
598 599 600 |
# File 'lib/everywhere/config.rb', line 598 def deep_linking_android_fingerprints Array(deep_linking_android["sha256_cert_fingerprints"]).map { |f| f.to_s.strip }.reject(&:empty?) end |
#deep_linking_android_package ⇒ Object
596 |
# File 'lib/everywhere/config.rb', line 596 def deep_linking_android_package = deep_linking_android["package"]&.to_s |
#deep_linking_apple? ⇒ Boolean
604 |
# File 'lib/everywhere/config.rb', line 604 def deep_linking_apple? = !deep_linking_apple_app_ids.empty? |
#deep_linking_apple_app_ids ⇒ Object
The iOS/tvOS/... app identifiers (TeamID.bundleID) the association file advertises. Explicit ids win; otherwise team_id + the app's iOS bundle id.
580 581 582 583 584 585 586 |
# File 'lib/everywhere/config.rb', line 580 def deep_linking_apple_app_ids explicit = Array(deep_linking["apple_app_ids"]) + [deep_linking["apple_app_id"]].compact return explicit.map(&:to_s).uniq unless explicit.empty? team = deep_linking["team_id"].to_s.strip team.empty? ? [] : ["#{team}.#{bundle_id(target: "ios")}"] end |
#deep_linking_domains ⇒ Object
Domains for the iOS Associated Domains entitlement (applinks:domains:. Bare hostnames (no scheme, no path).
611 612 613 614 615 616 617 618 619 620 621 |
# File 'lib/everywhere/config.rb', line 611 def deep_linking_domains hosts = [] if remote_url require "uri" hosts << URI(remote_url).host end hosts += Array(deep_linking["domains"]).map { |d| d.to_s.sub(%r{\Ahttps?://}, "").sub(%r{/.*\z}, "") } hosts.compact.reject(&:empty?).uniq rescue URI::InvalidURIError Array(deep_linking["domains"]).map(&:to_s).reject(&:empty?).uniq end |
#deep_linking_paths ⇒ Object
URL path patterns that open the app. Modern (iOS 14+) component form; defaults to every path.
590 591 592 593 |
# File 'lib/everywhere/config.rb', line 590 def deep_linking_paths paths = Array(deep_linking["paths"]).map(&:to_s).reject(&:empty?) paths.empty? ? ["*"] : paths end |
#entry_path ⇒ Object
128 129 130 131 |
# File 'lib/everywhere/config.rb', line 128 def entry_path path = app["entry_path"] || "/" path.start_with?("/") ? path : "/#{path}" end |
#icon ⇒ Object
App icon source: a PNG (ideally square, 1024px+). Explicit app.icon
path relative to the app root, or icon.png at the root by convention.
111 112 113 114 115 116 117 118 |
# File 'lib/everywhere/config.rb', line 111 def icon if (explicit = app["icon"]) File.(explicit, root) else default = File.join(root, "icon.png") File.exist?(default) ? default : nil end end |
#menu ⇒ Object
Custom native menu items (macOS app menu). Each entry: label + path (+ optional accelerator), or "separator". Clicks arrive in the page as "everywhere:menu" events and navigate via Turbo.
520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/everywhere/config.rb', line 520 def entries = @data["menu"] return [] unless entries.is_a?(Array) entries.filter_map do |item| if item == "separator" || (item.is_a?(Hash) && item["separator"]) { "separator" => true } elsif item.is_a?(Hash) && item["label"] && item["path"] { "label" => item["label"], "path" => item["path"], "accelerator" => item["accelerator"] }.compact end end end |
#mobile_rules ⇒ Object
App-declared Hotwire Native path-configuration rules (everywhere.yml
top-level rules:), appended after MOBILE_PATH_RULES — later rules win
per property, so apps can make routes modal, disable pull-to-refresh,
or set any other Hotwire path property without touching native code:
rules:
- patterns: ["/preferences$"]
properties:
context: default # not a modal, unlike other /edit routes
- patterns: ["/live/"]
properties:
pull_to_refresh_enabled: false
342 343 344 345 346 347 348 349 350 351 |
# File 'lib/everywhere/config.rb', line 342 def mobile_rules entries = @data["rules"] return [] unless entries.is_a?(Array) entries.filter_map do |rule| next unless rule.is_a?(Hash) && rule["patterns"].is_a?(Array) && rule["properties"].is_a?(Hash) { "patterns" => rule["patterns"].map(&:to_s), "properties" => rule["properties"] } end end |
#mode ⇒ Object
"local" — the app is tebako-pressed and runs on-device (default) "remote" — thin shell around an already-deployed app: no press, no sidecar
158 159 160 |
# File 'lib/everywhere/config.rb', line 158 def mode app["mode"] || (remote_url ? "remote" : "local") end |
#name(target: nil) ⇒ Object
96 97 98 |
# File 'lib/everywhere/config.rb', line 96 def name(target: nil) resolved(target)["name"] || File.basename(File.(root)).split(/[-_]/).map(&:capitalize).join(" ") end |
#native_ios ⇒ Object
Native code extensions ("supernative"): Swift the app repo carries in
native/ios/ that every build compiles into the shell, plus this
declaration of what to hook up. Build-time only — stores forbid
downloading native code, so extensions ship with the app binary.
native:
ios:
components: [ChartComponent] # BridgeComponent subclasses to register
screens: # path rules with view_controller: <id>
map: MapScreen # SwiftUI View or UIViewController, init(url:)
splash: LaunchSplash # SwiftUI View or UIViewController, init()
splash_min_seconds: 1.0 # how long the custom splash stays up at minimum
lazy_load_tabs: true # defer each tab's first visit until it's selected
383 384 385 386 |
# File 'lib/everywhere/config.rb', line 383 def native_ios section = @data.dig("native", "ios") section.is_a?(Hash) ? section : {} end |
#native_ios? ⇒ Boolean
454 |
# File 'lib/everywhere/config.rb', line 454 def native_ios? = !(native_ios_components.empty? && native_ios_screens.empty? && native_ios_splash.nil?) |
#native_ios_components ⇒ Object
388 |
# File 'lib/everywhere/config.rb', line 388 def native_ios_components = Array(native_ios["components"]).map(&:to_s) |
#native_ios_errors ⇒ Object
462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/everywhere/config.rb', line 462 def native_ios_errors types = native_ios_components + native_ios_screens.values + [native_ios_splash].compact errors = types.reject { |t| t.match?(SWIFT_TYPE_NAME) }.map do |t| "native.ios: #{t.inspect} is not a Swift type name (letters, digits, _)" end errors += native_ios_screens.keys.reject { |id| id.match?(SCREEN_IDENTIFIER) }.map do |id| "native.ios.screens: identifier #{id.inspect} must match #{SCREEN_IDENTIFIER.inspect}" end raw = native_ios["splash_min_seconds"] errors << "native.ios.splash_min_seconds must be a number (seconds)" if raw && !raw.is_a?(Numeric) errors + native_ios_package_errors end |
#native_ios_lazy_load_tabs ⇒ Object
Whether the native tab bar defers each non-selected tab's initial visit
until the tab is first selected (Hotwire Native lazyLoadTabs). Tri-state
so apps can opt in or opt out explicitly: true lazy-loads, false loads
every tab up front, and an absent key leaves the shell on its default
(eager).
412 413 414 415 |
# File 'lib/everywhere/config.rb', line 412 def native_ios_lazy_load_tabs value = native_ios["lazy_load_tabs"] value if value == true || value == false end |
#native_ios_package_errors ⇒ Object
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 |
# File 'lib/everywhere/config.rb', line 482 def native_ios_package_errors raw = native_ios["packages"] return [] if raw.nil? return ["native.ios.packages must be a list"] unless raw.is_a?(Array) raw.each_with_index.flat_map do |entry, i| at = "native.ios.packages[#{i}]" next ["#{at} must be a mapping with a url:"] unless entry.is_a?(Hash) errors = [] url = entry["url"].to_s.strip if url.empty? errors << "#{at}.url is required" elsif !url.match?(PACKAGE_URL) errors << "#{at}.url #{url.inspect} must be an https:// or git@ URL" end present = PACKAGE_REQUIREMENT_KEYS.select { |k| entry.key?(k) && !entry[k].to_s.strip.empty? } if present.empty? errors << "#{at} needs one version requirement (#{PACKAGE_REQUIREMENT_KEYS.join(" / ")})" elsif present.length > 1 errors << "#{at} sets conflicting requirements (#{present.join(", ")}) — pick one" elsif %w[from exact].include?(present.first) && !entry[present.first].to_s.strip.match?(PACKAGE_VERSION) errors << "#{at}.#{present.first} #{entry[present.first].to_s.inspect} must be a version like \"1.2.0\"" end Array(entry["products"]).each do |product| next if product.to_s.match?(SWIFT_TYPE_NAME) errors << "#{at}.products: #{product.to_s.inspect} is not a module name (letters, digits, _)" end errors end end |
#native_ios_packages ⇒ Object
432 433 434 435 436 437 438 439 440 441 442 443 444 445 |
# File 'lib/everywhere/config.rb', line 432 def native_ios_packages Array(native_ios["packages"]).filter_map do |entry| next unless entry.is_a?(Hash) url = entry["url"].to_s.strip kind = PACKAGE_REQUIREMENT_KEYS.find { |k| entry.key?(k) && !entry[k].to_s.strip.empty? } products = Array(entry["products"]).map { |p| p.to_s.strip }.reject(&:empty?) products = [package_identity(url)] if products.empty? { "url" => url, "requirement" => (kind && { "kind" => kind, "value" => entry[kind].to_s.strip }), "products" => products } end end |
#native_ios_packages? ⇒ Boolean
447 |
# File 'lib/everywhere/config.rb', line 447 def native_ios_packages? = !native_ios_packages.empty? |
#native_ios_screens ⇒ Object
390 391 392 393 394 395 |
# File 'lib/everywhere/config.rb', line 390 def native_ios_screens entries = native_ios["screens"] return {} unless entries.is_a?(Hash) entries.to_h { |id, type| [id.to_s, type.to_s] } end |
#native_ios_splash ⇒ Object
397 |
# File 'lib/everywhere/config.rb', line 397 def native_ios_splash = native_ios["splash"]&.to_s |
#native_ios_splash_min_seconds ⇒ Object
Minimum seconds a custom splash stays on screen (default 1.0 shell-side; a fast server would otherwise dismiss a branded splash in a ~50ms flash). Clamped to the shell's 8s give-up timeout.
402 403 404 405 |
# File 'lib/everywhere/config.rb', line 402 def native_ios_splash_min_seconds value = native_ios["splash_min_seconds"] value.to_f.clamp(0.0, 8.0) if value.is_a?(Numeric) end |
#package_identity(url) ⇒ Object
SPM's package identity: the URL's last path segment without a .git suffix
(github.com/simibac/ConfettiSwiftUI → "ConfettiSwiftUI"). Used as the
default product name and as the package: key in .product(name:package:).
452 |
# File 'lib/everywhere/config.rb', line 452 def package_identity(url) = File.basename(url.to_s.sub(%r{/+\z}, "")).sub(/\.git\z/, "") |
#path_configuration_hash(os, tabs: nil) ⇒ Object
The full path-configuration document for one mobile platform — rules plus
our settings (tabs live in settings, per Hotwire Native convention).
Pass tabs: to override the resolved list (the mobile config endpoint
passes a per-request-filtered list; build-time stamping omits it and
bakes them all).
358 359 360 361 362 363 |
# File 'lib/everywhere/config.rb', line 358 def path_configuration_hash(os, tabs: nil) settings = {} platform_tabs = tabs || tabs_for(os) settings["tabs"] = platform_tabs unless platform_tabs.empty? { "settings" => settings, "rules" => MOBILE_PATH_RULES + mobile_rules } end |
#path_configuration_json(os, tabs: nil) ⇒ Object
365 366 367 368 |
# File 'lib/everywhere/config.rb', line 365 def path_configuration_json(os, tabs: nil) require "json" JSON.generate(path_configuration_hash(os, tabs: tabs)) end |
#permission_errors(os) ⇒ Object
Problems with the permissions declaration for one platform, as human-readable strings — unknown names, and camera/location missing the mandatory usage string. Empty means buildable.
315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/everywhere/config.rb', line 315 def (os) .flat_map do |name, usage| unless MOBILE_PERMISSIONS.include?(name) next ["unknown permission #{name.inspect} — supported: #{MOBILE_PERMISSIONS.join(", ")}"] end if os == "ios" && IOS_USAGE_KEYS.key?(name) && usage["ios"].to_s.strip.empty? next ["#{name} needs a usage string — the sentence iOS shows when asking. " \ "In config/everywhere.yml:\n permissions:\n #{name}: \"Why the app needs #{name}.\""] end [] end end |
#permissions ⇒ Object
OS-integration permissions the app declares. Recorded in the receipt and (later) used to generate the shell's capabilities.
151 |
# File 'lib/everywhere/config.rb', line 151 def = Array(build["permissions"]) |
#remote? ⇒ Boolean
162 |
# File 'lib/everywhere/config.rb', line 162 def remote? = mode == "remote" |
#remote_instances? ⇒ Boolean
Multi-instance apps (remote.instances: true): the mobile shell boots
into remote.url — a hosted instance-picker page — and lets that page
re-root the app onto a chosen instance via Everywhere.instance.set,
persisted across launches until Everywhere.instance.clear. Opt-in
because it lets page JS repoint the whole app: apps that aren't
multi-instance shouldn't carry that surface.
174 175 176 |
# File 'lib/everywhere/config.rb', line 174 def remote_instances? @data.dig("remote", "instances") == true end |
#remote_url ⇒ Object
164 165 166 |
# File 'lib/everywhere/config.rb', line 164 def remote_url @data.dig("remote", "url")&.chomp("/") end |
#splash ⇒ Object
Optional custom splash page (HTML file, path relative to the app root). Shown while the packaged server boots; the shell injects window.EVERYWHERE_CONFIG so it can use the app's name and colors.
123 124 125 126 |
# File 'lib/everywhere/config.rb', line 123 def splash path = app["splash"] File.(path, root) if path end |
#tabs ⇒ Object
Mobile tab bar, platform-neutral. Icons are per-platform (ios = SF
Symbol, android = Material icon later), with a shared icon: fallback:
tabs:
- name: Builds
path: /builds
icons:
ios: hammer
android: build
Tabs ship two ways from the same source: baked into the app's bundled
path-configuration.json at build time (offline/first-launch), and served
live from /everywhere/
236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/everywhere/config.rb', line 236 def tabs entries = @data["tabs"] return [] unless entries.is_a?(Array) entries.filter_map do |tab| next unless tab.is_a?(Hash) && tab["name"] && tab["path"] path = tab["path"].start_with?("/") ? tab["path"] : "/#{tab["path"]}" { "name" => tab["name"], "path" => path, "icon" => tab["icon"], "icons" => (tab["icons"] if tab["icons"].is_a?(Hash)) }.compact end end |
#tabs_for(os) ⇒ Object
Tabs with the icon resolved for one platform: icons.
251 252 253 254 255 256 |
# File 'lib/everywhere/config.rb', line 251 def tabs_for(os) tabs.map do |tab| { "title" => tab["name"], "path" => tab["path"], "icon" => tab.dig("icons", os.to_s) || tab["icon"] || DEFAULT_TAB_ICONS[os.to_s] }.compact end end |
#targets ⇒ Object
Build targets (os-arch strings) — the CI matrix, expressed once.
154 |
# File 'lib/everywhere/config.rb', line 154 def targets = Array(build["targets"]) |
#tint_color ⇒ Object
214 215 216 |
# File 'lib/everywhere/config.rb', line 214 def tint_color normalize_color(appearance["tint_color"]) end |
#to_shell_hash(target: nil) ⇒ Object
The subset the shell needs, shipped as JSON (env var in dev,
Resources/everywhere.json inside a bundled .app). Pass target: so the
packaged config reflects the platform being built (its bundle id / name).
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 |
# File 'lib/everywhere/config.rb', line 661 def to_shell_hash(target: nil) { "name" => name(target: target), "bundle_id" => bundle_id(target: target), "version" => version(target: target), "mode" => mode, "remote_url" => remote_url, "remote_instances" => (true if remote_instances?), "entry_path" => entry_path, "tint_color" => tint_color, "background_color" => background_color, "menu" => ( unless .empty?), "tray" => (tray unless tray.empty?), "lazy_load_tabs" => native_ios_lazy_load_tabs, "splash_min_seconds" => native_ios_splash_min_seconds, "updates" => updates_shell_hash, "permissions" => (.keys unless .empty?), # Hosts the shell treats as its own for incoming universal links, so a # tapped link to any associated domain opens in-app rather than Safari. "universal_link_hosts" => (deep_linking_domains if deep_linking? && !deep_linking_domains.empty?) }.compact end |
#to_shell_json(target: nil) ⇒ Object
684 685 686 687 |
# File 'lib/everywhere/config.rb', line 684 def to_shell_json(target: nil) require "json" JSON.generate(to_shell_hash(target: target)) end |
#tray ⇒ Object
System tray: same entry shape as menu: plus action: quit | show.
tray:
- label: "Open My App"
action: show
- label: "New Note"
path: /notes/new
- separator
- label: Quit
action: quit
544 545 546 547 548 549 550 551 552 553 554 555 556 |
# File 'lib/everywhere/config.rb', line 544 def tray entries = @data["tray"] return [] unless entries.is_a?(Array) entries.filter_map do |item| if item == "separator" || (item.is_a?(Hash) && item["separator"]) { "separator" => true } elsif item.is_a?(Hash) && item["label"] && (item["path"] || item["action"]) { "label" => item["label"], "path" => item["path"], "action" => item["action"] }.compact end end end |
#updates ⇒ Object
The updates: section — self-hosted auto-updates. Two halves:
a client half (url/channel/public_key/auto/interval) that ships to the
shell, and a publish-side s3: half that NEVER leaves the dev machine.
channel here is a release channel (stable/beta) — not the receipt's
distribution_channel (direct vs app stores).
183 |
# File 'lib/everywhere/config.rb', line 183 def updates = @data.fetch("updates", nil) || {} |
#updates_auto ⇒ Object
off — never check; check — check + notify (default); download — also fetch/verify in the background; install — silent auto-update.
195 |
# File 'lib/everywhere/config.rb', line 195 def updates_auto = updates["auto"] || "check" |
#updates_channel ⇒ Object
187 |
# File 'lib/everywhere/config.rb', line 187 def updates_channel = updates["channel"] || "stable" |
#updates_interval ⇒ Object
197 |
# File 'lib/everywhere/config.rb', line 197 def updates_interval = [(updates["interval"] || 21_600).to_i, 300].max |
#updates_public_key ⇒ Object
191 |
# File 'lib/everywhere/config.rb', line 191 def updates_public_key = updates["public_key"] |
#updates_s3 ⇒ Object
185 |
# File 'lib/everywhere/config.rb', line 185 def updates_s3 = updates.fetch("s3", nil) || {} |
#updates_shell_hash ⇒ Object
The client subset for the shell; nil (and thus absent from everywhere.json) until both url and public_key are configured — an unsigned update feed is not a thing.
202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/everywhere/config.rb', line 202 def updates_shell_hash return nil unless updates_url && updates_public_key { "url" => updates_url, "channel" => updates_channel, "public_key" => updates_public_key, "auto" => updates_auto, "interval" => updates_interval } end |
#updates_url ⇒ Object
189 |
# File 'lib/everywhere/config.rb', line 189 def updates_url = updates["url"]&.chomp("/") |
#version(target: nil) ⇒ Object
Marketing/display version of the app (CFBundleShortVersionString, and the
version recorded in the build receipt). One shared app.version is the
default for every target; a platform may override it when a store forces a
different number. Defaults conservatively.
137 138 139 |
# File 'lib/everywhere/config.rb', line 137 def version(target: nil) resolved(target)["version"] || "0.1.0" end |