hu 1.6.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/hu.gemspec +2 -0
  3. data/lib/hu/deploy.rb +429 -86
  4. data/lib/hu/version.rb +1 -1
  5. metadata +30 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c9f8f593da28d57abc1cacbc159c5a349f42773
4
- data.tar.gz: 8b64875a8ef6dd8a9c7d211febad66d2239124ca
3
+ metadata.gz: d7cc26af0d32047802199428e01f0ba865a44f4a
4
+ data.tar.gz: abc0ac52217ac81fb9ee3c3bb15970df9675afa5
5
5
  SHA512:
6
- metadata.gz: 387f58e52abf0ba4f9a8f3d7a4ec7d90a390dda2b935ecb599c351e85b3ed9d7569e0bcaca2e228c0ac0a368afeb03452bf4e9627a22bd91952950580d5d7a81
7
- data.tar.gz: 29dff580a9fb107d95122a32c21675d8271a063b110f2f7a121c946441d904fc16376671ad84a71d815ff1e3fa19a6c5a809e342bbc5fc87c74a88cbde49624b
6
+ metadata.gz: 90ba3f3689b51068377b7c6fed9a5f88dff552499caaf7e29d2e4a41f56569de865d06bfe709bd8240aa6bb63bb19e2bf13858843567cdf684f47413b901beca
7
+ data.tar.gz: 21801cab2013fce68791d172245f7d65e76f5862ea470da43602c32d30454ea1afb3bae3f5d8c63ffe3ea1d430d656c8391f7808b93430d4e0819299bed201b9
data/hu.gemspec CHANGED
@@ -34,6 +34,8 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency 'tty-prompt', '~> 0.13.2'
35
35
  spec.add_dependency 'tty-spinner', '= 0.3.0'
36
36
  spec.add_dependency 'tty-table', '~> 0.9.0'
37
+ spec.add_dependency 'fidget', '~> 0.0.6'
38
+ spec.add_dependency 'octokit'
37
39
  spec.add_dependency 'tty-cursor'
38
40
  spec.add_dependency 'rainbow'
39
41
  spec.add_dependency 'netrc', '= 0.11.0'
@@ -7,12 +7,27 @@ module Hu
7
7
  ::TTY::Formats::FORMATS[:hu] = { frames: '🌑🌒🌓🌔🌕🌖🌗🌘'.chars, interval: 10 }
8
8
  ::TTY::Formats::FORMATS[:huroku] = { frames: '⣷⣯⣟⡿⢿⣻⣽⣾'.chars, interval: 10 }
9
9
 
10
+ class SigQuit < StandardError; end
11
+
10
12
  RELEASE_TYPE_HINT = {
11
13
  'patch' => 'only bugfixes',
12
14
  'minor' => 'fully backwards compatible',
13
15
  'major' => 'not backwards compatible'
14
16
  }
15
17
 
18
+ NUMBERS = {
19
+ 0 => 'Zero',
20
+ 1 => 'One',
21
+ 2 => 'Two',
22
+ 3 => 'Three',
23
+ 4 => 'Four',
24
+ 5 => 'Five',
25
+ 6 => 'Six',
26
+ 7 => 'Seven',
27
+ 8 => 'Eight',
28
+ 9 => 'Nine'
29
+ }
30
+
16
31
  $stdout.sync
17
32
  @@shutting_down = false
18
33
  @@spinner = nil
@@ -27,48 +42,57 @@ module Hu
27
42
  text "\e[1mWARNING: Environment variable 'HEROKU_API_KEY' must be set.\e[0m"
28
43
  end
29
44
  filter do
