rails_build 1.2.0 → 2.4.2

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!
54
-
55
- start_server! unless url
65
+ load_config!
56
66
 
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 = DATA.read.strip
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")
@@ -418,92 +362,151 @@ module RailsBuild
418
362
  end
419
363
 
420
364
  #
421
- def parallel_build!(n = nil)
422
- n ||= @parallel
365
+ =begin
366
+ def parallel_build!
367
+ size = @parallel
423
368
 
424
369
  stats = {
425
- :success => [], :missing => [], :failure => [],
370
+ :success => [],
371
+ :missing => [],
372
+ :failure => [],
426
373
  }
427
374
 
428
- times = Queue.new
375
+ times = []
376
+
377
+ elapsed = timing do
378
+ ThreadPool.new(size:) do |tp|
379
+ tp.run do
380
+ @urls.each{|url| tp.process!(url:)}
381
+ end
429
382
 
430
- avg = nil
383
+ tp.process do |url:|
384
+ uri = uri_for(url)
385
+ path = path_for(uri)
431
386
 
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)
387
+ upath = uri.path
388
+ rpath = relative_path(path, :from => @directory)
389
+
390
+ code = nil
391
+ body = nil
392
+
393
+ time =
394
+ timing do
395
+ code, body = http_get(uri)
396
+ write_path(path, body) if code == 200
397
+ end
398
+
399
+ tp.success!(url:, rpath:, time:, code:)
400
+ end
401
+
402
+ tp.success do |url:, rpath:, time:, code:|
403
+ times.push(time)
404
+ rps = (times.size / times.sum).round(4)
405
+ msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
406
+
407
+ case code
408
+ when 200
409
+ log(:info, msg)
410
+ stats[:success].push(url)
411
+ when 404
412
+ log(:error, msg)
413
+ stats[:missing].push(url)
414
+ else
415
+ log(:error, msg)
416
+ stats[:failure].push(url)
417
+ end
441
418
  end
442
419
  end
443
420
  end
444
421
 
445
- a = Time.now.to_f
422
+ borked = 0
423
+
424
+ if stats[:missing].size > 0
425
+ borked += stats[:missing].size
426
+ log(:error, "missing on #{ stats[:missing].size } url(s)")
427
+ end
428
+
429
+ if stats[:failure].size > 0
430
+ borked += stats[:failure].size
431
+ log(:error, "failure on #{ stats[:failure].size } url(s)")
432
+ end
433
+
434
+ if borked > 0
435
+ exit(borked)
436
+ end
437
+
438
+ rps = (times.size / times.sum).round(4)
446
439
 
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)
440
+ log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
441
+
442
+ @urls
443
+ end
444
+ =end
445
+
446
+ #
447
+ def parallel_build!
448
+ size = @parallel
453
449
 
454
- _a = Time.now.to_f
450
+ stats = {
451
+ :success => [],
452
+ :missing => [],
453
+ :failure => [],
454
+ }
455
455
 
456
- code, body = http_get(uri)
456
+ times = []
457
457
 
458
- _b = Time.now.to_f
459
- _e = (_b - _a).round(2)
458
+ Parallel.each(@urls, in_threads: 8) do |url|
459
+ uri = uri_for(url)
460
+ path = path_for(uri)
460
461
 
461
- times.push(_e)
462
+ rpath = relative_path(path, :from => @directory)
462
463
 
463
- label = "#{ code } @ (t̄:#{ avg }s)"
464
+ code = nil
465
+ body = nil
464
466
 
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]
467
+ time =
468
+ timing do
469
+ code, body = http_get(uri)
470
+ write_path(path, body) if code == 200
479
471
  end
480
- end
481
-
482
- _stats.each{|stat, url| stats[stat].push(url)}
483
472
 
484
- b = Time.now.to_f
473
+ times.push(time)
474
+ ts = times.dup
475
+ rps = (ts.size / ts.sum).round(4)
476
+ msg = "#{ url } -> /#{ rpath } (time:#{ time }, rps:#{ rps }, code:#{ code })"
477
+
478
+ case code
479
+ when 200
480
+ log(:info, msg)
481
+ stats[:success].push(url)
482
+ when 404
483
+ log(:error, msg)
484
+ stats[:missing].push(url)
485
+ else
486
+ log(:error, msg)
487
+ stats[:failure].push(url)
488
+ end
489
+ end
485
490
 
