spring 0.0.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +113 -0
  4. data/Gemfile +1 -3
  5. data/README.md +129 -187
  6. data/bin/spring +27 -0
  7. data/lib/spring/application.rb +121 -20
  8. data/lib/spring/application_manager.rb +21 -13
  9. data/lib/spring/client/binstub.rb +45 -41
  10. data/lib/spring/client/help.rb +4 -1
  11. data/lib/spring/client/rails.rb +8 -3
  12. data/lib/spring/client/run.rb +33 -10
  13. data/lib/spring/client/status.rb +2 -2
  14. data/lib/spring/client.rb +3 -3
  15. data/lib/spring/commands/rails.rb +60 -0
  16. data/lib/spring/commands/rake.rb +34 -0
  17. data/lib/spring/commands.rb +12 -195
  18. data/lib/spring/configuration.rb +6 -2
  19. data/lib/spring/env.rb +27 -18
  20. data/lib/spring/errors.rb +1 -1
  21. data/lib/spring/json.rb +626 -0
  22. data/lib/spring/server.rb +60 -29
  23. data/lib/spring/version.rb +1 -1
  24. data/lib/spring/watcher/abstract.rb +11 -7
  25. data/lib/spring/watcher/listen.rb +8 -10
  26. data/lib/spring/watcher/polling.rb +1 -1
  27. data/lib/spring/watcher.rb +17 -11
  28. data/spring.gemspec +1 -0
  29. data/test/acceptance/app_test.rb +270 -73
  30. data/test/apps/.gitignore +3 -0
  31. data/test/unit/client/help_test.rb +2 -0
  32. data/test/unit/commands_test.rb +8 -49
  33. data/test/unit/watcher_test.rb +22 -2
  34. metadata +18 -151
  35. data/lib/spring/client/start.rb +0 -17
  36. data/test/apps/rails-3-2/.gitignore +0 -18
  37. data/test/apps/rails-3-2/Gemfile +0 -9
  38. data/test/apps/rails-3-2/README.rdoc +0 -261
  39. data/test/apps/rails-3-2/Rakefile +0 -7
  40. data/test/apps/rails-3-2/app/assets/images/rails.png +0 -0
  41. data/test/apps/rails-3-2/app/assets/javascripts/application.js +0 -15
  42. data/test/apps/rails-3-2/app/assets/javascripts/posts.js.coffee +0 -3
  43. data/test/apps/rails-3-2/app/assets/stylesheets/application.css +0 -13
  44. data/test/apps/rails-3-2/app/assets/stylesheets/posts.css.scss +0 -3
  45. data/test/apps/rails-3-2/app/assets/stylesheets/scaffolds.css.scss +0 -69
  46. data/test/apps/rails-3-2/app/controllers/application_controller.rb +0 -3
  47. data/test/apps/rails-3-2/app/controllers/posts_controller.rb +0 -83
  48. data/test/apps/rails-3-2/app/helpers/application_helper.rb +0 -2
  49. data/test/apps/rails-3-2/app/helpers/posts_helper.rb +0 -2
  50. data/test/apps/rails-3-2/app/mailers/.gitkeep +0 -0
  51. data/test/apps/rails-3-2/app/models/.gitkeep +0 -0
  52. data/test/apps/rails-3-2/app/models/post.rb +0 -3
  53. data/test/apps/rails-3-2/app/views/layouts/application.html.erb +0 -14
  54. data/test/apps/rails-3-2/app/views/posts/_form.html.erb +0 -21
  55. data/test/apps/rails-3-2/app/views/posts/edit.html.erb +0 -6
  56. data/test/apps/rails-3-2/app/views/posts/index.html.erb +0 -23
  57. data/test/apps/rails-3-2/app/views/posts/new.html.erb +0 -5
  58. data/test/apps/rails-3-2/app/views/posts/show.html.erb +0 -10
  59. data/test/apps/rails-3-2/config/application.rb +0 -62
  60. data/test/apps/rails-3-2/config/boot.rb +0 -6
  61. data/test/apps/rails-3-2/config/database.yml +0 -25
  62. data/test/apps/rails-3-2/config/environment.rb +0 -5
  63. data/test/apps/rails-3-2/config/environments/development.rb +0 -37
  64. data/test/apps/rails-3-2/config/environments/production.rb +0 -67
  65. data/test/apps/rails-3-2/config/environments/test.rb +0 -37
  66. data/test/apps/rails-3-2/config/initializers/backtrace_silencers.rb +0 -7
  67. data/test/apps/rails-3-2/config/initializers/inflections.rb +0 -15
  68. data/test/apps/rails-3-2/config/initializers/mime_types.rb +0 -5
  69. data/test/apps/rails-3-2/config/initializers/secret_token.rb +0 -7
  70. data/test/apps/rails-3-2/config/initializers/session_store.rb +0 -8
  71. data/test/apps/rails-3-2/config/initializers/wrap_parameters.rb +0 -14
  72. data/test/apps/rails-3-2/config/locales/en.yml +0 -5
  73. data/test/apps/rails-3-2/config/routes.rb +0 -60
  74. data/test/apps/rails-3-2/config/spring.rb +0 -7
  75. data/test/apps/rails-3-2/config.ru +0 -4
  76. data/test/apps/rails-3-2/db/migrate/20121109171227_create_posts.rb +0 -9
  77. data/test/apps/rails-3-2/db/schema.rb +0 -22
  78. data/test/apps/rails-3-2/db/seeds.rb +0 -7
  79. data/test/apps/rails-3-2/lib/assets/.gitkeep +0 -0
  80. data/test/apps/rails-3-2/lib/tasks/.gitkeep +0 -0
  81. data/test/apps/rails-3-2/log/.gitkeep +0 -0
  82. data/test/apps/rails-3-2/public/404.html +0 -26
  83. data/test/apps/rails-3-2/public/422.html +0 -26
  84. data/test/apps/rails-3-2/public/500.html +0 -25
  85. data/test/apps/rails-3-2/public/favicon.ico +0 -0
  86. data/test/apps/rails-3-2/public/index.html +0 -241
  87. data/test/apps/rails-3-2/public/robots.txt +0 -5
  88. data/test/apps/rails-3-2/script/rails +0 -6
  89. data/test/apps/rails-3-2/test/fixtures/.gitkeep +0 -0
  90. data/test/apps/rails-3-2/test/fixtures/posts.yml +0 -7
  91. data/test/apps/rails-3-2/test/functional/.gitkeep +0 -0
  92. data/test/apps/rails-3-2/test/functional/posts_controller_test.rb +0 -49
  93. data/test/apps/rails-3-2/test/integration/.gitkeep +0 -0
  94. data/test/apps/rails-3-2/test/performance/browsing_test.rb +0 -12
  95. data/test/apps/rails-3-2/test/test_helper.rb +0 -13
  96. data/test/apps/rails-3-2/test/unit/.gitkeep +0 -0
  97. data/test/apps/rails-3-2/test/unit/helpers/posts_helper_test.rb +0 -4
  98. data/test/apps/rails-3-2/test/unit/post_test.rb +0 -7
  99. data/test/apps/rails-3-2/vendor/assets/javascripts/.gitkeep +0 -0
  100. data/test/apps/rails-3-2/vendor/assets/stylesheets/.gitkeep +0 -0
  101. data/test/apps/rails-3-2/vendor/plugins/.gitkeep +0 -0