30
- if Hu::API_TOKEN.nil?
31
- STDERR.puts "\e[0;31;1mERROR: Environment variable 'HEROKU_API_KEY' must be set.\e[0m"
45
+ begin
46
+ if Hu::API_TOKEN.nil?
47
+ STDERR.puts "\e[0;31;1mERROR: Environment variable 'HEROKU_API_KEY' must be set.\e[0m"
48
+ exit 1
49
+ end
50
+ require 'tty-cursor'
51
+ print TTY::Cursor.hide + "\e[30;1ms"
52
+ require 'rainbow'
53
+ print 'y'
54
+ require 'rainbow/ext/string'
55
+ print 'n'
56
+ require 'platform-api'
57
+ print 'c'
58
+ require 'version_sorter'
59
+ print 'h'
60
+ require 'versionomy'
61
+ print 'r'
62
+ require 'tty-prompt'
63
+ print 'o'
64
+ require 'tty-table'
65
+ require 'octokit'
66
+ print 'n'
67
+ require 'open3'
68
+ require 'fidget'
69
+ print 'i'
70
+ require 'json'
71
+ require 'awesome_print'
72
+ print 'z'
73
+ require 'chronic_duration'
74
+ require 'tempfile'
75
+ print 'i'
76
+ require 'thread_safe'
77
+ require 'io/console'
78
+ print 'n'
79
+ require 'rugged'
80
+ require 'pty'
81
+ print 'g'
82
+ require 'thread'
83
+ require 'paint'
84
+ require 'lolcat/lol'
85
+ require 'io/console'
86
+ rescue Interrupt
87
+ puts "\e[0m*** Abort (SIGINT)"
88
+ puts TTY::Cursor.show
32
89
  exit 1
33
90
  end
34
- require 'tty-cursor'
35
- print TTY::Cursor.hide + "\e[30;1ms"
36
- require 'rainbow'
37
- print 'y'
38
- require 'rainbow/ext/string'
39
- print 'n'
40
- require 'platform-api'
41
- print 'c'
42
- require 'version_sorter'
43
- print 'h'
44
- require 'versionomy'
45
- print 'r'
46
- require 'tty-prompt'
47
- print 'o'
48
- require 'tty-table'
49
- print 'n'
50
- require 'open3'
51
- print 'i'
52
- require 'json'
53
- require 'awesome_print'
54
- print 'z'
55
- require 'chronic_duration'
56
- require 'tempfile'
57
- print 'i'
58
- require 'thread_safe'
59
- require 'io/console'
60
- print 'n'
61
- require 'rugged'
62
- require 'pty'
63
- print 'g'
64
- require 'thread'
65
- require 'paint'
66
- require 'lolcat/lol'
67
- require 'io/console'
68
91
  end
69
92
 
70
93
  def deploy(_cmd, _opts, _argv)
71
94
  trap('INT') { shutdown; puts "\e[0m\e[35;1m^C\e[0m"; exit 1 }
95
+
72
96
  at_exit do
73
97
  if $!.class == SystemExit && 130 == $!.status
74
98
  puts "\n\n"
@@ -287,7 +311,7 @@ EOM
287
311
  puts
288
312
 
289
313
  unless git_revisions[:release] == git_revisions[stag_app_name] || !release_branch_exists
290
- puts ' Phase 1/3 '.inverse + ' The local release branch ' + "release/#{release_tag}".bright + ' was created.'
314
+ puts ' Phase 1/2 '.inverse + ' The local release branch ' + "release/#{release_tag}".bright + ' was created.'
291
315
  puts ' Nothing else has happened so far. Push this branch to'
292
316
  puts ' ' + stag_app_name.to_s.bright + ' to begin the deploy procedure.'
293
317
  puts
@@ -296,24 +320,25 @@ EOM
296
320
  if release_branch_exists && git_revisions[:release] == git_revisions[stag_app_name]
297
321
  hyperlink = "\e]8;;#{app['web_url']}\007#{app['web_url']}\e]8;;\007"
298
322
 
299
- puts ' Phase 2/3 '.inverse + ' Your local ' + "release/#{release_tag}".bright + ' (formerly ' + 'develop'.bright + ') is live on ' + stag_app_name.to_s.bright + '.'
323
+ puts ' Phase 2/2 '.inverse + ' Your local ' + "release/#{release_tag}".bright + ' (formerly ' + 'develop'.bright + ') is live on ' + stag_app_name.to_s.bright + '.'
300
324
  puts
