rails_build 1.2.0 → 2.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/rails_build CHANGED
@@ -1,34 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- # file : ./bin/rails_build
4
-
5
- END{
6
-
7
- RailsBuild::CLI.build!
8
-
9
- }
10
-
2
+ # encoding: utf-8
11
3
 
12
4
  module RailsBuild
13
5
  class CLI
14
6
  def CLI.usage
15
- <<-__
7
+ <<~__
16
8
  NAME
17
9
  rails_build
18
10
 
19
11
  SYNOPSIS
20
- a small, simple, bullet proof, and very fast static site generator built on rails 5
12
+ a small, simple, bullet proof, and fast enough static site generator built on top of the rails you already know and love
21
13
 
22
14
  USAGE
23
- rails_build [rails_root] *(options)
15
+ rails_build *(options)
24
16
 
25
17
  options:
26
18
  --help, -h : this message
27
- --rails_root,--r : specifiy the RAILS_ROOT, default=./
28
- --parallel,--p : how many requests to make in parallel, default=n_cpus/2
19
+ --init, -i : initialize ./config/rails_build.rb
20
+ --parallel,--p : how many requests to make in parallel, default=n_cpus-1
29
21
  --env,--e : speciify the RAILS_ENV, default=production
30
22
  --url, -u : provide the url of the build server, do *not* start separate one
31
- --server, -s : passenger | puma, default=passenger
32
23
  --log, -l : specify the logfile, default=STDERR
33
24
  --verbose, -v : turn on verbose logging
34
25
  __
@@ -36,27 +27,49 @@ module RailsBuild
36
27
 
37
28
  def CLI.opts
38
29
  GetoptLong.new(
39
- [ '--help' , '-h' , GetoptLong::NO_ARGUMENT ] ,
40
- [ '--url' , '-u' , GetoptLong::REQUIRED_ARGUMENT ] ,
41
- [ '--server' , '-s' , GetoptLong::REQUIRED_ARGUMENT ] ,
42
- [ '--parallel' , '-p' , GetoptLong::REQUIRED_ARGUMENT ] ,
43
- [ '--rails_root' , '-r' , GetoptLong::REQUIRED_ARGUMENT ] ,
44
- [ '--env' , '-e' , GetoptLong::REQUIRED_ARGUMENT ] ,
45
- [ '--log' , '-l' , GetoptLong::REQUIRED_ARGUMENT ] ,
46
- [ '--verbose' , '-v' , GetoptLong::NO_ARGUMENT ] ,
30
+ [ '--help' , '-h' , GetoptLong::NO_ARGUMENT ] ,
31
+ [ '--init' , '-i' , GetoptLong::NO_ARGUMENT ] ,
32
+ [ '--parallel' , '-p' , GetoptLong::REQUIRED_ARGUMENT ] ,
33
+ [ '--env' , '-e' , GetoptLong::REQUIRED_ARGUMENT ] ,
34
+ [ '--url' , '-u' , GetoptLong::REQUIRED_ARGUMENT ] ,
35
+ [ '--server' , '-s' , GetoptLong::REQUIRED_ARGUMENT ] ,
36
+ [ '--log' , '-l' , GetoptLong::REQUIRED_ARGUMENT ] ,
37
+ [ '--verbose' , '-v' , GetoptLong::NO_ARGUMENT ] ,
47
38
  )
48
39
  end
49
40
 
41
+ def run!
42
+ @args = parse_args!
43
+ @opts = parse_opts!
44
+
45
+ case
46
+ when @args[0] == 'help' || @opts[:help]
47
+ usage!
48
+
49
+ when @args[0] == 'init' || @opts[:init]
50
+ init!
51
+
52
+ else
53
+ if @args.empty?
54
+ build!
55
+ else
56
+ usage!
57
+ exit(42)
58
+ end
59
+ end
60
+ end
61
+
50
62
  def build!
51
63
  prepare!
52
64
 
53
- mkdir!
65
+ load_config!
54
66
 
55
- start_server! unless url
56
-
57
- clear_cache!
67
+ unless url
68
+ clear_cache!
69
+ start_server!
70
+ end
58
71
 
59
- extract_urls! url_for('/rails_build/configuration.json')
72
+ extract_urls!
60
73
 
61
74
  precompile_assets!
62
75
 
@@ -68,8 +81,8 @@ module RailsBuild
68
81
  end
69
82
 
70
83
  #