@@ -1,87 +1,162 @@
1
1
  require "set"
2
- require "json"
3
-
4
- require "spring/configuration"
5
2
  require "spring/watcher"
3
+ require "thread"
6
4
 
7
5
  module Spring
8
6
  class Application
9
- attr_reader :manager, :watcher
7
+ attr_reader :manager, :watcher, :spring_env
10
8
 
11
9
  def initialize(manager, watcher = Spring.watcher)
12
- @manager = manager
13
- @watcher = watcher
14
- @setup = Set.new
10
+ @manager = manager
11
+ @watcher = watcher
12
+ @setup = Set.new
13
+ @spring_env = Env.new
14
+ @preloaded = false
15
+ @mutex = Mutex.new
16
+ @waiting = 0
17
+ @exiting = false
15
18
 
16
19
  # Workaround for GC bug in Ruby 2 which causes segfaults if watcher.to_io
17
20
  # instances get dereffed.
18
21
  @fds = [manager, watcher.to_io]
19
22
  end
20
23
 
21
- def start
24
+ def log(message)
25
+ spring_env.log "[application:#{ENV['RAILS_ENV']}] #{message}"
26
+ end
27
+
28
+ def preloaded?
29
+ @preloaded
30
+ end
31
+
32
+ def exiting?
33
+ @exiting
34
+ end
35
+
36
+ def preload
37
+ log "preloading app"
38
+
22
39
  require Spring.application_root_path.join("config", "application")
