Class: Everywhere::Framework

Inherits:
Object
  • Object
show all
Defined in:
lib/everywhere/framework.rb

Overview

Framework adapters keep both the CLI (dev/build) and the packaged runtime (boot) framework-agnostic. Each adapter owns two sides of one framework:

CLI side     — dev_command / precompile_* / cleanup_command (build.rb, dev.rb)
runtime side — prepare_runtime_env / boot! (boot.rb, inside the binary)

Rails keeps its own boot path (Rails::Command server, multi-db prepare_all). Sinatra and Hanami are plain Rack apps and share RackFramework's boot: load config.ru, prepare the database, serve it under Puma bound to localhost.

Defined Under Namespace

Classes: Hanami, RackFramework, Rails, Sinatra

Constant Summary collapse

GEMFILE_NAMES =

Bundler reads either name, and gem "rails" / gem("rails") are the same line to it. Anchored at the start of the line so a commented-out gem — the usual reason a Gemfile still mentions one — doesn't count as detection.

%w[Gemfile gems.rb].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Framework

Returns a new instance of Framework.



59
60
61
# File 'lib/everywhere/framework.rb', line 59

def initialize(root)
  @root = File.expand_path(root)
end

Instance Attribute Details

#rootObject (readonly)

Returns the value of attribute root.



57
58
59
# File 'lib/everywhere/framework.rb', line 57

def root
  @root
end

Class Method Details

.detect(root = Dir.pwd) ⇒ Object

Raises:



16
17
18
19
20
21
22
# File 'lib/everywhere/framework.rb', line 16

def self.detect(root = Dir.pwd)
  return Rails.new(root)   if rails?(root)
  return Hanami.new(root)  if hanami?(root)
  return Sinatra.new(root) if sinatra?(root)

  raise Error, "No supported framework detected in #{root} (looked for Rails, Hanami, Sinatra)"
end

.gemfile_mentions?(root, gem_name) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
45
46
47
48
# File 'lib/everywhere/framework.rb', line 42

def self.gemfile_mentions?(root, gem_name)
  pattern = /^\s*gem[\s(]+["']#{Regexp.escape(gem_name)}/
  GEMFILE_NAMES.any? do |name|
    path = File.join(root, name)
    File.exist?(path) && read_source(path).match?(pattern)
  end
end

.hanami?(root) ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/everywhere/framework.rb', line 29

def self.hanami?(root)
  File.exist?(File.join(root, "config", "app.rb")) && gemfile_mentions?(root, "hanami")
end

.rails?(root) ⇒ Boolean

Returns:

  • (Boolean)


24
25
26
27
# File 'lib/everywhere/framework.rb', line 24

def self.rails?(root)
  File.exist?(File.join(root, "config", "application.rb")) &&
    gemfile_mentions?(root, "rails")
end

.read_source(path) ⇒ Object

Gemfiles are UTF-8, but the process may be running under LANG=C, where File.read tags the bytes as US-ASCII and any accented comment makes the match raise instead of answering.



53
54
55
# File 'lib/everywhere/framework.rb', line 53

def self.read_source(path)
  File.read(path, encoding: "UTF-8").scrub
end

.sinatra?(root) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/everywhere/framework.rb', line 33

def self.sinatra?(root)
  File.exist?(File.join(root, "config.ru")) && gemfile_mentions?(root, "sinatra")
end

Instance Method Details

#boot!(port:) ⇒ Object

Load the app, prepare its database, and start the server bound to 127.0.0.1:port. Blocks. Implemented per framework.

Raises:

  • (NotImplementedError)


70
71
72
# File 'lib/everywhere/framework.rb', line 70

def boot!(port:)
  raise NotImplementedError, "#{name} has no packaged boot path yet"
end

#boot_stubObject

The native_boot.rb entry point every install writes. Frameworks differ in which env they set and how the bundle is loaded before the gem.



86
87
88
# File 'lib/everywhere/framework.rb', line 86

def boot_stub
  render_boot_stub(env_exports, "require \"bundler/setup\"")
end

#env_exportsObject

Env each framework needs set before the app loads, beyond NATIVE_PACKAGED.



91
# File 'lib/everywhere/framework.rb', line 91

def env_exports = {}

#load_rack_appObject

Build the Rack app from config.ru. Rack 3 returns the app; Rack 2 returned [app, options].



76
77
78
79
80
# File 'lib/everywhere/framework.rb', line 76

def load_rack_app
  require "rack"
  built = ::Rack::Builder.parse_file(File.join(root, "config.ru"))
  built.is_a?(Array) ? built.first : built
end

#precompile?Boolean

Whether the build must run anything against the app's bundle on the HOST Ruby (asset precompile). Sinatra's no-op precompile opts out.

Returns:

  • (Boolean)


95
# File 'lib/everywhere/framework.rb', line 95

def precompile? = precompile_command != "true"

#prepare_runtime_envObject

Set framework-specific env before the app loads. Called by Boot.



66
# File 'lib/everywhere/framework.rb', line 66

def prepare_runtime_env; end

#render_boot_stub(env, setup_line) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/everywhere/framework.rb', line 97

def render_boot_stub(env, setup_line)
  exports = ({ "NATIVE_PACKAGED" => "1" }.merge(env))
    .map { |k, v| "ENV[#{k.inspect}] ||= #{v.inspect}" }.join("\n")
  <<~RUBY
    #!/usr/bin/env ruby
    # frozen_string_literal: true

    # RubyEverywhere packaged-app entry point (generated by `every install`).
    # Keep this thin: the real boot lives in the ruby_everywhere gem.
    #{exports}

    #{setup_line}
    require "ruby_everywhere"

    Everywhere.boot!(root: __dir__)
  RUBY
end

#serve_rack(app, port:) ⇒ Object

Serve a Rack app under Puma on localhost. Keep-alives are disabled: WebKit (the webview) reuses connections Puma has already closed, which shows up as intermittent "network connection was lost" asset failures.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/everywhere/framework.rb', line 118

def serve_rack(app, port:)
  require "puma"
  require "puma/configuration"
  require "puma/launcher"

  configuration = ::Puma::Configuration.new do |c|
    c.bind "tcp://127.0.0.1:#{port}"
    c.app app
    c.environment ENV.fetch("RACK_ENV", "production")
    c.workers 0
    c.threads 1, 5
    c.enable_keep_alives false if c.respond_to?(:enable_keep_alives)
  end
  ::Puma::Launcher.new(configuration).run
end