301
325
  puts ' Please test here: ' + hyperlink.bright
302
326
  puts
303
- puts ' If everything looks good you may proceed and finish the release.'
327
+ puts ' If everything looks good you may proceed and deploy to production.'
304
328
  puts ' If there are problems: Quit, delete the release branch and start fixing.'
305
329
  puts
306
330
  elsif git_revisions[prod_app_name] != git_revisions[stag_app_name] && !release_branch_exists && git_revisions[:release] != git_revisions[stag_app_name]
307
331
  hyperlink = "\e]8;;#{app['web_url']}\007#{app['web_url']}\e]8;;\007"
308
332
 
309
- puts ' Phase 3/3 '.inverse + ' HEADS UP! This is the last chance to detect problems.'
310
- puts ' The final version of ' + "release/#{release_tag}".bright + ' is now staged.'
333
+ puts ' DEPLOY '.inverse + ' HEADS UP! This is the last chance to detect problems.'.bright
334
+ puts ' The final version of ' + "release/#{release_tag}".bright + ' is staged.'
311
335
  puts
312
- puts ' Test here: ' + hyperlink.bright
313
- sleep 1
336
+ puts ' Test here: ' + hyperlink.bright
337
+ sleep 0.1
338
+ puts
339
+ puts ' This is the exact version that will be promoted to production.'
340
+ type " From here you are on your own. Good luck #{`whoami`.chomp}!"
314
341
  puts
315
- puts ' This is the exact version that will be promoted to production.'
316
- puts " From here you are on your own. Good luck #{`whoami`.chomp}!"
317
342
  puts
318
343
  end
319
344
 
@@ -323,7 +348,7 @@ EOM
323
348
  menu.choice 'Refresh', :refresh
324
349
  menu.choice 'Quit', :abort_ask
325
350
  unless git_revisions[:release] == git_revisions[stag_app_name] || !release_branch_exists
326
- menu.choice "Push release/#{release_tag} to #{stag_app_name}", :push_to_staging
351
+ menu.choice "Push develop to origin/develop and release/#{release_tag} to #{stag_app_name}", :push_to_staging
327
352
  end
328
353
  if release_branch_exists
329
354
  unless release_tag == tiny_bump
@@ -339,54 +364,147 @@ EOM
339
364
  end
340
365
 
341
366
  if git_revisions[:release] == git_revisions[stag_app_name]
342
- menu.choice 'Finish release (merge, tag and final stage)', :finish_release
367
+ menu.choice "DEPLOY to #{prod_app_name}", :finish_release
343
368
  end
344
369
  elsif git_revisions[prod_app_name] != git_revisions[stag_app_name]
345
370
  menu.choice "DEPLOY (promote #{stag_app_name} to #{prod_app_name})", :DEPLOY
346
371
  end
347
372
  end
348
- rescue TTY::Prompt::Reader::InputInterrupt
373
+ rescue TTY::Reader::InputInterrupt
349
374
  choice = :abort
350
375
  puts "\n\n"
351
376
  end
352
377
 
353
378
  case choice
354
379
  when :DEPLOY
355
- promote_to_production
380
+ Fidget.prevent_sleep(:display, :sleep, :user) do
381
+ promote_to_production
382
+ end
356
383
  anykey
357
384
  when :finish_release