23
40
 
24
- # The test environment has config.cache_classes = true set by default.
25
- # However, we don't want this to prevent us from performing class reloading,
26
- # so this gets around that.
41
+ # config/environments/test.rb will have config.cache_classes = true. However
42
+ # we want it to be false so that we can reload files. This is a hack to
43
+ # override the effect of config.cache_classes = true. We can then actually
44
+ # set config.cache_classes = false after loading the environment.
27
45
  Rails::Application.initializer :initialize_dependency_mechanism, group: :all do
28
46
  ActiveSupport::Dependencies.mechanism = :load
29
47
  end
30
48
 
31
49
  require Spring.application_root_path.join("config", "environment")
32
50
 
51
+ Rails.application.config.cache_classes = false
52
+ disconnect_database
53
+
33
54
  watcher.add loaded_application_features
34
- watcher.add "Gemfile", "Gemfile.lock"
55
+ watcher.add Spring.gemfile, "#{Spring.gemfile}.lock"
35
56
  watcher.add Rails.application.paths["config/initializers"]
36
57
 
37
- run
58
+ @preloaded = true
38
59
  end
39
60
 
40
61
  def run
62
+ log "running"
41
63
  watcher.start
42
64
 
43
65
  loop do
44
66
  IO.select(@fds)
45
67
 
46
68
  if watcher.stale?
47
- exit
69
+ log "watcher stale; exiting"
70
+ manager.close
71
+ @exiting = true
72
+ try_exit
73
+ sleep
48
74
  else
49
75
  serve manager.recv_io(UNIXSocket)
50
76
  end
51
77
  end
52
78
  end
53
79
 
80
+ def try_exit
81
+ @mutex.synchronize {
82
+ exit if exiting? && @waiting == 0
83
+ }
84
+ end
85
+
54
86
  def serve(client)
87
+ log "got client"
55
88
  manager.puts
56
89
 
57
90
  streams = 3.times.map { client.recv_io }
58
- args = JSON.parse(client.read(client.gets.to_i))
59
- command = Spring.command(args.shift)
91
+ [STDOUT, STDERR].zip(streams).each { |a, b| a.reopen(b) }
92
+
93
+ preload unless preloaded?
94
+
95
+ args, env = JSON.load(client.read(client.gets.to_i)).values_at("args", "env")
96
+ command = Spring.command(args.shift)
60
97
 
98
+ connect_database
61
99
  setup command
62
100
 
63
- ActionDispatch::Reloader.cleanup!
64
- ActionDispatch::Reloader.prepare!
101
+ if Rails.application.reloaders.any?(&:updated?)
102
+ ActionDispatch::Reloader.cleanup!
103
+ ActionDispatch::Reloader.prepare!
104
+ end
65
105
 
66
106
  pid = fork {
67
107
  Process.setsid
68
- [STDOUT, STDERR, STDIN].zip(streams).each { |a, b| a.reopen(b) }
108
+ STDIN.reopen(streams.last)
69
109
  IGNORE_SIGNALS.each { |sig| trap(sig, "DEFAULT") }
110
+
111
+ ARGV.replace(args)
112
+
113
+ # Delete all env vars which are unchanged from before spring started
114
+ Spring.original_env.each { |k, v| ENV.delete k if ENV[k] == v }
115
+
116
+ # Load in the current env vars, except those which *were* changed when spring started
117
+ env.each { |k, v| ENV[k] ||= v }
118
+
119
+ connect_database
120
+ srand
121
+
70
122
  invoke_after_fork_callbacks
71
- command.call(args)
123
+ shush_backtraces
124
+
125
+ if command.respond_to?(:call)
126
+ command.call
127
+ else
128
+ exec_name = command.exec_name
129
+ gem_name = command.gem_name if command.respond_to?(:gem_name)
130
+
131
+ exec = Gem.bin_path(gem_name || exec_name, exec_name)
132
+ $0 = exec
133
+ load exec
134
+ end
72
135
  }
73
136
 