486
- borked = false
491
+ borked = 0
487
492
 
488
493
  if stats[:missing].size > 0
489
- borked = true
490
- log(:error, "missing on #{ stats[:missing].size } urls")
494
+ borked += stats[:missing].size
495
+ log(:error, "missing on #{ stats[:missing].size } url(s)")
491
496
  end
492
497
 
493
498
  if stats[:failure].size > 0
494
- borked = true
495
- log(:error, "failure on #{ stats[:failure].size } urls")
499
+ borked += stats[:failure].size
500
+ log(:error, "failure on #{ stats[:failure].size } url(s)")
496
501
  end
497
502
 
498
- if borked
499
- exit(1)
503
+ if borked > 0
504
+ exit(borked)
500
505
  end
501
506
 
502
- elapsed = b - a
503
- n = @urls.size
504
- rps = (n / elapsed).round(2)
507
+ rps = (times.size / times.sum).round(4)
505
508
 
506
- log(:info, "downloaded #{ n } urls at ~#{ rps }/s")
509
+ log(:info, "downloaded #{ @urls.size } urls at ~#{ rps } rps")
507
510
 
508
511
  @urls
509
512
  end
@@ -511,39 +514,50 @@ module RailsBuild
511
514
  #
512
515
  def finalize!
513
516
  @finished_at = Time.now
517
+
514
518
  elapsed = (@finished_at.to_f - @started_at.to_f)
519
+
515
520
  log(:info, "build time - #{ hms(elapsed) }")
521
+
522
+ # because netlify refuses to deploy from a symlink!
516
523
  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
524
+
525
+ cp = on_netlify ? 'cp_r' : 'ln_s'
526
+
518
527
  build = File.join(@rails_root, 'build')
528
+
519
529
  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
- )
530
+ FileUtils.send(cp, @directory, build)
531
+
532
+ #log(:info, "to preview run: ruby -run -ehttpd ./build/ -p4242")
533
+ end
534
+
535
+ def timing(&block)
536
+ t = Time.now.to_f
537
+
538
+ block.call
539
+
540
+ (Time.now.to_f - t).round(2)
535
541
  end
536
542
 
537
543
  def http_get(url)
538
- request = Net::HTTP::Get.new(url)
539
- response = http_connection.request(request)
544
+ uri = URI.parse(url.to_s)
545
+
546
+ response =
547
+ begin
548
+ Net::HTTP.get_response(uri)
549
+ rescue
550
+ [code = 500, body = '']
551
+ end
540
552
 
541
553
  if response.is_a?(Net::HTTPRedirection)
542
554
  location = response['Location']
555
+
543
556
  if location.to_s == url.to_s
544
557
  log(:fatal, "circular redirection on #{ url }")
545
558
  exit(1)
546
559
  end
560
+
547
561
  return http_get(location)
548
562
  end
549
563
 
@@ -566,18 +580,33 @@ module RailsBuild
566
580
  #
567
581
  def path_for(url)
568
582
  uri = uri_for(url)
583
+ path = nil
569
584
 
570
585
  case
571
586
  when uri.path=='/' || uri.path=='.'
572
587
  path = File.join(@directory, 'index.html')
588
+
573
589
  else
574
590
  path = File.join(@directory, uri.path)
591
+
575
592
  dirname, basename = File.split(path)
576
593
  base, ext = basename.split('.', 2)
577
- if ext.nil?
578
- path = File.join(path, 'index.html')
594
+
595
+ case
596
+ when uri.path.end_with?('/')
597
+ path =
598
+ File.join(path, 'index.html')
599
+
600
+ when ext.nil?
601
+ path =
602
+ if @config.fetch('index_html')
603
+ File.join(path, 'index.html')
604
+ else
605
+ path + '.html'
606
+ end
579
607
  end
580
608
  end
609
+
581
610
  path
582
611
  end
583
612
 
@@ -606,13 +635,14 @@ module RailsBuild
606
635
  uri = URI.parse(url.to_s)
607
636
 
608
637
  if uri.absolute?
638
+ uri.path = path
609
639
  uri.to_s
610
640
  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
641
+ rel = @url ? URI.parse(@url) : URI.parse('')
642
+ rel.path = absolute_path_for(uri.path)
643
+ rel.query = uri.query
644
+ rel.fragment = uri.fragment
645
+ rel.to_s
616
646
  end