71
- def CLI.build!(*args, &block)
72
- new(*args, &block).build!
84
+ def CLI.run!(*args, &block)
85
+ new(*args, &block).run!
73
86
  end
74
87
 
75
88
  #
@@ -82,86 +95,29 @@ module RailsBuild
82
95
  attr_accessor :env
83
96
  attr_accessor :parallel
84
97
 
85
- #
86
- def initialize(*args, &block)
87
- setup!
88
-
89
- @args = parse_args!
90
-
91
- @opts = parse_opts!
92
-
93
- if @opts[:help]
94
- usage!
95
- exit(42)
96
- end
97
- end
98
-
99
- #
100
- def setup!
101
- #
102
- STDOUT.sync = true
103
- STDERR.sync = true
104
-
105
- #
106
- ENV['SPRING_DISABLE'] = 'true'
107
- ENV['DISABLE_SPRING'] = 'true'
108
-
109
- #
110
- %w[
111
- fileutils pathname thread socket timeout time uri etc open-uri securerandom logger getoptlong rubygems json
112
- ].each do |stdlib|
113
- require(stdlib)
114
- end
115
- end
116
-
117
98
  #
118
99
  def prepare!
119
100
  #
120
- @rails_root = find_rails_root!(@args[0] || '.')
101
+ @rails_root = find_rails_root!
121
102
 
122
103
  #
123
104
  Dir.chdir(@rails_root)
124
105
 
125
106
  #
126
- if File.exists?('./Gemfile')
127
- require 'bundler/setup'
128
- Bundler.setup(:require => false)
129
- end
130
-
131
- #
132
- %w[
133
- threadify persistent_http
134
- ].each do |gem|
135
- begin
136
- require(gem)
137
- rescue LoadError
138
- abort "add gem '#{ gem }' to your Gemfile"
139
- end
140
- end
107
+ @logger = Logger.new(@opts[:log] || STDERR)
141
108
 
142
- #
143
- begin
144
- require('pry')
145
- rescue LoadError
146
- nil
147
- end
109
+ @env = @opts[:env] || ENV['RAILS_BUILD_ENV'] || ENV['RAILS_ENV']
110
+ @url = @opts[:url] || ENV['RAILS_BUILD_URL']
111
+ @parallel = @opts[:parallel] || ENV['RAILS_BUILD_PARALLEL']
112
+ @verbose = @opts[:verbose] || ENV['RAILS_BUILD_VERBOSE']
148
113
 
149
- #
150
- @logger = Logger.new(@opts[:log] || STDERR)
114
+ @uuid = ENV['RAILS_BUILD_UUID']
115
+ @time = ENV['RAILS_BUILD_TIME']
151
116
 
152
- @uuid = ENV['RAILS_BUILD']
153
- @time = ENV['RAILS_BUILD_TIME']
154
- @url = @opts[:url] || ENV['RAILS_BUILD_URL']
155
- @server = @opts[:server] || ENV['RAILS_BUILD_SERVER']
156
- @env = @opts[:env] || ENV['RAILS_BUILD_ENV'] || ENV['RAILS_ENV']
157
- @parallel = @opts[:parallel] || ENV['RAILS_BUILD_PARALLEL']
158
- @verbose = @opts[:verbose] || ENV['RAILS_BUILD_VERBOSE']
159
-
160
- @uuid ||= SecureRandom.uuid
161
- @time ||= Time.now.utc
162
- @server ||= 'passenger'
163
117
  @env ||= 'production'
164
- @parallel ||= (Etc.nprocessors/2)
118
+ @parallel ||= (Etc.nprocessors - 1)
119
+ @uuid ||= SecureRandom.uuid
120
+ @time ||= Time.now.utc
165
121
 
166
122
  unless @time.is_a?(Time)
167
123
  @time = Time.parse(@time.to_s).utc
@@ -186,15 +142,13 @@ module RailsBuild
186
142
 
187
143
  @started_at = Time.now
188
144
 
189
- unless(( server = Server.for(@server, self) ))
190
- abort "no server found for server=#{ @server.inspect }"
191
- else
192
- @server = server
193
- end
145
+ mkdir!
146
+
147
+ @server = Server.new(cli: self)
194
148
  end
195
149
 
196
150
  #
197
- def find_rails_root!(path)
151
+ def find_rails_root!(path = '.')
198
152
  rails_root = File.expand_path(path.to_s)
199
153
 
200
154
  loop do