137
+ disconnect_database
138
+ [STDOUT, STDERR].each { |stream| stream.reopen(spring_env.log_file) }
139
+
140
+ log "forked #{pid}"
74
141
  manager.puts pid
75
142
 
76
143
  # Wait in a separate thread so we can run multiple commands at once
77
144
  Thread.new {
145
+ @mutex.synchronize { @waiting += 1 }
146
+
78
147
  _, status = Process.wait2 pid
148
+ log "#{pid} exited with #{status.exitstatus}"
149
+
79
150
  streams.each(&:close)
80
151
  client.puts(status.exitstatus)
81
152
  client.close
153
+
154
+ @mutex.synchronize { @waiting -= 1 }
155
+ try_exit
82
156
  }
83
157
 
84
158
  rescue => e
159
+ log "exception: #{e}"
85
160
  streams.each(&:close) if streams
86
161
  client.puts(1)
87
162
  client.close
@@ -110,5 +185,31 @@ module Spring
110
185
  def loaded_application_features
111
186
  $LOADED_FEATURES.select { |f| f.start_with?(File.realpath(Rails.root)) }
112
187
  end
188
+
189
+ def disconnect_database
190
+ ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base)
191
+ end
192
+
193
+ def connect_database
194
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
195
+ end
196
+
197
+ # This feels very naughty
198
+ def shush_backtraces
199
+ Kernel.module_eval do
200
+ old_raise = Kernel.method(:raise)
201
+ remove_method :raise
202
+ define_method :raise do |*args|
203
+ begin
204
+ old_raise.call(*args)
205
+ ensure
206
+ if $!
207
+ lib = File.expand_path("..", __FILE__)
208
+ $!.backtrace.reject! { |line| line.start_with?(lib) }
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
113
214
  end
114
215
  end
@@ -4,16 +4,19 @@ require "spring/application"
4
4
 
5
5
  module Spring
6
6
  class ApplicationManager
7
- attr_reader :pid, :child, :app_env, :spring_env
8
-
9
- def initialize(app_env)
10
- super()
7
+ attr_reader :pid, :child, :app_env, :spring_env, :server
11
8
 
9
+ def initialize(server, app_env)
10
+ @server = server
12
11
  @app_env = app_env
13
12
  @spring_env = Env.new
14
13
  @mutex = Mutex.new
15
14
  end
16
15
 
16
+ def log(message)
17
+ server.log(message)
18
+ end
19
+
17
20
  # We're not using @mutex.synchronize to avoid the weird "<internal:prelude>:10"
18
21
  # line which messes with backtraces in e.g. rspec
19
22
  def synchronize
@@ -29,9 +32,6 @@ module Spring
29
32
  end
30
33
 
31
34
  def restart
32
- # Restarting is a background operation. If it fails, we don't want
33
- # any terminal output. The user will see the output when they next
34
- # try to run a command.
35
35
  start_child(true)
36
36
  end
37
37
 
@@ -47,10 +47,12 @@ module Spring
47
47
  rescue Errno::ECONNRESET, Errno::EPIPE
48
48
  # The child has died but has not been collected by the wait thread yet,
49
49
  # so start a new child and try again.
50
+ log "child dead; starting"
50
51
  start
51
52
  yield
52
53
  end
53
54
  else
55
+ log "child not running; starting"
54
56
  start
55
57
  yield
56
58
  end
@@ -64,8 +66,12 @@ module Spring
64
66
  child.gets
65
67
  end
66
68
 
67
- child.gets.chomp.to_i # get the pid
68
- rescue Errno::ECONNRESET, Errno::EPIPE
69
+ pid = child.gets
70
+ pid = pid.chomp.to_i if pid
71
+ log "got worker pid #{pid}"
72
+ pid
73
+ rescue Errno::ECONNRESET, Errno::EPIPE => e
74
+ log "#{e} while reading from child; returning no pid"
69
75
  nil
70
76
  ensure
71
77
  client.close
@@ -77,11 +83,11 @@ module Spring
77
83
 
78
84
  private
79
85
 
80
- def start_child(silence = false)
86
+ def start_child(preload = false)
87
+ server.application_starting
88
+
81
89
  @child, child_socket = UNIXSocket.pair