358
- old_editor = ENV['EDITOR']
359
- old_git_editor = ENV['GIT_EDITOR']
360
- tf = Tempfile.new('hu-tag')
361
- tf.write "#{release_tag}\n#{changelog}"
362
- tf.close
363
- ENV['EDITOR'] = ENV['GIT_EDITOR'] = "cp #{tf.path}"
364
- env = {
365
- 'PREVIOUS_TAG' => highest_version,
366
- 'RELEASE_TAG' => release_tag,
367
- 'GIT_MERGE_AUTOEDIT' => 'no'
368
- }
369
- unless 0 == finish_release(release_tag, env, tf.path)
370
- abort_merge
371
- puts '*** ERROR! Could not finish release *** '.color(:red)
372
- puts
373
- puts 'This usually means a merge conflict or'
374
- puts 'something equally annoying has occured.'
375
- puts
376
- puts 'Please bring the universe into a state'
377
- puts 'where the above sequence of commands can'
378
- puts 'succeed. Then try again.'
379
- puts
380
- exit 1
385
+ Fidget.prevent_sleep(:display, :sleep, :user) do
386
+ if ci_clear?
387
+ old_editor = ENV['EDITOR']
388
+ old_git_editor = ENV['GIT_EDITOR']
389
+ tf = Tempfile.new('hu-tag')
390
+ tf.write "#{release_tag}\n#{changelog}"
391
+ tf.close
392
+ ENV['EDITOR'] = ENV['GIT_EDITOR'] = "cp #{tf.path}"
393
+ env = {
394
+ 'PREVIOUS_TAG' => highest_version,
395
+ 'RELEASE_TAG' => release_tag,
396
+ 'GIT_MERGE_AUTOEDIT' => 'no'
397
+ }
398
+ unless 0 == finish_release(release_tag, env, tf.path)
399
+ abort_merge
400
+ puts '*** ERROR! Could not finish release *** '.color(:red)
401
+ puts
402
+ puts 'This usually means a merge conflict or'
403
+ puts 'something equally annoying has occured.'
404
+ puts
405
+ puts 'Please bring the universe into a state'
406
+ puts 'where the above sequence of commands can'
407
+ puts 'succeed. Then try again.'
408
+ puts
409
+ exit 1
410
+ end
411
+ ENV['EDITOR'] = old_editor
412
+ ENV['GIT_EDITOR'] = old_git_editor
413
+
414
+ promote_to_production
415
+ promoted_at = Time.now.to_i
416
+
417
+ formation = h.formation.info(prod_app_name, 'web')
418
+ dyno_count = formation['quantity']
419
+
420
+ phase = :init
421
+ want = dyno_count
422
+ have = 0
423
+ release_rev = `git rev-parse develop`[0..7]
424
+ parser = Proc.new do |line, pid|
425
+ source, line = line.chomp.split(' ', 2)[1].split(' ', 2)
426
+ source = /\[(.*)\]:/.match(source)[1]
427
+ prefix = "\e[0m"
428
+ case phase
429
+ when :init
430
+ if line =~ /Deploy #{release_rev}/
431
+ phase = :observe
432
+ end
433
+ when :observe
434
+ if line =~ /State changed from starting to crashed/
435
+ prefix = "\e[31;1m"
436
+ elsif line =~ /State changed from starting to up/
437
+ prefix = "\e[32;1m"
438
+ have += 1
439
+ end
440
+
441
+ t = Time.now.to_i - promoted_at
442
+ ts = sprintf("%02d:%02d", t / 60, t % 60)
443
+ print "\e[30;1m[\e[0;33mT+#{ts} #{prefix}#{have}\e[0m/#{prefix}#{want}\e[30;1m] \e[0m"
444
+ print "#{source}: " unless source == 'api'
445
+ print prefix + line
446
+ puts
447
+
448
+ if have >= want
449
+ Process.kill("TERM", pid)
450
+ end
451
+ end
452
+ end
453
+
454
+ puts
455
+ puts "\e[0;1m# Observe startup\e[0m"
456
+ if h.app_feature.info(prod_app_name, 'preboot').dig('enabled')
457
+ puts <<EOF
458
+ #
459
+ # \e[0m\e]8;;https://devcenter.heroku.com/articles/preboot\007Preboot\e]8;;\007 is \e[32;1menabled\e[0m for \e[1m#{prod_app_name}\e[0m.
460
+ #
461
+ # #{NUMBERS[formation['quantity']] || formation['quantity']} new dyno#{formation['quantity'] == 1 ? '' : 's'} (\e[1m#{formation['size']}\e[0m) #{formation['quantity'] == 1 ? 'is' : 'are'} starting up.
462
+ # The old dynos will shut down within 3 minutes.
463
+ EOF
464
+ end
465
+
466
+ script = <<-EOS.strip_heredoc
467
+ :stream
468
+ :quiet
469
+ :failquiet
470
+ :nospinner
471
+ :return
472
+ heroku logs --tail -a #{prod_app_name} | grep -E "heroku\\[|app\\[api\\]"
473
+ EOS
474
+
475
+ sigint_handler = Proc.new do
476
+ puts
477
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
478
+
479
+ puts
480
+ puts "\e[43m \e[0m \e[0;32mRelease \e[1m#{release_tag}\e[0;32m is being launched on \e[1m#{prod_app_name}\e[0;32m\e[0m \e[43m \e[0m"
481
+ puts
482
+
483
+ shutdown
484
+
485
+ exit 0
486
+ end
487
+
488
+ run_each(script, parser: parser, sigint_handler: sigint_handler)
489
+
490
+ puts
491
+ print"\a"; sleep 0.4
492
+ puts "\e[42m \e[0m \e[0;32mRelease \e[1m#{release_tag}\e[0;32m has been deployed to \e[1m#{prod_app_name}\e[0;32m\e[0m \e[42m \e[0m"
493
+ print"\a"; sleep 0.4
494
+ puts
495
+ print"\a"; sleep 0.4
496
+
497
+ exit 0
498
+ end
381
499
  end
