Skip to content

Commit

Permalink
Add enhanced default bin/dev
Browse files Browse the repository at this point in the history
Fixes rails#52459

The current default `bin/dev` execs the Rails server directly and
doesn't support Procfiles. Therefore, gems like `jsbundling-rails`,
`tailwindcss-rails`, and soon `solid_queue` need to overwrite the
`bin/dev` script with their own version.

With this new version, gems can skip overwriting the `bin/dev` script
and add to the Procfile instead.

It spawns Foreman in the background to allow breakpoints in the Rails
process without Foreman eating the inputs or interleaving its output.
  • Loading branch information
JoeDupuis committed Sep 25, 2024
1 parent fd975a8 commit b32547e
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 1 deletion.
92 changes: 92 additions & 0 deletions railties/lib/rails/commands/devserver/devserver_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

module Rails
module Command
class DevserverCommand < Base # :nodoc:
DEFAULT_PORT = 3000
PROCFILE = "Procfile.dev"

class_option :verbose, aliases: "-v", type: :boolean, default: false,
desc: "Show all process outputs."
option :port, aliases: "-p", type: :numeric,
desc: "Run Rails on the specified port - defaults to 3000.", banner: :port

desc "devserver", "Start everything"
def perform
exec_rails unless procfile_exists?

Signal.trap("INT") { }

install_foreman

begin
start_foreman
start_rails
ensure
stop
end
end

private
attr_reader :foreman_pgid, :pid

def start_foreman
@foreman_pgid =
Process.spawn(
env,
*%W(foreman start -f Procfile.dev -m all=1,web=0 --env /dev/null),
in: "/dev/null",
out: (verbose ? STDOUT : "/dev/null"),
err: (verbose ? STDOUT : "/dev/null"),
pgroup: true,
).then(&Process.method(:getpgid))
end

def install_foreman
if system(env, *%w(gem list --no-installed --exact --silent foreman))
puts "Installing foreman..."
system(env, *%w(gem install foreman))
end
end

def start_rails
@pid = Process.spawn(*%W(bundle exec rails server --port=#{port}))
Process.wait(pid)
@pid = false
end

def exec_rails
Process.exec(env, *%W(bundle exec rails server --port=#{port}))
end

def stop
Process.kill("INT", -foreman_pgid) if foreman_pgid
Process.wait(-foreman_pgid) if foreman_pgid

begin
Process.kill("KILL", pid) if pid
rescue Errno::ESRCH
end
end

def procfile_exists?
File.exist?(Rails::Command.application_root.join(PROCFILE))
end

def env
{
"BUNDLER_SETUP" => nil,
"RUBYOPT" => nil,
}
end

def verbose
options[:verbose]
end

def port
options[:port] || ENV.fetch("PORT", DEFAULT_PORT).to_i
end
end
end
end
37 changes: 36 additions & 1 deletion railties/lib/rails/generators/rails/app/templates/bin/dev.tt
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
exec "./bin/rails", "server", *ARGV
# !/usr/bin/env ruby

ENV["PORT"] ||= "3000"
VERBOSE = ARGV.include?("--verbose") || ARGV.include?("-v") || ENV["VERBOSE"]

PROCFILE = 'Procfile.dev'
Process.exec(*%w[bundle exec rails server], *ARGV) unless File.exist?(PROCFILE)

Signal.trap("INT") { }

if system(*%w[gem list --no-installed --exact --silent foreman])
puts "Installing foreman..."
system(*%w[gem install foreman])
end

begin
foreman_pid = Process.spawn(
*%w[foreman start -f Procfile.dev -m all=1,web=0 --env /dev/null],
in: "/dev/null",
out: (VERBOSE ? STDOUT : "/dev/null"),
err: (VERBOSE ? STDOUT : "/dev/null"),
pgroup: true,
)
foreman_pgid = Process.getpgid(foreman_pid)
pid = Process.spawn(*%w[bundle exec rails server])

Process.wait(pid)
pid = nil
ensure
Process.kill("INT", -foreman_pgid)
Process.wait(-foreman_pgid)
begin
Process.kill("KILL", pid) if pid
rescue Errno::ESRCH
end
end

0 comments on commit b32547e

Please sign in to comment.