@@ -238,11 +192,25 @@ module RailsBuild
238
192
  STDERR.puts(usage)
239
193
  end
240
194
 
195
+ #
196
+ def init!
197
+ config = SAMPLE_CONFIG
198
+
199
+ path = './config/rails_build.rb'
200
+
201
+ FileUtils.mkdir_p(File.dirname(path))
202
+
203
+ IO.binwrite(path, config)
204
+
205
+ STDERR.puts("please review #{ path } before running `rails_build`")
206
+ end
207
+
241
208
  #
242
209
  def mkdir!
243
210
  FileUtils.rm_rf(@directory)
244
211
  FileUtils.mkdir_p(@directory)
245
- log(:info, "directory: #{ @directory }")
212
+
213
+ log(:info, "build: #{ @directory }")
246
214
  end
247
215
 
248
216
  #
@@ -256,48 +224,31 @@ module RailsBuild
256
224
  ports =
257
225
  (2000 .. 9000).to_a
258
226
 
259
- start_server, stop_server = nil
260
-
261
- @server.setup!
262
-
263
227
  ports.each do |port|
264
228
  next unless port_open?(port)
265
229
 
266
- start_server = @server.start_command_for(port)
267
- stop_server = @server.stop_command_for(port)
268
-
269
- `#{ stop_server } 2>&1`.strip
270
-
271
- log(:info, "cmd: #{ start_server }")
272
- server_output = `#{ start_server } 2>&1`.strip
273
-
274
- log(:info, "status: #{ $?.exitstatus }")
230
+ @server.start!(port:)
275
231
 
232
+ timeout = 11
276
233
  t = Time.now.to_f
277
- timeout = 10
278
234
  i = 0
235
+
236
+ @proto = @config.fetch('force_ssl') ? 'https' : 'http'
279
237
  url = nil
280
238
 
281
239
  loop do
282
240
  i += 1
241
+ sleep(rand(0.42))
283
242
 
284
243
  begin
285
- url = "http://localhost:#{ port }"
286
- open(url){|socket| socket.read}
244
+ raise if port_open?(port)
245
+ url = "#{ @proto }://0.0.0.0:#{ port }"
287
246
  @url = url
288
247
  @port = port
289
248
  break
290
249
  rescue Object => e
291
- if i > 2
292
- log :error, "url: #{ url } ->"
293
- log :error, "#{ e.message }(#{ e.class })\n"
294
- log :error, "#{ server_output }\n\n"
295
- end
296
-
297
250
  if((Time.now.to_f - t) > timeout)
298
- abort("could not start server inside of #{ timeout } seconds via\n\n#{ start_server }\n\n;-/")
299
- else
300
- sleep(rand(0.42))
251
+ abort("could not start server inside of #{ timeout } seconds")
301
252
  end
302
253
  end
303
254
  end
@@ -310,76 +261,50 @@ module RailsBuild
310
261
  unless @url
311
262
  abort("could not start server on any of ports #{ ports.first } .. #{ ports.last }")
312
263
  else
313
- log(:info, "started server on #{ @url }")
264
+ log(:info, "url: #{ @url }")
314
265
  end
315
266
 
316
- # set assassins to ensure the server daemon never outlives the build script
317
- # no matter how it is killed (even -9)
318
- #
319
- at_exit{
320
- log(:info, "cmd: #{ stop_server }")
321
- `#{ stop_server } >/dev/null 2>&1`
322
- log(:info, "status: #{ $?.exitstatus }")
323
- log(:info, "stopped server #{ @url }")
324
- @server.cleanup!(:port => @port)
325
- }
326
-
327
- assassin = <<-__
328
- pid = #{ Process.pid }
329
-
330
- 4242.times do
331
- begin
332
- Process.kill(0, pid)
333
- rescue Object => e
334
- if e.is_a?(Errno::ESRCH)
335
- `#{ stop_server } >/dev/null 2>&1`
336
- Process.kill(-15, pid) rescue nil
337
- sleep(rand + rand)
338
- Process.kill(-9, pid) rescue nil
339
- end
340
- exit
341
- end
342
- sleep(1 + rand)
343
- end
344
- __
345
- IO.binwrite('tmp/build-assassin.rb', assassin)
346
- cmd = "nohup ruby tmp/build-assassin.rb >/dev/null 2>&1 &"
347
- system cmd
348
-
349
267
  #
350
268
  @started_at = Time.now
351
269
  @url
352
270
  end
353
271
 
354
272
  #
