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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Framework

Returns a new instance of Framework.



42
43
44
# File 'lib/everywhere/framework.rb', line 42

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

Instance Attribute Details

#rootObject (readonly)

Returns the value of attribute root.



40
41
42
# File 'lib/everywhere/framework.rb', line 40

def root
  @root
end

Class Method Details

.detect(root = Dir.pwd) ⇒ Object

Raises:



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

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)


35
36
37
38
# File 'lib/everywhere/framework.rb', line 35

def self.gemfile_mentions?(root, gem_name)
  gemfile = File.join(root, "Gemfile")
  File.exist?(gemfile) && File.read(gemfile).match?(/gem ["']#{Regexp.escape(gem_name)}/)
end

.hanami?(root) ⇒ Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/everywhere/framework.rb', line 27

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

.rails?(root) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
25
# File 'lib/everywhere/framework.rb', line 22

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

.sinatra?(root) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/everywhere/framework.rb', line 31

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)


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

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.



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

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.



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

def env_exports = {}

#load_rack_appObject

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



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

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

#prepare_runtime_envObject

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



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

def prepare_runtime_env; end

#render_boot_stub(env, setup_line) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/everywhere/framework.rb', line 76

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.



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

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