382
- ENV['EDITOR'] = old_editor
383
- ENV['GIT_EDITOR'] = old_git_editor
384
- anykey
385
500
  when :push_to_staging
386
- run_each <<-EOS.strip_heredoc
387
- :stream
388
- git push #{push_url} release/#{release_tag}:master -f
389
- EOS
501
+ Fidget.prevent_sleep(:display, :sleep, :user) do
502
+ run_each <<-EOS.strip_heredoc
503
+ :stream
504
+ git push origin develop
505
+ git push #{push_url} release/#{release_tag}:master -f
506
+ EOS
507
+ end
390
508
  anykey
391
509
  when :abort_ask
392
510
  puts if delete_branch("release/#{release_tag}")
@@ -411,6 +529,169 @@ EOM
411
529
  end
412
530
  end
413
531
 
532
+ def type(text, delay=0.01)
533
+ text.chars.each do |c|
534
+ print c
535
+ sleep rand * delay unless c == ' '
536
+ end
537
+ end
538
+
539
+ def ci_info
540
+ puts
541
+ if ENV['HU_GITHUB_ACCESS_TOKEN'].nil?
542
+ msg = "ERROR: Environment variable 'HU_GITHUB_ACCESS_TOKEN' must be set."
543
+ else
544
+ msg = 'ERROR: Github access token is invalid or has insufficient permissions'
545
+ end
546
+ puts msg.color(:red)
547
+ puts <<EOF
548
+
549
+ 1. Go to \e]8;;https://github.com/settings/tokens\007https://github.com/settings/tokens\e]8;;\007
550
+
551
+ 2. Click on [Generate new token]
552
+
553
+ 3. Create a token with (only) the following permissions:
554
+
555
+ - repo:status
556
+ - repo_deployment
557
+ - public_repo
558
+ - read:user
559
+
560
+ 4. Add the following line to your shell environment (e.g. ~/.bash_profile):
561
+
562
+ \e[1mexport HU_GITHUB_ACCESS_TOKEN=<your_token>\e[0m
563
+
564
+ EOF
565
+ end
566
+
567
+ def ci_status(release_branch_exists=true)
568
+ okit = Octokit::Client.new(access_token: ENV['HU_GITHUB_ACCESS_TOKEN'])
569
+
570
+ repo_name = @git.remotes['origin'].url.split(':')[1].gsub('.git', '')
571
+
572
+ begin
573
+ raw_status_develop = status_develop = okit.status(repo_name, @git.branches["origin/develop"].target_id)
574
+ status_develop = status_develop[:statuses].empty? ? ' ' : status_develop[:state]
575
+ status_develop = ' ' if @git.branches["origin/develop"].target_id != @git.branches["develop"].target_id
576
+ status_develop = ' ' unless release_branch_exists
577
+ rescue Octokit::NotFound, Octokit::Unauthorized
578
+ return :error
579
+ end
580
+
581
+ begin
582
+ # status_master = okit.status(repo_name, @git.branches["origin/master"].target_id)
583
+ # p status_master
584
+ # status_master = status_master[:statuses].empty? ? 'n/a' : status_master[:state]
585
+ # status_master = ' ' if @git.branches["origin/master"].target_id != @git.branches["master"].target_id
586
+ status_master = ' '
587
+ rescue Octokit::NotFound
588
+ status_master = 'unknown'
589
+ end
590
+
591
+ {
592
+ master: status_master,
593
+ develop: status_develop,
594
+ raw_develop: raw_status_develop
595
+ }
596
+ end
597
+
598
+ def ci_symbol(value)
599
+ case value
600
+ when ' '
601
+ ''
602
+ when 'pending'
603
+ ' 🐌'
604
+ when 'success'
605
+ ' ✅'
606
+ else
607
+ ' ❌'
608
+ end
609
+ end
610
+
611
+ def ci_clear?
612
+ return true if ENV['HU_GITHUB_ACCESS_TOKEN'].nil?
613
+ msg = ''
614
+ prefix = "CI: "
615
+ ci_develop = ''
616
+ begin_wait_at = Time.now - 1
617
+ i = 0
618
+ puts
619
+ Signal.trap('QUIT') { raise SigQuit }
620
+ Signal.trap('INT') { raise Interrupt }
621
+ while ci_develop != 'success' do
622
+ puts
623
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.hide
624
+ print prefix
625
+ print ('-' * (ci_develop.length + 2)) unless i == 0
626
+ print msg unless i == 0
627
+ ci_develop = ci_status(true)[:develop] if i % 10 == 0
628
+ # ci_develop = 'failed'
629
+
630
+ puts
631
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
632
+
633
+ print TTY::Cursor.hide + "\n" + TTY::Cursor.up
634
+ msg = " - press ^\\ to override, ^C to abort (#{ChronicDuration.output((Time.now - begin_wait_at).to_i, format: :short)}) "
635
+
636
+ status = ci_develop.upcase
637
+ case status
638
+ when 'PENDING'
639
+ status = "\e[44;33;1m PENDING \e[0m"
640
+ when 'SUCCESS'
641
+ status = "\e[42;30;1m CLEAR \e[0m"
642
+ when ' '
643
+ status = "\e[40;30;1m UNCONFIGURED \e[0m"
644
+ else
645
+ status = "\e[41;33;1m #{status} \e[0m"
646
+ end
647
+ print prefix + status
648
+ print msg unless ci_develop == 'success' || ci_develop == ' '
649
+
650
+ # key = nil
651
+
652
+ sleep 1
653
+
654
+ break if ci_develop == ' '
655
+ # catch :sigint do
656
+ # begin
657
+ # Timeout::timeout(1) do
658
+ # Signal.trap('INT') { throw :sigint }
659
+ # key = STDIN.getch
660
+ # end
661
+ # rescue Timeout::Error
662
+ # key = :timeout
663
+ # ensure
664
+ # Signal.trap('INT', 'DEFAULT')
665
+ # end
666
+ # end
667
+ # if key == "\u0003"
668
+ # puts
669
+ # print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
670
+ # return false
671
+ # end
672
+
673
+ i += 1
674
+ end
675
+ puts
676
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
677
+ print TTY::Cursor.up
678
+ return true
679
+ rescue Interrupt
680
+ puts
681
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
682
+ return false
683
+ rescue SigQuit
684
+ puts
685
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
686
+ puts "CI: \e[41;33;1m OVERRIDE \e[0m"
687
+ return true
688
+ ensure
689
+ Signal.trap('QUIT', 'DEFAULT')
690
+ Signal.trap('INT', 'DEFAULT')
691
+ puts
692
+ print TTY::Cursor.up + TTY::Cursor.clear_line + TTY::Cursor.show
693
+ end
694
+
414
695
  def show_pipeline_status(pipeline_name, stag_app_name, prod_app_name, release_tag, clear = true)
415
696
  table = TTY::Table.new header: ['', 'commit', 'tag', 'last_modified', 'last_modified_by', 'dynos', '']
416
697
  busy 'synchronizing', :dots
@@ -493,6 +774,17 @@ EOM
493
774
  end
494
775
  end
495
776
 
777
+ ci = Thread.new do
778
+ ci_status(branch_exists?("release/#{release_tag}"))
779
+ end
780
+
781
+ ci = ci.value
782
+ if ci == :error
783
+ unbusy
784
+ ci_info
785
+ exit 1
786
+ end
787
+
496
788
  workers.each(&:join)
497
789
 
498
790
  rows = []
@@ -506,6 +798,7 @@ EOM
506
798
  revs[:master] = row[1] = `git rev-parse master`[0..5]
507
799
  row[2] = `git tag --points-at master`
508
800
  row[1] = row[1].color(row[1] == revs[:develop] ? :green : :red)
801
+ # row[3] = color_ci(ci[:master])
509
802
  rows.unshift row
510
803
 
511
804
  if branch_exists? "release/#{release_tag}"
@@ -517,10 +810,19 @@ EOM
517
810
  rows.unshift row
518
811
  end
519
812
 
813
+ row = tpl_row.dup
814
+ row[0] = 'origin/develop'
815
+ row[1] = `git rev-parse origin/develop`[0..5]
816
+ row[1] = row[1].color(row[1] == revs[:develop] ? :green : :red) + ci_symbol(ci[:develop])
817
+ row[2] = `git tag --points-at origin/develop`
818
+ # row[3] = color_ci(ci[:develop])
819
+ rows.unshift row
820
+
520
821
  row = tpl_row.dup
521
822
  row[0] = 'develop'
522
823
  row[1] = revs[:develop].color(:green)
523
824
  row[2] = `git tag --points-at develop`
825
+ # row[3] = color_ci(ci[:develop]) if
524
826
  rows.unshift row
525
827
 
526
828
  unbusy
@@ -557,7 +859,24 @@ EOM
557
859
  end
558
860
  end
559
861
 
862
+ # p ci
863
+ if ci[:develop] != ' '
864
+ ci[:raw_develop][:statuses].each do |status|
865
+ if ['failure', 'error'].include? status[:state]
866
+ puts
867
+ print " CI #{status[:state].upcase} ".background(:red).color(:yellow).bright
868
+ print " \033]1337;RequestAttention=fireworks\a"
869
+ sleep 1
870
+ puts "#{status[:description]}".bright
871
+ puts " " + (" " * status[:state].length) + status[:target_url]
872
+ end
873
+ end
874
+ end
875
+
560
876
  revs
877
+ rescue Interrupt
878
+ puts "*** Abort"
879
+ exit 1
561
880
  end
562
881
 
563
882
  def heroku_app_by_git(git_url)
@@ -603,10 +922,14 @@ EOM
603
922
  opts = {
604
923
  quiet: false,
605
924
  failfast: true,
925
+ failquiet: false,
606
926
  spinner: true,
607
- stream: false
927
+ stream: false,
928
+ parser: nil
608
929
  }.merge(opts)
609
930
 
931
+ parser = opts[:parser]
932
+
610
933
  @spinlock ||= Mutex.new # :P
611
934
  script.lines.each_with_index do |line, i|
612
935
  line.chomp!
@@ -616,6 +939,7 @@ EOM
616
939
  when ':'
617
940
  opts[:quiet] = true if line == ':quiet'
618
941
  opts[:failfast] = false if line == ':return'
942
+ opts[:failquiet] = true if line == ':failquiet'
619
943
  opts[:spinner] = false if line == ':nospinner'
620
944
  if line == ':stream'
621
945
  opts[:stream] = true
@@ -633,7 +957,7 @@ EOM
633
957
  @tspin ||= Thread.new do
634
958
  i = 0
635
959
  loop do
636
- break if @minispin_last_char_at == :end
960
+ break if @minispin_last_char_at == :end || @shutdown
637
961
  begin
638
962
  if 0.23 > Time.now - @minispin_last_char_at || @minispin_disable
639
963
  sleep 0.1
@@ -658,10 +982,17 @@ EOM
658
982
 
659
983
  PTY.spawn("stty rows #{rows} cols #{cols}; " + line) do |r, _w, pid|
660
984
  begin
985
+ l = ''
661
986
  until r.eof?
662
987
  c = r.getc
663
988
  @spinlock.synchronize do
664
- print c
989
+ print c unless opts[:quiet]
990
+ if c == "\n" && parser
991
+ parser.call(l, pid)
992
+ l = ''
993
+ else
994
+ l += c
995
+ end
665
996
  @minispin_last_char_at = Time.now
666
997
  c = c.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: "\e") # barf.
667
998
  # hold on when we are (likely) inside an escape sequence
@@ -673,6 +1004,14 @@ EOM
673
1004
  # Linux raises EIO on EOF, cf.
674
1005
  # https://github.com/ruby/ruby/blob/57fb2199059cb55b632d093c2e64c8a3c60acfbb/ext/pty/pty.c#L519
675
1006
  nil
1007
+ rescue Interrupt
1008
+ if opts[:sigint_handler]
1009
+ opts[:sigint_handler].call
1010
+ else
1011
+ puts
1012
+ puts "*** Abort (SIGINT)"
1013
+ exit 1
1014
+ end
676
1015
  end
677
1016
 
678
1017
  _pid, status = Process.wait2(pid)
@@ -694,7 +1033,7 @@ EOM
694
1033
  end
695
1034
  next unless status.exitstatus != 0
696
1035
  shutdown if opts[:failfast]
697
- puts "Error, exit #{status.exitstatus}: #{line} (L#{i})".color(:red).bright
1036
+ puts "Error, exit #{status.exitstatus}: #{line} (L#{i})".color(:red).bright unless opts[:failquiet]
698
1037
 
699
1038
  exit status.exitstatus if opts[:failfast]
700
1039
  return status.exitstatus
@@ -734,7 +1073,11 @@ EOM
734
1073
 
735
1074
  def delete_branch(branch_name)
736
1075
  return false unless branch_exists? branch_name
737
- return false if TTY::Prompt.new.no?("Delete branch #{branch_name}?")
1076
+ begin
1077
+ return false if TTY::Prompt.new.no?("Delete branch #{branch_name}?")
1078
+ rescue TTY::Reader::InputInterrupt
1079
+ return false
1080
+ end
738
1081
  run_each <<-EOS.strip_heredoc
739
1082
  :quiet
740
1083
  # Delete branch #{branch_name}
@@ -1015,8 +1358,8 @@ EOM
1015
1358
  unbusy
1016
1359
  end
1017
1360
 
1018
- def anykey
1019
- unless ENV['HU_ANYKEY']
1361
+ def anykey(force=false)
1362
+ unless ENV['HU_ANYKEY'] || force
1020
1363
  puts
1021
1364
  return
1022
1365
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Hu
3
- VERSION = '1.6.5'
3
+ VERSION = '2.0.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hu
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.5
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - moe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-02 00:00:00.000000000 Z
11
+ date: 2018-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -192,6 +192,34 @@ dependencies:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
194
  version: 0.9.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: fidget
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.0.6
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.0.6
209
+ - !ruby/object:Gem::Dependency
210
+ name: octokit
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :runtime
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
195
223
  - !ruby/object:Gem::Dependency
196
224
  name: tty-cursor
197
225
  requirement: !ruby/object:Gem::Requirement