circleci-cli 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5210a33cd66ffb1d7cdf4294653d0a8b0098ad7755689ead6ff50419377937ca
4
- data.tar.gz: 16e6f04777f3046d6e0c67d3919d39713236da8a2cf9393497cf62b5310a271b
3
+ metadata.gz: 4823a93a5df3fed802d98f9131b3792b9fc7c51e371688c70bf09233f804a49c
4
+ data.tar.gz: 8ede76476f63b3bf97641fa996229de56c5f8177b624080fcba2eb6c2f9c945b
5
5
  SHA512:
6
- metadata.gz: 6166152b480e9accfbbb5321496a14b5fa92ea5277c629d4ce96ebbd4b5bfd520a803afebe713fcba873f2945e23f75743e24671155f018f157f7e373a50860f
7
- data.tar.gz: ec1d2459890466344fc641fad40b0e1375d997ce3979d6babf5259d4227124cd6c0c4976dbcdb7007ea3cfad656bc1218508f76a12edadceecf06b553902840e
6
+ metadata.gz: b01c02e60ad803dca24506568397c243c86f0cebe00d8b4cecc881ac0a39e299fd8410a355aae03f3c5763909ea7f095e11c08f3d0a2d2f7067544de5f5fcb5d
7
+ data.tar.gz: df4a2d04fc9d52ed27e64630c788773431d4ca6ded37879480c26d3e654133d1459d6a901b0bde633f577be56ea4ef67d9cc831d3506589c62fd3de592430102
@@ -8,9 +8,9 @@ update_bundler: &update_bundler
8
8
  bundle_install: &bundle_install
9
9
  run:
10
10
  name: bundle install
11
- command: bundle update --bundler && bundle install --path vendor/bundle --jobs 4
11
+ command: bundle install --path vendor/bundle --jobs 4
12
12
 
13
- restore_bundle_cache: &restore_bundle_cache
13
+ git statusrestore_bundle_cache: &restore_bundle_cache
14
14
  restore_cache:
15
15
  key: cache-bundler-{{ checksum "Gemfile.lock" }}
16
16
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- circleci-cli (1.0.0)
4
+ circleci-cli (2.0.0)
5
5
  circleci (~> 2.0.2)
6
6
  colorize (~> 0.8.1)
7
7
  faraday (>= 0.14, < 0.16)
@@ -133,4 +133,4 @@ DEPENDENCIES
133
133
  simplecov
134
134
 
135
135
  BUNDLED WITH
136
- 1.17.2
136
+ 2.0.2
@@ -64,6 +64,7 @@ module CircleCI
64
64
  desc 'watch', 'watch a build in real time'
65
65
  method_option :project, aliases: 'p', type: :string, banner: 'user/project'
66
66
  method_option :build, aliases: 'n', type: :numeric, banner: 'build-number'
67
+ method_option :verbose, aliases: 'v', type: :boolean, banner: 'verbose'
67
68
  def watch
68
69
  Command::WatchCommand.run(options)
69
70
  end
@@ -1,70 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'circleci/cli/command/watch_command/build_repository'
4
+ require 'circleci/cli/command/watch_command/build_watcher'
5
+
3
6
  module CircleCI
4
7
  module CLI
5
8
  module Command
6
9
  class WatchCommand < BaseCommand
7
10
  class << self
8
- def run(options)
11
+ def run(options) # rubocop:disable Metrics/MethodLength
9
12
  setup_token
10
- setup_client
11
13
 
12
- build = get_build(options)
14
+ @options = options
15
+ @repository = BuildRepository.new(*project_name(options).split('/'))
16
+ @client = Networking::CircleCIPusherClient.new.tap(&:connect)
17
+ @build_watcher = nil
18
+
19
+ bind_status_event
13
20
 
14
- if build&.running?
15
- start_watch(build)
16
- wait_until_finish
17
- finalize(build, build.channel_name)
18
- else
19
- say 'The build is not running'
21
+ loop do
22
+ stop_existing_watcher_if_needed
23
+ start_watcher_if_needed
24
+ sleep 1
20
25
  end
26
+ rescue Interrupt
27
+ say 'Exited'
21
28
  end