617
647
  end
618
648
 
@@ -636,14 +666,6 @@ module RailsBuild
636
666
  [hours.to_i, minutes.to_s, seconds]
637
667
  end
638
668
 
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
669
  #
648
670
  def port_open?(port, options = {})
649
671
  seconds = options[:timeout] || 1
@@ -675,7 +697,8 @@ module RailsBuild
675
697
 
676
698
  #
677
699
  def absolute_path_for(*args)
678
- path = ('/' + paths_for(*args).join('/')).squeeze('/')
700
+ trailing_slash = args.join.end_with?('/') ? '/' : ''
701
+ path = ('/' + paths_for(*args).join('/') + trailing_slash).squeeze('/')
679
702
  path unless path.strip.empty?
680
703
  end
681
704
 
@@ -698,121 +721,199 @@ module RailsBuild
698
721
  end
699
722
 
700
723
  #
701
- def spawn(command)
702
- oe = `#{ command } 2>&1`
724
+ def spawn(arg, *args, **kws)
725
+ command = [arg, *args]
726
+
727
+ env = kws.fetch(:env){ {} }
728
+ error = kws.fetch(:error){ true }
729
+ quiet = kws.fetch(:quiet){ false }
730
+ stdin = kws.fetch(:stdin){ '' }
731
+
732
+ env.transform_keys!(&:to_s)
733
+ env.transform_values!(&:to_s)
734
+
735
+ pid = nil
736
+ status = nil
737
+ stdout = nil
738
+ stderr = nil
739
+
740
+ Tempfile.open do |i|
741
+ i.write(stdin)
742
+ i.flush
703
743
 
704
- unless $? == 0
705
- msg = "command(#{ command }) failed with (#{ $? })"
706
- log(:error, msg)
707
- raise(msg)
744
+ Tempfile.open do |o|
745
+ Tempfile.open do |e|
746
+ redirects = {:in => i.path, :out => o.path, :err => e.path}
747
+
748
+ pid = Process.spawn(env, *command, redirects)
749
+
750
+ Process.wait(pid)
751
+
752
+ status = $?.exitstatus
753
+
754
+ stdout = IO.binread(o.path)
755
+ stderr = IO.binread(e.path)
756
+ end
757
+ end
708
758
  end
709
759
 
710
- oe
760
+ unless status == 0
761
+ unless kws[:quiet] == true
762
+ log(:error, "#{ command.join(' ') } ###===>>> #{ status }\nSTDOUT:\n#{ stdout }\n\STDERR:\n#{ stderr }")
763
+ exit(status)
764
+ end
765
+ end
766
+
767
+ {command:, pid:, env:, status:, stdin:, stdout:, stderr:}
711
768
  end
712
769
  end
713
770
 
714
771
  #
715
772
  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)
773
+ attr_reader :pid
774
+
775
+ def initialize(cli:)
730
776
  @cli = cli
731
- end
732
777
 
733
- def setup!
734
- nil
778
+ @env = @cli.env
779
+ @directory = @cli.directory
780
+ @rails_root = @cli.rails_root
781
+ @parallel = @cli.parallel
782
+ @uuid = @cli.uuid
783
+
784
+ @thread = nil
785
+ @pid = nil
735
786
  end
736
787
 
737
- class Puma < Server
738
- def initialize(cli)
739
- @cli = cli
788
+ def start!(port:)
789
+ system("#{ version_command } >/dev/null 2>&1") ||
790
+ abort("app fails to load via: #{ version_command }")
740
791
 
741
- @env = @cli.env
742
- @directory = @cli.directory
743
- @rails_root = @cli.rails_root
744
- @parallel = @cli.parallel
792
+ q = Queue.new
745
793
 
746
- @puma = "bundle exec puma"
747
- @pumactl = "bundle exec pumactl"
794
+ cmd = start_command_for(port)
748
795
 
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
796
+ log = './tmp/rails_build_server.log'
752
797
 
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
798
+ @cli.log(:info, "server: #{ cmd } > #{ log } 2>&1")
799
+
800
+ @thread = Thread.new do
801
+ Thread.current.abort_on_exception = true
802
+ pipe = IO.popen("#{ cmd } > #{ log } 2>&1")
803
+ q.push(pipe.pid)
767
804
  end
768
805
 
