Module: Everywhere::Config::App

Included in:
Everywhere::Config
Defined in:
lib/everywhere/config/app.rb

Constant Summary collapse

BUNDLE_ID =

A bundle id isn't only an identifier: it's a path segment (Paths.*_work_dir, which the builders rm_rf, and the update-signing key on disk). The derived default is safe by construction; an explicit one is whatever the app repo says, so it's held to reverse-DNS characters and nothing else.

/\A[A-Za-z0-9][A-Za-z0-9.-]*\z/

Instance Method Summary collapse

Instance Method Details

#android_target_sdkObject

platforms.android.target_sdk — the escape hatch for shipping ahead of a Google Play deadline that lands before our next release does. It is the ONLY SDK level an app may set, and deliberately not a general knob: targetSdk is a contract with the OS, not an app preference, and every behaviour change it opts into lands on the Kotlin in the frozen shell, which is only tested at AndroidSdk::TARGET_SDK. Setting it means "I accept untested OS behaviour".

compileSdk stays unexposed on purpose: from API 37 on it needs a matching compileSdkMinor and an AGP that knows the level, so moving it is a template change, never a config one.

nil when unset — the builder substitutes AndroidSdk::TARGET_SDK, so the default lives in one place rather than being duplicated here.



52
53
54
55
# File 'lib/everywhere/config/app.rb', line 52

def android_target_sdk
  raw = android_platform["target_sdk"]
  Integer(raw.to_s, exception: false) unless raw.nil?
end

#android_target_sdk_errorsObject

Raising it is the supported direction. Lowering is refused: the shipped Kotlin assumes the behaviour of the level it was built for, and a target under Play's floor is rejected at upload — the exact failure AndroidSdk::PLAY_TARGET_SDK_FLOOR exists to catch. Nothing caps the top end, because targeting a level above compileSdk is the whole point (and AGP allows it: the manifest claims the level, the code compiled against ours).



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/everywhere/config/app.rb', line 64

def android_target_sdk_errors
  raw = android_platform["target_sdk"]
  return [] if raw.nil?

  value = android_target_sdk
  tested = AndroidSdk::TARGET_SDK

  if value.nil?
    ["platforms.android.target_sdk #{raw.inspect} is not an API level — " \
     "a whole number like #{tested}"]
  elsif value < tested
    ["platforms.android.target_sdk #{value} is below the shell's tested level " \
     "(#{tested}) — this option only raises it, to ship ahead of a Play deadline"]
  else
    []
  end
end

#background_colorObject



177
178
179
# File 'lib/everywhere/config/app.rb', line 177

def background_color
  normalize_color(appearance["background_color"])
end

#buildObject

The build: section — durable build knobs that were once every build flags (ruby, targets, capabilities). CLI flags still override per-run. See platform/docs/build-engine.md §2.



117
# File 'lib/everywhere/config/app.rb', line 117

def build = @data.fetch("build", nil) || {}

#build_capabilitiesObject

Desktop OS-integration capabilities the app declares (build.capabilities), as a plain list. Deliberately not called permissions: the top-level permissions: below is the mobile prompt declaration — a different shape for a different consumer — and while both were spelled "permissions" this accessor was silently redefined by that one and could never be read.



127
# File 'lib/everywhere/config/app.rb', line 127

def build_capabilities = Array(build["capabilities"])

#build_rubyObject

Ruby version to package. nil here means "let the CLI decide its default".



120
# File 'lib/everywhere/config/app.rb', line 120

def build_ruby = build["ruby"]

#build_target_specsObject

The raw build.targets entries, suffixes intact ("ios-arm64:testflight").



137
# File 'lib/everywhere/config/app.rb', line 137

def build_target_specs = Array(build["targets"])

#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).



14
15
16
17
# File 'lib/everywhere/config/app.rb', line 14

def bundle_id(target: nil)
  resolved(target)["bundle_id"] ||
    "com.rubyeverywhere.#{name(target: target).downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-|-\z/, "")}"
end

#entry_pathObject



101
102
103
104
# File 'lib/everywhere/config/app.rb', line 101

def entry_path
  path = app["entry_path"] || "/"
  path.start_with?("/") ? path : "/#{path}"
end

#iconObject

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.



84
85
86
87
88
89
90
91
# File 'lib/everywhere/config/app.rb', line 84

def icon
  if (explicit = app["icon"])
    File.expand_path(explicit, root)
  else
    default = File.join(root, "icon.png")
    File.exist?(default) ? default : nil
  end
end

#identity_errorsObject



25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/everywhere/config/app.rb', line 25

def identity_errors
  sections = [["app", app]] + platforms.map { |os, override| ["platforms.#{os}", override] }
  sections.filter_map do |at, section|
    next unless section.is_a?(Hash)

    value = section["bundle_id"].to_s
    next if value.empty? || (value.match?(BUNDLE_ID) && !value.include?(".."))

    "#{at}.bundle_id #{value.inspect} is not a bundle id — reverse-DNS, " \
      "letters/digits/./- only (like com.example.app)"
  end + android_target_sdk_errors
end

#modeObject

"local" — the app is tebako-pressed and runs on-device (default) "remote" — thin shell around an already-deployed app: no press, no sidecar



141
142
143
# File 'lib/everywhere/config/app.rb', line 141

def mode
  app["mode"] || (remote_url ? "remote" : "local")
end

#name(target: nil) ⇒ Object



6
7
8
# File 'lib/everywhere/config/app.rb', line 6

def name(target: nil)
  resolved(target)["name"] || File.basename(File.expand_path(root)).split(/[-_]/).map(&:capitalize).join(" ")
end

#remote?Boolean

Returns:

  • (Boolean)


145
# File 'lib/everywhere/config/app.rb', line 145

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.

Returns:

  • (Boolean)


157
158
159
# File 'lib/everywhere/config/app.rb', line 157

def remote_instances?
  @data.dig("remote", "instances") == true
end

#remote_instances_escape?Boolean

Whether the shell offers its built-in escape hatch while re-rooted on an instance: a "back to start" toolbar action on screens where the tab bar (and the injected leave tab it carries) is hidden — every modal-context screen, and an instance serving no tabs at all. On by default because stranding is inherent to the picker pattern (a previewed app's whole signed-out surface can be modal); an instance app that wants no shell chrome opts out with remote: { instances_escape: false }.

Returns:

  • (Boolean)


169
170
171
# File 'lib/everywhere/config/app.rb', line 169

def remote_instances_escape?
  remote_instances? && @data.dig("remote", "instances_escape") != false
end

#remote_urlObject



147
148
149
# File 'lib/everywhere/config/app.rb', line 147

def remote_url
  @data.dig("remote", "url")&.chomp("/")
end

#splashObject

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.



96
97
98
99
# File 'lib/everywhere/config/app.rb', line 96

def splash
  path = app["splash"]
  File.expand_path(path, root) if path
end

#targetsObject

Build targets as BARE os-arch strings — any ":" suffix is stripped. Callers treat these as filesystem/URL-safe identifiers (every publish derives S3 update-manifest key paths from the first one), so a suffix leaking through here would poison those paths. Use #build_target_specs when the channel matters.



134
# File 'lib/everywhere/config/app.rb', line 134

def targets = build_target_specs.map { |t| t.to_s.split(":", 2).first }

#tint_colorObject



173
174
175
# File 'lib/everywhere/config/app.rb', line 173

def tint_color
  normalize_color(appearance["tint_color"])
end

#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.



110
111
112
# File 'lib/everywhere/config/app.rb', line 110

def version(target: nil)
  resolved(target)["version"] || "0.1.0"
end