355
- def extract_urls!(build_url)
356
- urls = []
273
+ def load_config!
274
+ unless test(?s, RailsBuild.config_path)
275
+ log(:error, "no config found in #{ RailsBuild.config_path }")
276
+ abort
277
+ end
357
278
 
358
- code, body = http_get(build_url)
279
+ Tempfile.open do |tmp|
280
+ env = {RAILS_ENV:@env, RAILS_BUILD_CONFIG_DUMP:tmp.path}
281
+ spawn('rails', 'runner', 'RailsBuild.dump_config!', env:)
282
+ json = IO.binread(tmp.path)
283
+ hash = JSON.parse(json)
359
284
 
360
- unless code == 200
361
- raise "failed to get build urls from #{ build_url }"
285
+ @config = Configuration.new(hash)
362
286
  end
287
+ end
363
288
 
364
- @_build = JSON.parse(body)
289
+ def extract_urls!
290
+ path = @config.path
291
+ urls = @config.urls
365
292
 
366
- unless @_build['urls'].is_a?(Array)
367
- raise "failed to find any build urls at #{ build_url } - edit ./config/rails_build.rb # see README"
293
+ if urls.size == 0
294
+ abort("failed to find any rails_build urls in:\n#{ @config.to_json }")
368
295
  end
369
296
 
370
- urls = @_build['urls']
371
-
372
297
  urls.map!{|url| url_for(url)}
373
298
 
374
- log(:info, "extracted #{ urls.size } url(s) to build from #{ build_url }")
299
+ log(:info, "extracted #{ urls.size } url(s) to build from #{ path }")
375
300
 
376
301
  @urls = urls
377
302
  end
378
303
 
379
304
  #
380
305
  def clear_cache!
381
- #spawn "rake tmp:cache:clear"
382
- spawn "rails runner 'Rails.cache.clear'"
306
+ spawn "rails tmp:cache:clear", error: false
307
+ spawn "rails runner 'Rails.cache.clear'", error: false
383
308
  end
384
309
 
385
310
  #
@@ -389,10 +314,10 @@ module RailsBuild
389
314
 
390
315
  if test(?d, @asset_dir)
391
316
  @asset_tmp = File.join(@rails_root, "tmp/assets-build-#{ @uuid }")
392
- FileUtils.mv(@asset_dir, @asset_tmp)
317
+ FileUtils.mv(@asset_dir, @asset_tmp)
393
318
  end
394
319
 
395
- spawn "RAILS_ENV=production rake assets:precompile"
320
+ spawn "RAILS_ENV=production DISABLE_SPRING=true rake assets:precompile"
396
321
 
397
322
  assets = Dir.glob(File.join(@rails_root, 'public/assets/**/**'))
398
323
 
@@ -403,10 +328,29 @@ module RailsBuild
403
328
 
404
329
  #
405
330
  def rsync_public!
406
- spawn "rsync -avz ./public/ #{ @directory }"
331
+ commands = [
332
+ "rsync -avz ./public/ #{ @directory }",
333
+ "cp -ru ./public/ #{ @directory }",
334
+ proc{ FileUtils.cp_r('./public', @directory) }
335
+ ]
336
+
337
+ rsynced = false
338
+
339
+ commands.each do |command|
340
+ begin
341
+ spawn(command)
342
+ rsynced = true
343
+ break
344
+ rescue
345
+ next
346
+ end
347
+ end
348
+
349
+ unless rsynced
350
+ abort "failed to rsync ./public to `#{ @directory }`"
351
+ end
407
352
 
408
353
  count = 0
409
-
410
354
  Dir.glob(File.join(@directory, '**/**')).each{ count += 1 }
411
355
 
412
356
  log(:info, "rsync'd #{ count } files")
@@ -417,93 +361,70 @@ module RailsBuild
417
361
  end
418
362
  end
419
363
 
420
- #
421
- def parallel_build!(n = nil)
422
- n ||= @parallel
364
+ #
365
+ def parallel_build!
366
+ size = @parallel
423
367
 
424
368
  stats = {
425
- :success => [], :missing => [], :failure => [],
369
+ :success => [],
370
+ :missing => [],
371
+ :failure => [],
426
372
  }
427
373
 