769
- def stop_command_for(port)
806
+ @pid = q.pop
807
+
808
+ @cli.log(:info, "pid: #{ @pid }")
809
+
810
+ @assassin = Assassin.ate(@pid)
811
+
812
+ at_exit{ stop! }
813
+ end
814
+
815
+ def version_command
816
+ cmd_for(
770
817
  %W[
771
- #{ @pumactl }
772
- --pidfile=#{ @pidfile }
773
- --state=#{ @statefile }
774
- stop
775
- ].join(' ').strip
776
- end
818
+ RAILS_ENV=#{ @env }
819
+ DISABLE_SPRING=true
777
820
 
778
- def cleanup!(*args)
779
- nil
780
- end
821
+ rails --version
822
+ ]
823
+ )
781
824
  end
782
825
 
783
- class Passenger < Server
784
- def initialize(cli)
785
- @cli = cli
826
+ def start_command_for(port)
827
+ cmd_for(
828
+ %W[
829
+ RAILS_ENV=#{ @env }
830
+ DISABLE_SPRING=true
786
831
 
787
- @env = @cli.env
788
- @directory = @cli.directory
789
- @rails_root = @cli.rails_root
790
- @parallel = @cli.parallel
832
+ RAILS_BUILD=#{ @uuid }
791
833
 
792
- @passenger = "bundle exec passenger"
793
- end
834
+ RAILS_SERVE_STATIC_FILES=true
835
+ RAILS_LOG_TO_STDOUT=true
836
+ WEB_CONCURRENCY=#{ @parallel.to_s }
794
837
 
795
- def start_command_for(port)
796
- "#{ @passenger } start --daemonize --environment #{ @env } --port #{ port } --max-pool-size #{ @parallel }"
797
- end
838
+ rails server
798
839
 
799
- def stop_command_for(port)
800
- "#{ @passenger } stop --port #{ port }"
801
- end
840
+ --environment=#{ @env }
841
+ --port=#{ port }
842
+ --binding=0.0.0.0
843
+ --log-to-stdout
844
+ ]
845
+ )
846
+ end
802
847
 
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
848
+ def cmd_for(arg, *args)
849
+ [arg, *args].flatten.compact.join(' ').squeeze(' ').strip
850
+ end
808
851
 
809
- def setup!
852
+ def stop!
853
+ kill!(@pid)
854
+ @thread.kill
855
+ @cli.log(:info, "stopped: #{ @pid }")
856
+ end
857
+
858
+ def kill!(pid)
859
+ 42.times do
810
860
  begin
811
- require 'phusion_passenger'
812
- rescue LoadError => le
813
- abort "please add `gem 'passenger'` to your Gemfile"
861
+ Process.kill(0, pid)
862
+ return(true)
863
+ rescue Object => e
864
+ if e.is_a?(Errno::ESRCH)
865
+ Process.kill(-15, pid) rescue nil
866
+ sleep(rand + rand)
867
+ Process.kill(-9, pid) rescue nil
868
+ end
814
869
  end
870
+ sleep(0.42 + rand)
815
871
  end
872
+ return(false)
816
873
  end
817
874
  end
818
875
  end
876
+
877
+ END {
878
+ require_relative '../lib/rails_build.rb'
879
+
880
+ STDOUT.sync = true
881
+ STDERR.sync = true
882
+
883
+ RailsBuild::CLI.run!
884
+ }
885
+
886
+ __END__
887
+ <<~________
888
+
889
+ this file should to enumerate all the urls you'd like to build
890
+
891
+ the contents of your ./public directory, and any assets, are automaticaly included
892
+
893
+ therefore you need only declare which dynamic urls, that is to say, 'routes'
894
+
895
+ you would like included in your build
896
+
897
+ it is not loaded except during build time, and will not affect your normal rails app in any way
898
+
899
+ ________
900
+
901
+
902
+ RailsBuild.configure do |config|
903
+
904
+ # most of the time you are going to want your route included, which will
905
+ # translate into an ./index.html being output in the build
906
+ #
907
+
908
+ config.urls << '/'
909
+
910
+ # include any/all additional routes youd' like built thusly
911
+ #
912
+
913
+ Post.each do |post|
914
+ config.urls << "/posts/#{ post.id }"
915
+ end
916
+
917
+ # thats it! - now just run `rails_build` and you are GTG
918
+
919
+ end