22
29
 
23
30
  private
24
31
 
25
- def setup_client
26
- @client = Networking::CircleCIPusherClient.new
27
- @client.connect
32
+ def bind_status_event
33
+ @client.bind("private-#{Response::Account.me.pusher_id}", 'call') { @repository.update }
28
34
  end
29
35
 
30
- def get_build(options)
31
- username, reponame = project_name(options).split('/')
32
- number = build_number options
33
- Response::Build.get(username, reponame, number)
34
- end
36
+ def stop_existing_watcher_if_needed
37
+ return if @build_watcher.nil?
35
38
 
36
- def start_watch(build)
37
- @running = true
38
- text = "Start watching #{build.project_name} ##{build.build_number}"
39
- print_bordered text
40
- TerminalNotifier.notify text
39
+ build = @repository.build_for(@build_watcher.build.build_number)
40
+ return if build.nil? || !build.finished?
41
41
 
42
- bind_event_handling build.channel_name
42
+ @build_watcher.stop(build.status)
43
+ @build_watcher = nil
44
+ show_interrupted_build_results
43
45
  end
44
46
 
45
- def bind_event_handling(channel)
46
- @client.bind_event_json(channel, 'newAction') do |json|
47
- print_bordered json['log']['name'].green
48
- end
49
-
50
- @client.bind_event_json(channel, 'appendAction') do |json|
51
- say json['out']['message']
52
- end
47
+ def start_watcher_if_needed
48
+ build_to_watch = @repository.builds_to_show.select(&:running?).first
49
+ return unless build_to_watch && @build_watcher.nil?
53
50
 
54
- @client.bind_event_json(channel, 'updateAction') do |json|
55
- @running = json['log']['name'] != 'Disable SSH'
56
- end
51
+ show_interrupted_build_results
52
+ @repository.mark_as_shown(build_to_watch.build_number)
53
+ @build_watcher = BuildWatcher.new(build_to_watch, verbose: @options.verbose)
54
+ @build_watcher.start
57
55
  end
58
56
 
59
- def wait_until_finish
60
- sleep(1) while @running
61
- end
62
-
63
- def finalize(build, channel)
64
- @client.unsubscribe(channel)
65
- text = "Finish watching #{build.project_name} ##{build.build_number}"
66
- print_bordered text.blue
67
- TerminalNotifier.notify text
57
+ def show_interrupted_build_results # rubocop:disable Metrics/AbcSize
58
+ @repository.builds_to_show.select(&:finished?).each do |build|
59
+ b = Response::Build.get(build.username, build.reponame, build.build_number)
60
+ title = "✅ Result of #{build.project_name} ##{build.build_number} completed in background".light_black
61
+ say Printer::BuildPrinter.header_for(build, title)
62
+ say Printer::StepPrinter.new(b.steps, pretty: @options.verbose).to_s
63
+ @repository.mark_as_shown(b.build_number)
64
+ end
68
65
  end
69
66
 
70
67
  def print_bordered(text)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CircleCI