428
- times = Queue.new
429
-
430
- avg = nil
431
-
432
- Thread.new do
433
- Thread.current.abort_on_exception = true
434
- total = 0.0
435
- n = 0
436
- loop do
437
- while(time = times.pop)
438
- total += time
439
- n += 1
440
- avg = (total / n).round(2)
441
- end
442
- end
443
- end
444
-
445
- a = Time.now.to_f
446
-
447
- _stats =
448
- @urls.threadify(n) do |url|
449
- uri = uri_for(url)
450
- path = path_for(uri)
451
- upath = uri.path
452
- rpath = relative_path(path, :from => @directory)
374
+ times = []
453
375
 
454
- _a = Time.now.to_f
376
+ Parallel.each(@urls, in_threads: 8) do |url|
377
+ uri = uri_for(url)
378
+ path = path_for(uri)
455
379
 
456
- code, body = http_get(uri)
380
+ rpath = relative_path(path, :from => @directory)
457
381
 
458
- _b = Time.now.to_f
459
- _e = (_b - _a).round(2)
382
+ code = nil
383
+ body = nil
460
384
 
461
- times.push(_e)
462
-
463
- label = "#{ code } @ (t̄:#{ avg }s)"
464
-
465
- case code
466
- when 200
467
- write_path(path, body)
468
- log(:info, "#{ label }: #{ upath } -> #{ rpath } (t:#{ _e }s)")
469
- [:success, url]
470
- when 404
471
- log(:error, "#{ label }: #{ upath }")
472
- [:missing, url]
473
- when 500
474
- log(:error, "#{ label }: #{ upath }")
475
- [:failure, url]
476
- else
477
- log(:error, "#{ label }: #{ upath }")
478
- [:failure, url]
385
+ time =
386
+ timing do
387
+ code, body = http_get(uri)
388
+ write_path(path, body) if code == 200
479
389
  end
480
- end
481
-
482
- _stats.each{|stat, url| stats[stat].push(url)}
483
390
 
484
- b = Time.now.to_f
391
+ times.push(time)
392
+ ts = times.dup
393
+ rps = (ts.size / ts.sum).round(4)
394
+ msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
395
+
396
+ case code
397
+ when 200
398
+ log(:info, msg)
399
+ stats[:success].push(url)
400
+ when 404
401
+ log(:error, msg)
402
+ stats[:missing].push(url)
403
+ else
404
+ log(:error, msg)
405
+ stats[:failure].push(url)
406
+ end
407
+ end
485
408
 
486
- borked = false
409
+ borked = 0
487
410
 
488
411
  if stats[:missing].size > 0
489
- borked = true
490
- log(:error, "missing on #{ stats[:missing].size } urls")
412
+ borked += stats[:missing].size
413
+ log(:error, "missing on #{ stats[:missing].size } url(s)")
491
414
  end
492
415
 
493
416
  if stats[:failure].size > 0
494
- borked = true
495
- log(:error, "failure on #{ stats[:failure].size } urls")
417
+ borked += stats[:failure].size
418
+ log(:error, "failure on #{ stats[:failure].size } url(s)")
496
419
  end
497
420
 
498
- if borked
499
- exit(1)
421
+ if borked > 0
422
+ exit(borked)
500
423
  end
501
424
 
502
- elapsed = b - a
503
- n = @urls.size
504
- rps = (n / elapsed).round(2)
425
+ rps = (times.size / times.sum).round(4)
505
426
 
506
- log(:info, "downloaded #{ n } urls at ~#{ rps }/s")
427
+ log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
507
428
 
508
429
  @urls
509
430
  end
@@ -511,39 +432,50 @@ module RailsBuild
511
432
  #
512
433
  def finalize!
513
434
  @finished_at = Time.now
435
+
514
436
  elapsed = (@finished_at.to_f - @started_at.to_f)
437
+
515
438
  log(:info, "build time - #{ hms(elapsed) }")
439
+
440
+ # because netlify refuses to deploy from a symlink!
516
441
  on_netlify = ENV['DEPLOY_PRIME_URL'].to_s =~ /netlify/
517
- strategy = on_netlify ? 'cp_r' : 'ln_s' # netlify refuses to deploy from a symlink ;-/ # FIXME
442
+
443
+ cp = on_netlify ? 'cp_r' : 'ln_s'
444
+
518
445
  build = File.join(@rails_root, 'build')
446
+
519
447
  FileUtils.rm_rf(build)
