hu 1.6.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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