82
90
  @pid = fork {
83
- [STDOUT, STDERR].each { |s| s.reopen('/dev/null', 'w') } if silence
84
-
85
91
  (ObjectSpace.each_object(IO).to_a - [STDOUT, STDERR, STDIN, child_socket])
86
92
  .reject(&:closed?)
87
93
  .each(&:close)
@@ -92,7 +98,9 @@ module Spring
92
98
  "spring app | #{spring_env.app_name} | started #{distance} ago | #{app_env} mode"
93
99
  }
94
100
 
95
- Application.new(child_socket).start
101
+ app = Application.new(child_socket)
102
+ app.preload if preload
103
+ app.run
96
104
  }
97
105
  child_socket.close
98
106
  end
@@ -1,10 +1,10 @@
1
1
  module Spring
2
2
  module Client
3
3
  class Binstub < Command
4
- attr_reader :bindir, :name
4
+ attr_reader :bindir, :commands
5
5
 
6
6
  def self.description
7
- "Generate spring based binstubs."
7
+ "Generate spring based binstubs. Use --all to generate a binstub for all known commands."
8
8
  end
9
9
 
10
10
  def self.call(args)
@@ -12,64 +12,68 @@ module Spring
12
12
  super
13
13
  end
14
14
 
15
+ class RailsCommand
16
+ def fallback
17
+ <<CODE
18
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
19
+ require_relative '../config/boot'
20
+ require 'rails/commands'
21
+ CODE
22
+ end
23
+ end
24
+
15
25
  def initialize(args)
16
26
  super
17
27
 
18
- @bindir = env.root.join("bin")
19
- @name = args[1]
28
+ @bindir = env.root.join("bin")
29
+ @commands = args.drop(1).inject({}) { |mem, name| mem.merge(find_commands(name)) }
20
30
  end
21
31
 
22
- def call
23
- if Spring.command?(name) || name == "rails"
24
- bindir.mkdir unless bindir.exist?
25
- generate_spring_binstub
26
- generate_command_binstub
32
+ def find_commands(name)
33
+ case name
34
+ when "--all"
35
+ commands = Spring.commands
36
+ commands.delete_if { |name, _| name.start_with?("rails_") }
37
+ commands["rails"] = RailsCommand.new
38
+ commands
39
+ when "rails"
40
+ { name => RailsCommand.new }
27
41
  else
28
- $stderr.puts "The '#{name}' command is not known to spring."
29
- exit 1
42
+ if command = Spring.commands[name]
43
+ { name => command }
44
+ else
45
+ $stderr.puts "The '#{name}' command is not known to spring."
46
+ exit 1
47
+ end
30
48
  end
31
49
  end
32
50
 
33
- def spring_binstub
34
- bindir.join("spring")
35
- end
36
-
37
- def command_binstub
38
- bindir.join(name)
51
+ def call
52
+ bindir.mkdir unless bindir.exist?
53
+ commands.each { |name, command| generate_binstub(name, command) }
39
54
  end
40
55
 
41
- def generate_spring_binstub
42
- File.write(spring_binstub, <<'CODE')
56
+ def generate_binstub(name, command)
57
+ File.write(bindir.join(name), <<CODE)
43
58
  #!/usr/bin/env ruby
44
59
 
45
- # This is a special way of invoking the spring gem in order to
46
- # work around the performance issue discussed in
47
- # https://github.com/rubygems/rubygems/pull/435
48
-
49
- glob = "{#{Gem::Specification.dirs.join(",")}}/spring-*.gemspec"
50
- candidates = Dir[glob].to_a.sort
51
-
52
- spec = Gem::Specification.load(candidates.last)
53
-
54
- if spec
55
- spec.activate
56
- load spec.bin_file("spring")
60
+ if !Process.respond_to?(:fork) || Gem::Specification.find_all_by_name("spring").empty?
61
+ #{fallback(name, command).strip.gsub(/^/, " ")}
57
62
  else
58
- $stderr.puts "Could not find spring gem in #{Gem::Specification.dirs.join(", ")}."
59
- exit 1
63
+ ARGV.unshift "#{name}"
64
+ load Gem.bin_path("spring", "spring")
60
65
  end
61
66
  CODE
62
67
 
63
- spring_binstub.chmod 0755
68
+ bindir.join(name).chmod 0755
64
69
  end
65
70
 