520
- FileUtils.send(strategy, @directory, build)
521
- log(:info, "preview with `static ./build/` # brew install node-static")
522
- end
523
-
524
- def http_connection
525
- @http_connection ||= (
526
- PersistentHTTP.new({
527
- :url => @url,
528
- :pool_size => (@parallel + 1),
529
- :logger => (@verbose ? @logger : nil),
530
- :pool_timeout => 10,
531
- :warn_timeout => 1,
532
- :force_retry => true,
533
- })
534
- )
448
+ FileUtils.send(cp, @directory, build)
449
+
450
+ #log(:info, "to preview run: ruby -run -ehttpd ./build/ -p4242")
451
+ end
452
+
453
+ def timing(&block)
454
+ t = Time.now.to_f
455
+
456
+ block.call
457
+
458
+ (Time.now.to_f - t).round(2)
535
459
  end
536
460
 
537
461
  def http_get(url)
538
- request = Net::HTTP::Get.new(url)
539
- response = http_connection.request(request)
462
+ uri = URI.parse(url.to_s)
463
+
464
+ response =
465
+ begin
466
+ Net::HTTP.get_response(uri)
467
+ rescue
468
+ [code = 500, body = '']
469
+ end
540
470
 
541
471
  if response.is_a?(Net::HTTPRedirection)
542
472
  location = response['Location']
473
+
543
474
  if location.to_s == url.to_s
544
475
  log(:fatal, "circular redirection on #{ url }")
545
476
  exit(1)
546
477
  end
478
+
547
479
  return http_get(location)
548
480
  end
549
481
 
@@ -566,18 +498,33 @@ module RailsBuild
566
498
  #
567
499
  def path_for(url)
568
500
  uri = uri_for(url)
501
+ path = nil
569
502
 
570
503
  case
571
504
  when uri.path=='/' || uri.path=='.'
572
505
  path = File.join(@directory, 'index.html')
506
+
573
507
  else
574
508
  path = File.join(@directory, uri.path)
509
+
575
510
  dirname, basename = File.split(path)
576
511
  base, ext = basename.split('.', 2)
577
- if ext.nil?
578
- path = File.join(path, 'index.html')
512
+
513
+ case
514
+ when uri.path.end_with?('/')
515
+ path =
516
+ File.join(path, 'index.html')
517
+
518
+ when ext.nil?
519
+ path =
520
+ if @config.fetch('index_html')
521
+ File.join(path, 'index.html')
522
+ else
523
+ path + '.html'
524
+ end
579
525
  end
580
526
  end
527
+
581
528
  path
582
529
  end
583
530
 
@@ -606,13 +553,14 @@ module RailsBuild
606
553
  uri = URI.parse(url.to_s)
607
554
 
608
555
  if uri.absolute?
556
+ uri.path = path
609
557
  uri.to_s
610
558
  else
611
- relative_uri = URI.parse(@url)
612
- relative_uri.path = absolute_path_for(uri.path)
613
- relative_uri.query = uri.query
614
- relative_uri.fragment = uri.fragment
615
- relative_uri.to_s
559
+ rel = @url ? URI.parse(@url) : URI.parse('')
560
+ rel.path = absolute_path_for(uri.path)
561
+ rel.query = uri.query
562
+ rel.fragment = uri.fragment
563
+ rel.to_s
616
564
  end
617
565
  end
618
566
 
@@ -636,14 +584,6 @@ module RailsBuild
636
584
  [hours.to_i, minutes.to_s, seconds]
637
585
  end
638
586
 
639
- #
640
- def stopwatch(&block)
641
- a = Time.now
642
- result = block.call
643
- b = Time.now
644
- [result, b.to_f - a.to_f]
645
- end
646
-
647
587
  #
648
588
  def port_open?(port, options = {})
649
589
  seconds = options[:timeout] || 1
@@ -675,7 +615,8 @@ module RailsBuild
675
615
 
676
616
  #
677
617
  def absolute_path_for(*args)
678
- path = ('/' + paths_for(*args).join('/')).squeeze('/')
618
+ trailing_slash = args.join.end_with?('/') ? '/' : ''
619
+ path = ('/' + paths_for(*args).join('/') + trailing_slash).squeeze('/')
679
620
  path unless path.strip.empty?
680
621
  end
681
622
 
@@ -698,121 +639,202 @@ module RailsBuild
698
639
  end
699
640
 
700
641
  #