4
+ module CLI
5
+ module Command
6
+ class BuildRepository
7
+ def initialize(username, reponame)
8
+ @username = username
9
+ @reponame = reponame
10
+ @builds = Response::Build.all(@username, @reponame)
11
+ @build_numbers_shown = @builds.select(&:finished?).map(&:build_number)
12
+ end
13
+
14
+ def update
15
+ @builds = (Response::Build.all(@username, @reponame) + @builds)
16
+ .uniq(&:build_number)
17
+ end
18
+
19
+ def mark_as_shown(build_number)
20
+ @build_numbers_shown = (@build_numbers_shown + [build_number]).uniq
21
+ end
22
+
23
+ def builds_to_show
24
+ @builds
25
+ .reject { |build| @build_numbers_shown.include?(build.build_number) }
26
+ .sort_by(&:build_number)
27
+ end
28
+
29
+ def build_for(build_number)
30
+ @builds.find { |build| build.build_number == build_number }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CircleCI
4
+ module CLI
5
+ module Command
6
+ class BuildWatcher
7
+ attr_reader :build
8
+
9
+ def initialize(build, verbose: false)
10
+ @client = Networking::CircleCIPusherClient.new.tap(&:connect)
11
+ @build = build
12
+ @verbose = verbose
13
+ @messages = Hash.new { |h, k| h[k] = [] }
14
+ end
15
+
16
+ def start
17
+ bind_event_handling @build.channel_name
18
+ notify_started
19
+ end
20
+
21
+ def stop(status)
22
+ @client.unsubscribe(@build.channel_name + '@0')
23
+ notify_stopped(status)
24
+ end
25
+
26
+ private
27
+
28
+ def bind_event_handling(channel) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
29
+ @client.bind_event_json(channel, 'newAction') do |json|
30
+ if @verbose
31
+ print_bordered json['log']['name'].light_black
32
+ else
33
+ print json['log']['name'].light_black
34
+ end
35
+ end
36
+
37
+ @client.bind_event_json(channel, 'appendAction') do |json|
38
+ if @verbose
39
+ say json['out']['message']
40
+ else
41
+ @messages[json['step']] << json['out']['message']
42
+ end
43
+ end
44
+
45
+ @client.bind_event_json(channel, 'updateAction') do |json|
46
+ next if @verbose
47
+
48
+ case json['log']['status']
49
+ when 'success'
50
+ puts "\e[2K\r#{json['log']['name'].green}"
51
+ when 'failed'
52
+ puts "\e[2K\r#{json['log']['name'].red}"
53
+ @messages[json['step']].each(&method(:say))
54
+ end
55
+ end
56
+ end
57
+
58
+ def notify_started
59
+ title = "👀 Start watching #{@build.project_name} ##{@build.build_number}".light_black
60
+ say Printer::BuildPrinter.header_for(@build, title)
61
+ end
62
+
63
+ def notify_stopped(status)
64
+ text = case status
65
+ when 'success' then "🎉 #{@build.project_name} ##{@build.build_number} has succeeded!".green
66
+ when 'failed' then "😥 #{@build.project_name} ##{@build.build_number} has failed...".red
67
+ end
68
+
69
+ @verbose ? print_bordered(text) : say(text)
70
+ end
71
+
72
+ def print_bordered(text)
73
+ say Terminal::Table.new(rows: [[text]], style: { width: 120 }).to_s
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -4,8 +4,20 @@ module CircleCI
4
4
  module CLI
5
5
  module Printer
6
6
  class BuildPrinter
7
+ class << self
8
+ def header_for(build, title) # rubocop:disable Metrics/AbcSize
9
+ texts = [
10
+ ['Project: '.light_black + build.project_name],
11
+ ['Build: '.light_black + build.build_number.to_s],
12
+ ['Author: '.light_black + build.author_name],
13
+ ['Workflow: '.light_black + "#{build.workflow_name}/#{build.workflow_job_name}"]
14
+ ]
15
+ Terminal::Table.new(title: title, rows: texts, style: { width: 120 }).to_s
16
+ end
17
+ end
18
+
7
19
  def initialize(builds, pretty: true)
8
- @builds = builds
20
+ @builds_to_show = builds
9
21
  @pretty = pretty
10
22
  end
11
23
 
@@ -24,7 +36,7 @@ module CircleCI
24
36
  end
25
37
 
26
38
  def title
27
- build = @builds.first
39
+ build = @builds_to_show.first
28
40
  "Recent Builds / #{build.project_name}".green
29
41
  end
30
42
 
@@ -33,11 +45,11 @@ module CircleCI
33
45
  end
34
46
 
35
47
  def rows
36
- @builds.map(&:information)
48
+ @builds_to_show.map(&:information)
37
49
  end
38
50
 
39
51
  def max_row_widths
40
- @builds
52
+ @builds_to_show
41
53
  .map(&:information)
42
54
  .map { |array| array.map(&:to_s).map(&:size) }
43
55
  .transpose
@@ -9,16 +9,26 @@ module CircleCI
9
9
  @pretty = pretty
10
10
  end
11
11
 