66
- def generate_command_binstub
67
- File.write(command_binstub, <<CODE)
68
- #!/usr/bin/env bash
69
- exec $(dirname $0)/spring #{name} "$@"
70
- CODE
71
-
72
- command_binstub.chmod 0755
71
+ def fallback(name, command)
72
+ if command.respond_to?(:fallback)
73
+ command.fallback
74
+ else
75
+ %{exec "bundle", "exec", "#{name}", *ARGV}
76
+ end
73
77
  end
74
78
  end
75
79
  end
@@ -28,7 +28,8 @@ module Spring
28
28
  end
29
29
 
30
30
  def formatted_help
31
- ["Usage: spring COMMAND [ARGS]\n",
31
+ ["Version: #{env.version}\n",
32
+ "Usage: spring COMMAND [ARGS]\n",
32
33
  *command_help("spring itself", spring_commands),
33
34
  '',
34
35
  *command_help("your application", application_commands)].join("\n")
@@ -48,6 +49,8 @@ module Spring
48
49
  def description_for_command(command)
49
50
  if command.respond_to?(:description)
50
51
  command.description
52
+ elsif command.respond_to?(:exec_name)
53
+ "Runs the #{command.exec_name} command"
51
54
  else
52
55
  "No description given."
53
56
  end
@@ -3,12 +3,13 @@ require "set"
3
3
  module Spring
4
4
  module Client
5
5
  class Rails < Command
6
- COMMANDS = Set.new %w(console runner generate)
6
+ COMMANDS = Set.new %w(console runner generate destroy)
7
7
 
8
8
  ALIASES = {
9
9
  "c" => "console",
10
10
  "r" => "runner",
11
- "g" => "generate"
11
+ "g" => "generate",
12
+ "d" => "destroy"
12
13
  }
13
14
 
14
15
  def self.description
@@ -21,7 +22,11 @@ module Spring
21
22
  if COMMANDS.include?(command_name)
22
23
  Run.call(["rails_#{command_name}", *args.drop(2)])
23
24
  else
24
- exec "bundle", "exec", *args
25
+ require "spring/configuration"
26
+ ARGV.shift
27
+ Object.const_set(:APP_PATH, Spring.application_root_path.join("config/application").to_s)
28
+ require Spring.application_root_path.join("config/boot")
29
+ require "rails/commands"
25
30
  end
26
31
  end
27
32
  end
@@ -1,19 +1,20 @@
1
1
  require "rbconfig"
2
2
  require "socket"
3
- require "json"
4
3
 
5
4
  module Spring
6
5
  module Client
7
6
  class Run < Command
8
7
  FORWARDED_SIGNALS = %w(INT QUIT USR1 USR2 INFO) & Signal.list.keys
9
8
 
9
+ def log(message)
10
+ env.log "[client] #{message}"
11
+ end
12
+
10
13
  def server
11
14
  @server ||= UNIXSocket.open(env.socket_name)
12
15
  end
13
16
 
14
17
  def call
15
- Spring.verify_environment!
16
-
17
18
  boot_server unless env.server_running?
18
19
  verify_server_version
19
20
 
@@ -29,15 +30,24 @@ module Spring
29
30
 
30
31
  def boot_server
31
32
  env.socket_path.unlink if env.socket_path.exist?
32
- Process.spawn("spring", "start")
33
- sleep 0.1 until env.socket_path.exist?
33
+
34
+ pid = fork {
35
+ require "spring/server"
36
+ Spring::Server.boot
37
+ }
38
+
39
+ until env.socket_path.exist?
40
+ _, status = Process.waitpid2(pid, Process::WNOHANG)
41
+ exit status.exitstatus if status
42
+ sleep 0.1
43
+ end
34
44
  end
35
45
 
36
46
  def verify_server_version
37
47
  server_version = server.gets.chomp
38
48
  if server_version != env.version
39
49
  $stderr.puts <<-ERROR
40
- There is a version mismatch beween the spring client and the server.
50
+ There is a version mismatch between the spring client and the server.
41
51
  You should restart the server and make sure to use the same version.
42
52
 
43
53
  CLIENT: #{env.version}, SERVER: #{server_version}
@@ -48,16 +58,18 @@ ERROR
48
58
 
49
59
  def connect_to_application(client)
50
60
  server.send_io client
51
- send_json server, args: args, env: ENV
61
+ send_json server, "args" => args, "default_rails_env" => default_rails_env
52
62
  server.gets or raise CommandNotFound
53
63
  end
54
64
 
55
65
  def run_command(client, application)
66
+ log "sending command"
67
+
56
68
  application.send_io STDOUT
57
69
  application.send_io STDERR
58
70
  application.send_io STDIN
59
71
 
60
- send_json application, args
72
+ send_json application, "args" => args, "env" => ENV.to_hash
61
73
 
62
74
  pid = server.gets
63
75
  pid = pid.chomp if pid
@@ -68,9 +80,16 @@ ERROR
68
80
  client.close
69
81
 
70
82
  if pid && !pid.empty?
83
+ log "got pid: #{pid}"
84
+
71
85
  forward_signals(pid.to_i)
72
- exit application.read.to_i
86
+ status = application.read.to_i
87
+
88
+ log "got exit status #{status}"
89
+
90
+ exit status
73
91
  else
92
+ log "got no pid"
74
93
  exit 1
75
94
  end
76
95
  ensure
@@ -95,9 +114,13 @@ ERROR
95
114
  def send_json(socket, data)
96
115
  data = JSON.dump(data)
97
116
 
98
- socket.puts data.length
117
+ socket.puts data.bytesize
99
118
  socket.write data
100
119
  end
120
+
121
+ def default_rails_env
122
+ ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
123
+ end
101
124
  end
102
125
  end
103
126
  end
@@ -21,8 +21,8 @@ module Spring
21
21
  end
22
22
 
23
23
  def application_pids
24
- candidates = `ps -a -o ppid= -o pid=`.lines
25
- candidates.select { |l| l =~ /^#{env.pid} / }
24
+ candidates = `ps -A -o ppid= -o pid=`.lines
25
+ candidates.select { |l| l =~ /^(\s+)?#{env.pid} / }
26
26
  .map { |l| l.split(" ").last }
27
27
  end
28
28
  end
data/lib/spring/client.rb CHANGED
@@ -1,9 +1,10 @@
1
- require "spring/configuration"
1
+ require "spring/errors"
2
+ require "spring/json"
3
+
2
4
  require "spring/client/command"
3
5
  require "spring/client/run"
4
6
  require "spring/client/help"
5
7
  require "spring/client/binstub"
6
- require "spring/client/start"
7
8
  require "spring/client/stop"
8
9
  require "spring/client/status"
9
10
  require "spring/client/rails"
@@ -13,7 +14,6 @@ module Spring
13
14
  COMMANDS = {
14
15
  "help" => Client::Help,
15
16
  "binstub" => Client::Binstub,
16
- "start" => Client::Start,
17
17
  "stop" => Client::Stop,
18
18
  "status" => Client::Status,
19
19
  "rails" => Client::Rails
@@ -0,0 +1,60 @@
1
+ module Spring
2
+ module Commands
3
+ class Rails
4
+ def call
5
+ ARGV.unshift command_name
6
+ Object.const_set(:APP_PATH, ::Rails.root.join('config/application'))
7
+ require "rails/commands"
8
+ end
9
+
10
+ def description
11
+ nil
12
+ end
13
+ end
14
+
15
+ class RailsConsole < Rails
16
+ def env(args)
17
+ args.first if args.first && !args.first.index("-")
18
+ end
19
+
20
+ def command_name
21
+ "console"
22
+ end
23
+ end
24
+
25
+ class RailsGenerate < Rails
26
+ def command_name
27
+ "generate"
28
+ end
29
+ end
30
+
31
+ class RailsDestroy < Rails
32
+ def command_name
33
+ "destroy"
34
+ end
35
+ end
36
+
37
+ class RailsRunner < Rails
38
+ def env(tail)
39
+ previous_option = nil
40
+ tail.reverse.each do |option|
41
+ case option
42
+ when /--environment=(\w+)/ then return $1
43
+ when '-e' then return previous_option
44
+ end
45
+ previous_option = option
46
+ end
47
+ nil
48
+ end
49
+
50
+ def command_name
51
+ "runner"
52
+ end
53
+ end
54
+
55
+ Spring.register_command "rails_console", RailsConsole.new
56
+ Spring.register_command "rails_generate", RailsGenerate.new
57
+ Spring.register_command "rails_destroy", RailsDestroy.new
58
+ Spring.register_command "rails_runner", RailsRunner.new
59
+ end
60
+ end