701
- def spawn(command)
702
- oe = `#{ command } 2>&1`
642
+ def spawn(arg, *args, **kws)
643
+ command = [arg, *args]
644
+
645
+ env = kws.fetch(:env){ {} }
646
+ error = kws.fetch(:error){ true }
647
+ quiet = kws.fetch(:quiet){ false }
648
+ stdin = kws.fetch(:stdin){ '' }
649
+
650
+ env.transform_keys!(&:to_s)
651
+ env.transform_values!(&:to_s)
652
+
653
+ pid = nil
654
+ status = nil
655
+ stdout = nil
656
+ stderr = nil
657
+
658
+ Tempfile.open do |i|
659
+ i.write(stdin)
660
+ i.flush
661
+
662
+ Tempfile.open do |o|
663
+ Tempfile.open do |e|
664
+ redirects = {:in => i.path, :out => o.path, :err => e.path}
665
+
666
+ pid = Process.spawn(env, *command, redirects)
667
+
668
+ Process.wait(pid)
669
+
670
+ status = $?.exitstatus
703
671
 
704
- unless $? == 0
705
- msg = "command(#{ command }) failed with (#{ $? })"
706
- log(:error, msg)
707
- raise(msg)
672
+ stdout = IO.binread(o.path)
673
+ stderr = IO.binread(e.path)
674
+ end
675
+ end
676
+ end
677
+
678
+ unless status == 0
679
+ unless kws[:quiet] == true
680
+ log(:error, "#{ command.join(' ') } ###===>>> #{ status }\nSTDOUT:\n#{ stdout }\n\STDERR:\n#{ stderr }")
681
+ exit(status)
682
+ end
708
683
  end
709
684
 
710
- oe
685
+ {command:, pid:, env:, status:, stdin:, stdout:, stderr:}
711
686
  end
712
687
  end
713
688
 
714
689
  #
715
690
  class Server
716
- def Server.for(type, cli, *args, &block)
717
- type = type.to_s.strip.downcase
718
-
719
- case type
720
- when /puma/
721
- Puma.new(cli, *args, &block)
722
- when /passenger/
723
- Passenger.new(cli, *args, &block)
724
- else
725
- nil
726
- end
727
- end
728
-
729
- def initialize(cli)
691
+ attr_reader :pid
692
+
693
+ def initialize(cli:)
730
694
  @cli = cli
731
- end
732
695
 
733
- def setup!
734
- nil
696
+ @env = @cli.env
697
+ @directory = @cli.directory
698
+ @rails_root = @cli.rails_root
699
+ @parallel = @cli.parallel
700
+ @uuid = @cli.uuid
701
+
702
+ @thread = nil
703
+ @pid = nil
735
704
  end
736
705
 
737
- class Puma < Server
738
- def initialize(cli)
739
- @cli = cli
706
+ def start!(port:)
707
+ system("#{ version_command } >/dev/null 2>&1") ||
708
+ abort("app fails to load via: #{ version_command }")
740
709
 
741
- @env = @cli.env
742
- @directory = @cli.directory
743
- @rails_root = @cli.rails_root
744
- @parallel = @cli.parallel
710
+ q = Queue.new
745
711
 
746
- @puma = "bundle exec puma"
747
- @pumactl = "bundle exec pumactl"
712
+ cmd = start_command_for(port)
748
713
 
749
- @pidfile = @cli.relative_path(File.join(@directory, ".puma-pid.txt"), :from => @cli.rails_root)
750
- @statefile = @cli.relative_path(File.join(@directory, ".puma-state.txt"), :from => @cli.rails_root)
751
- end
714
+ log = './tmp/rails_build_server.log'
752
715
 
753
- def start_command_for(port)
754
- %W[
755
- #{ @puma }
756
- --pidfile=#{ @pidfile }
757
- --state=#{ @statefile }
758
- --port=#{ port }
759
- --environment=#{ @env }
760
- --workers=#{ @parallel }
761
- --config=/dev/null
762
- --threads=1:1
763
- --preload
764
- --daemon
765
- ./config.ru
766
- ].join(' ').strip
716
+ @cli.log(:info, "server: #{ cmd } > #{ log } 2>&1")
717
+
718
+ @thread = Thread.new do
719
+ Thread.current.abort_on_exception = true
720
+ pipe = IO.popen("#{ cmd } > #{ log } 2>&1")
721
+ q.push(pipe.pid)
767
722
  end
768
723
 