12
- def to_s
13
- Terminal::Table.new do |t|
14
- @steps
15
- .group_by(&:type)
16
- .each do |key, steps|
17
- t << :separator
18
- t << [{ value: key.green, alignment: :center, colspan: 2 }]
19
- steps.each { |s| print_actions(t, s) }
20
- end
21
- end.to_s
12
+ def to_s # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
13
+ if @pretty
14
+ Terminal::Table.new do |t|
15
+ @steps
16
+ .group_by(&:type)
17
+ .each do |key, steps|
18
+ t << :separator
19
+ t << [{ value: key.green, alignment: :center, colspan: 2 }]
20
+ steps.each { |s| print_actions(t, s) }
21
+ end
22
+ end.to_s
23
+ else
24
+ @steps.group_by(&:type).map do |_, steps|
25
+ steps.map do |step|
26
+ step.actions.map do |a|
27
+ "#{colorize_by_status(a.name.slice(0..120), a.status)}\n#{"#{a.log}\n" if a.failed? && a.log}"
28
+ end
29
+ end.flatten.join('')
30
+ end.join("\n")
31
+ end
22
32
  end
23
33
 
24
34
  private
@@ -8,12 +8,14 @@ module CircleCI
8
8
  @hash = hash
9
9
  end
10
10
 
11
- def user_name
12
- @hash['name']
11
+ def pusher_id
12
+ @hash['pusher_id']
13
13
  end
14
14
 
15
- def self.me
16
- Account.new(CircleCi::User.me.body)
15
+ class << self
16
+ def me
17
+ Account.new(CircleCi::User.new.me.body)
18
+ end
17
19
  end
18
20
  end
19
21
  end
@@ -30,28 +30,27 @@ module CircleCI
30
30
  end
31
31
  end
32
32
 
33
+ attr_reader :username, :build_number, :reponame, :status, :author_name, :start_time,
34
+ :workflow_name, :workflow_job_name
35
+
33
36
  def initialize(hash)
34
37
  @hash = hash
38
+ @username = hash['username']
39
+ @build_number = hash['build_num']
40
+ @reponame = hash['reponame']
41
+ @status = hash['status']
42
+ @author_name = hash['author_name']
43
+ @start_time = hash['start_time']
44
+ @workflow_name = hash.dig('workflows', 'workflow_name')
45
+ @workflow_job_name = hash.dig('workflows', 'job_name')
35
46
  end
36
47
 
37
- def username
38
- @hash['username']
39
- end
40
-
41
- def reponame
42
- @hash['reponame']
43
- end
44
-
45
- def status
46
- @hash['status']
47
- end
48
-
49
- def build_number
50
- @hash['build_num']
48
+ def running?
49
+ status == 'running' || status || 'queued'
51
50
  end
52
51
 
53
- def running?
54
- status == 'running'
52
+ def finished?
53
+ status == 'success' || status == 'canceled' || status == 'failed' || status == 'no_tests'
55
54
  end
56
55
 
57
56
  def channel_name
@@ -64,13 +63,13 @@ module CircleCI
64
63
 
65
64
  def information
66
65
  [
67
- @hash['build_num'],
68
- colorize_by_status(@hash['status'], @hash['status']),
69
- colorize_by_status(@hash['branch'], @hash['status']),
70
- @hash['author_name'],
66
+ build_number,
67
+ colorize_by_status(status, status),
68
+ colorize_by_status(@hash['branch'], status),
69
+ author_name,
71
70
  (@hash['subject'] || '').slice(0..60),
72
71
  format_time(@hash['build_time_millis']),
73
- @hash['start_time']
72
+ start_time
74
73
  ]
75
74
  end
76
75
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CircleCI
4
4
  module CLI
5
- VERSION = '1.0.0'
5
+ VERSION = '2.0.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: circleci-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - unhappychoice
@@ -326,6 +326,8 @@ files:
326
326
  - lib/circleci/cli/command/projects_command.rb
327
327
  - lib/circleci/cli/command/retry_command.rb
328
328
  - lib/circleci/cli/command/watch_command.rb
329
+ - lib/circleci/cli/command/watch_command/build_repository.rb
330
+ - lib/circleci/cli/command/watch_command/build_watcher.rb
329
331
  - lib/circleci/cli/networking.rb
330
332
  - lib/circleci/cli/networking/pusher_client.rb
331
333
  - lib/circleci/cli/printer.rb