rails_build 1.2.0 → 2.4.3

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.
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