769
- def stop_command_for(port)
724
+ @pid = q.pop
725
+
726
+ @cli.log(:info, "pid: #{ @pid }")
727
+
728
+ @assassin = Assassin.ate(@pid)
729
+
730
+ at_exit{ stop! }
731
+ end
732
+
733
+ def version_command
734
+ cmd_for(
770
735
  %W[
771
- #{ @pumactl }
772
- --pidfile=#{ @pidfile }
773
- --state=#{ @statefile }
774
- stop
775
- ].join(' ').strip
776
- end
736
+ RAILS_ENV=#{ @env }
737
+ DISABLE_SPRING=true
777
738
 
778
- def cleanup!(*args)
779
- nil
780
- end
739
+ rails --version
740
+ ]
741
+ )
781
742
  end
782
743
 
783
- class Passenger < Server
784
- def initialize(cli)
785
- @cli = cli
744
+ def start_command_for(port)
745
+ cmd_for(
746
+ %W[
747
+ RAILS_ENV=#{ @env }
748
+ DISABLE_SPRING=true
786
749
 
787
- @env = @cli.env
788
- @directory = @cli.directory
789
- @rails_root = @cli.rails_root
790
- @parallel = @cli.parallel
750
+ RAILS_BUILD=#{ @uuid }
791
751
 
792
- @passenger = "bundle exec passenger"
793
- end
752
+ RAILS_SERVE_STATIC_FILES=true
753
+ RAILS_LOG_TO_STDOUT=true
754
+ WEB_CONCURRENCY=#{ @parallel.to_s }
794
755
 
795
- def start_command_for(port)
796
- "#{ @passenger } start --daemonize --environment #{ @env } --port #{ port } --max-pool-size #{ @parallel }"
797
- end
756
+ rails server
798
757
 
799
- def stop_command_for(port)
800
- "#{ @passenger } stop --port #{ port }"
801
- end
758
+ --environment=#{ @env }
759
+ --port=#{ port }
760
+ --binding=0.0.0.0
761
+ --log-to-stdout
762
+ ]
763
+ )
764
+ end
802
765
 
803
- def cleanup!(*args)
804
- options = args.last.is_a?(Hash) ? args.pop : {}
805
- port = options[:port]
806
- FileUtils.rm_rf("#{ @rails_root }/tmp/pids/passenger.#{ port }.pid")
807
- end
766
+ def cmd_for(arg, *args)
767
+ [arg, *args].flatten.compact.join(' ').squeeze(' ').strip
768
+ end
769
+
770
+ def stop!
771
+ kill!(@pid)
772
+ @thread.kill
773
+ @cli.log(:info, "stopped: #{ @pid }")
774
+ end
808
775
 
809
- def setup!
776
+ def kill!(pid)
777
+ 42.times do
810
778
  begin
811
- require 'phusion_passenger'
812
- rescue LoadError => le
813
- abort "please add `gem 'passenger'` to your Gemfile"
779
+ Process.kill(0, pid)
780
+ return(true)
781
+ rescue Object => e
782
+ if e.is_a?(Errno::ESRCH)
783
+ Process.kill(-15, pid) rescue nil
784
+ sleep(rand + rand)
785
+ Process.kill(-9, pid) rescue nil
786
+ end
814
787
  end
788
+ sleep(0.42 + rand)
815
789
  end
790
+ return(false)
816
791
  end
817
792
  end
818
793
  end
794
+
795
+ END {
796
+ require_relative '../lib/rails_build.rb'
797
+
798
+ STDOUT.sync = true
799
+ STDERR.sync = true
800
+
801
+ RailsBuild::CLI.run!
802
+ }
803
+
804
+ module RailsBuild
805
+ SAMPLE_CONFIG = <<~'__'
806
+ <<~________
807
+
808
+ this file should to enumerate all the urls you'd like to build
809
+
810
+ the contents of your ./public directory, and any assets, are automaticaly included
811
+
812
+ therefore you need only declare which dynamic urls, that is to say, 'routes'
813
+
814
+ you would like included in your build
815
+
816
+ it is not loaded except during build time, and will not affect your normal rails app in any way
817
+
818
+ ________
819
+
820
+
821
+ RailsBuild.configure do |config|
822
+
823
+ # most of the time you are going to want your route included, which will
824
+ # translate into an ./index.html being output in the build
825
+ #
826
+
827
+ config.urls << '/'
828
+
829
+ # include any/all additional routes youd' like built thusly
830
+ #
831
+
832
+ Post.each do |post|
833
+ config.urls << "/posts/#{ post.id }"
834
+ end
835
+
836
+ # thats it! - now just run `rails_build` and you are GTG
837
+
838
+ end
839
+ __
840
+ end