chelsea 0.0.11 → 0.0.12

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: 2c64fc1b71e170403212d48a6882293c42c80b4d034e06fe4968736ec5256e84
4
- data.tar.gz: a65ca2fbfb43e9f9e82b0c3e0c6ffcd42d220dee77642078c49de18a2c71e0bf
3
+ metadata.gz: 51aa180ab06d876ef21c119a2127745fb903c219c812e314cb526cc686b765fd
4
+ data.tar.gz: 279d57c44b27fefb84815357e31db7b0ff2cbdbb0a65ffcd8c1332dc8bacd60e
5
5
  SHA512:
6
- metadata.gz: acaeebea469ad89aae49bdbf0cb3c89f34e2c8b83b0fbdd184d90997459c8a94a35e6575d072ec5b6eacecede3a5ebffeccee4b710d2751c6d16d17c9acdc67f
7
- data.tar.gz: 85123d9942b78903df70fba3bb51288b0dc40a24e2cba7c83c43c027bc00c4948a906568b17b9ec24edbf17d27627d381b6f64e1127bdc861ac5236d29d5274b
6
+ metadata.gz: 4839db5a475789889a93a0e0f7e3e9ef79800dbdfee4b04001e0054607fa85127ebec6eadbb23d8870b217437559cc2c756c764a0f4993eb62831619c71002f2
7
+ data.tar.gz: 8905197d67b7909227c64fb943425e1493287bf60568a78aa4a0124c0e05e7bb1433744defde88934608e6476c781669567bb84166c558a59f38e67c95def91b
@@ -36,6 +36,9 @@ jobs:
36
36
  - run:
37
37
  name: Install gem
38
38
  command: gem install ./chelsea-*.gem
39
+ - run:
40
+ name: Clear cache
41
+ command: chelsea --clear
39
42
  - run:
40
43
  name: Dogfood
41
44
  command: chelsea --file Gemfile.lock
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ .byebug_history
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- chelsea (0.0.10)
4
+ chelsea (0.0.11)
5
5
  bundler (>= 1.2.0, < 3)
6
6
  ox (~> 2.13.2)
7
7
  pastel (~> 0.7.2)
@@ -15,6 +15,7 @@ GEM
15
15
  specs:
16
16
  addressable (2.7.0)
17
17
  public_suffix (>= 2.0.2, < 5.0)
18
+ byebug (11.1.2)
18
19
  crack (0.4.3)
19
20
  safe_yaml (~> 1.0.0)
20
21
  diff-lcs (1.3)
@@ -33,7 +34,7 @@ GEM
33
34
  equatable (~> 0.6)
34
35
  tty-color (~> 0.5)
35
36
  public_suffix (4.0.3)
36
- rake (10.5.0)
37
+ rake (12.3.3)
37
38
  rest-client (2.0.2)
38
39
  http-cookie (>= 1.0.2, < 2.0)
39
40
  mime-types (>= 1.16, < 4.0)
@@ -72,8 +73,9 @@ PLATFORMS
72
73
  ruby
73
74
 
74
75
  DEPENDENCIES
76
+ byebug (~> 11.1.2)
75
77
  chelsea!
76
- rake (~> 10.0)
78
+ rake (~> 12.3)
77
79
  rspec (~> 3.0)
78
80
  rspec_junit_formatter (~> 0.4.1)
79
81
  webmock (~> 3.8.3)
@@ -6,6 +6,7 @@ opts =
6
6
  begin
7
7
  Slop.parse do |o|
8
8
  o.string '-f', '--file', 'Path to your Gemfile.lock'
9
+ o.bool '-x', '--clear', 'Clear OSS Index cache'
9
10
  o.bool '-c', '--config', 'Set persistent config for OSS Index'
10
11
  o.string '-u', '--user', 'Specify OSS Index Username'
11
12
  o.string '-p', '--token', 'Specify OSS Index API Token'
@@ -43,8 +43,9 @@ Gem::Specification.new do |spec|
43
43
  spec.add_dependency "bundler", ">= 1.2.0", "< 3"
44
44
  spec.add_dependency "ox", "~> 2.13.2"
45
45
 
46
- spec.add_development_dependency "rake", "~> 10.0"
46
+ spec.add_development_dependency "rake", "~> 12.3"
47
47
  spec.add_development_dependency "rspec", "~> 3.0"
48
48
  spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.1"
49
49
  spec.add_development_dependency "webmock", "~> 3.8.3"
50
+ spec.add_development_dependency "byebug", "~> 11.1.2"
50
51
  end
@@ -4,16 +4,23 @@ require 'securerandom'
4
4
  require 'ox'
5
5
 
6
6
  module Chelsea
7
- # Class to convext dependencies to BOM xml
7
+ # Class to convert dependencies to SBOM xml
8
8
  class Bom
9
- attr_accessor :xml
10
9
  def initialize(dependencies)
11
10
  @dependencies = dependencies
12
- @xml = _get_xml
11
+ end
12
+
13
+ def collect
14
+ xml
15
+ to_s
16
+ end
17
+
18
+ def xml
19
+ @xml ||= _get_xml
13
20
  end
14
21
 
15
22
  def to_s
16
- Ox.dump(@xml).to_s
23
+ Ox.dump(@xml)
17
24
  end
18
25
 
19
26
  def random_urn_uuid
@@ -21,9 +21,15 @@ module Chelsea
21
21
  def process!
22
22
  if @opts.config?
23
23
  _set_config # move to init
24
- elsif @opts.file? # don't process unless there was a file
25
- gems = _process_file
26
- _submit_sbom(gems) if @opts.sbom?
24
+ elsif @opts.clear?
25
+ require_relative 'db'
26
+ Chelsea::DB.new().clear_cache
27
+ puts "OSS Index cache cleared"
28
+ elsif @opts.file? && @opts.iq?
29
+ dependencies = _process_file_iq
30
+ _submit_sbom(dependencies)
31
+ elsif @opts.file?
32
+ _process_file
27
33
  elsif @opts.help? # quit on opts.help earlier
28
34
  puts _cli_flags # this doesn't exist
29
35
  end
@@ -44,8 +50,13 @@ module Chelsea
44
50
  auth_token: @opts[:iqpass]
45
51
  }
46
52
  )
47
- bom = Chelsea::Bom.new(gems.deps.dependencies)
48
- iq.submit_sbom(bom)
53
+ bom = Chelsea::Bom.new(gems.deps.dependencies).collect
54
+
55
+ status_url = iq.post_sbom(bom)
56
+
57
+ return unless status_url
58
+
59
+ iq.poll_status(status_url)
49
60
  end
50
61
 
51
62
  def _process_file
@@ -54,7 +65,16 @@ module Chelsea
54
65
  quiet: @opts[:quiet],
55
66
  options: @opts
56
67
  )
57
- gems.execute # should be more like collect
68
+ gems.execute ? (exit 1) : (exit 0)
69
+ end
70
+
71
+ def _process_file_iq
72
+ gems = Chelsea::Gems.new(
73
+ file: @opts[:file],
74
+ quiet: @opts[:quiet],
75
+ options: @opts
76
+ )
77
+ gems.collect_iq
58
78
  gems
59
79
  end
60
80
 
@@ -12,11 +12,13 @@ module Chelsea
12
12
  def self.config(options = {})
13
13
  if !options[:user].nil? && !options[:token].nil?
14
14
  Chelsea::OSSIndex.new(
15
- oss_index_user_name: options[:user],
16
- oss_index_user_token: options[:token]
15
+ options: {
16
+ oss_index_user_name: options[:user],
17
+ oss_index_user_token: options[:token]
18
+ }
17
19
  )
18
20
  else
19
- Chelsea::OSSIndex.new(oss_index_config)
21
+ Chelsea::OSSIndex.new(options: oss_index_config)
20
22
  end
21
23
  end
22
24
 
@@ -20,6 +20,15 @@ module Chelsea
20
20
  end
21
21
  end
22
22
 
23
+ # This method will delete all values in the pstore db
24
+ def clear_cache
25
+ @store.transaction do
26
+ @store.roots.each do |root|
27
+ @store.delete(root)
28
+ end
29
+ end
30
+ end
31
+
23
32
  def _get_db_store_location()
24
33
  initial_path = File.join(Dir.home.to_s, '.ossindex')
25
34
  Dir.mkdir(initial_path) unless File.exist? initial_path
@@ -5,9 +5,6 @@ require 'rubygems/commands/dependency_command'
5
5
  require 'json'
6
6
  require 'rest-client'
7
7
 
8
- require_relative 'dependency_exception'
9
- require_relative 'oss_index'
10
-
11
8
  module Chelsea
12
9
  class Deps
13
10
  def initialize(path:, quiet: false)
@@ -38,11 +35,17 @@ module Chelsea
38
35
  reverse = Gem::Commands::DependencyCommand.new
39
36
  reverse.options[:reverse_dependencies] = true
40
37
  # We want to filter the reverses dependencies by specs in lockfile
41
- spec_names = @lockfile.specs.map { |i| i.to_s.split }.map { |n, _| "#{n}" }
42
- reverse.reverse_dependencies(@lockfile.specs).to_h.transform_values do |reverse_dep|
43
- # Add filtering if version meets range
44
- reverse_dep.select { |name, dep, req, _| spec_names.include?(name.split("-")[0]) }
38
+ spec_names = @lockfile.specs.map { |i| i.to_s.split }.map do |n, _v|
39
+ n.to_s
45
40
  end
41
+ reverse
42
+ .reverse_dependencies(@lockfile.specs)
43
+ .to_h
44
+ .transform_values do |reverse_dep|
45
+ reverse_dep.select do |name, _dep, _req, _|
46
+ spec_names.include?(name.split('-')[0])
47
+ end
48
+ end
46
49
  end
47
50
 
48
51
  # Iterates over all dependencies and stores them
@@ -18,6 +18,8 @@ module Chelsea
18
18
 
19
19
  i = 0
20
20
  count = server_response.count()
21
+ server_response.sort! {|x| x['vulnerabilities'].count}
22
+
21
23
  server_response.each do |r|
22
24
  i += 1
23
25
  package = r['coordinates']
@@ -29,8 +31,8 @@ module Chelsea
29
31
  if vulnerable
30
32
  response += @pastel.red("[#{i}/#{count}] - #{package} ") + @pastel.red.bold("Vulnerable.\n")
31
33
  response += _get_reverse_deps(reverse_deps, name) if reverse_deps
32
- r['vulnerabilities'].each do |k, v|
33
- response += _format_vuln(v)
34
+ r['vulnerabilities'].each do |k, _|
35
+ response += _format_vuln(k)
34
36
  end
35
37
  else
36
38
  if !@quiet
@@ -48,7 +50,29 @@ module Chelsea
48
50
  end
49
51
 
50
52
  def _format_vuln(vuln)
51
- @pastel.red.bold("\n#{vuln}\n")
53
+ cvssScore = vuln['cvssScore']
54
+ vuln_response = "\n\tVulnerability Details:\n"
55
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tID: #{vuln['id']}\n")
56
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tTitle: #{vuln['title']}\n")
57
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tDescription: #{vuln['description']}\n")
58
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tCVSS Score: #{vuln['cvssScore']}\n")
59
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tCVSS Vector: #{vuln['cvssVector']}\n")
60
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tCVE: #{vuln['cve']}\n")
61
+ vuln_response += _color_based_on_cvss_score(cvssScore, "\n\tReference: #{vuln['reference']}\n\n")
62
+ vuln_response
63
+ end
64
+
65
+ def _color_based_on_cvss_score(cvssScore, text)
66
+ case cvssScore
67
+ when 0..3
68
+ @pastel.cyan.bold(text)
69
+ when 4..5
70
+ @pastel.yellow.bold(text)
71
+ when 6..7
72
+ @pastel.orange.bold(text)
73
+ else
74
+ @pastel.red.bold(text)
75
+ end
52
76
  end
53
77
 
54
78
  def _get_reverse_deps(coords, name)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require 'pastel'
3
- require 'tty-spinner'
4
3
  require 'bundler'
5
4
  require 'bundler/lockfile_parser'
6
5
  require 'rubygems'
@@ -10,21 +9,27 @@ require_relative 'version'
10
9
  require_relative 'formatters/factory'
11
10
  require_relative 'deps'
12
11
  require_relative 'bom'
12
+ require_relative 'spinner'
13
13
 
14
14
  module Chelsea
15
+ # Class to collect and audit packages from a Gemfile.lock
15
16
  class Gems
16
17
  attr_accessor :deps
17
- def initialize(file:, quiet: false, options: {'format': 'text'})
18
+ def initialize(file:, quiet: false, options: { 'format': 'text' })
18
19
  @quiet = quiet
19
20
  unless File.file?(file) || file.nil?
20
21
  raise 'Gemfile.lock not found, check --file path'
21
22
  end
23
+
22
24
  _silence_stderr if @quiet
23
25
 
24
26
  @pastel = Pastel.new
25
- @formatter = FormatterFactory.new.get_formatter(format: options[:format], quiet: @quiet)
27
+ @formatter = FormatterFactory.new.get_formatter(
28
+ format: options[:format],
29
+ quiet: @quiet)
26
30
  @client = Chelsea.client(options)
27
31
  @deps = Chelsea::Deps.new(path: Pathname.new(file))
32
+ @spinner = Chelsea::Spinner.new
28
33
  end
29
34
 
30
35
  # Audits depenencies using deps library and prints results
@@ -42,6 +47,13 @@ module Chelsea
42
47
  end
43
48
  results = @formatter.get_results(server_response, reverse_dependencies)
44
49
  @formatter.do_print(results)
50
+
51
+ server_response.map { |r| r['vulnerabilities'].length.positive? }.any?
52
+ end
53
+
54
+ def collect_iq
55
+ dependencies = @deps.dependencies
56
+ dependencies
45
57
  end
46
58
 
47
59
  # Runs through auditing algorithm, raising exceptions
@@ -50,40 +62,40 @@ module Chelsea
50
62
  # This spinner management is out of control
51
63
  # we should wrap a block with start and stop messages,
52
64
  # or use a stack to ensure all spinners stop.
53
- spinner = _spin_msg 'Parsing dependencies'
65
+ spin = @spinner.spin_msg 'Parsing dependencies'
54
66
 
55
67
  begin
56
68
  dependencies = @deps.dependencies
57
- spinner.success('...done.')
69
+ spin.success('...done.')
58
70
  rescue StandardError => e
59
- spinner.stop
71
+ spin.stop
60
72
  _print_err "Parsing dependency line #{gem} failed."
61
73
  end
62
74
 
63
75
  reverse_dependencies = @deps.reverse_dependencies
64
76
 
65
- spinner = _spin_msg 'Parsing Versions'
77
+ spin = @spinner.spin_msg 'Parsing Versions'
66
78
  coordinates = @deps.coordinates
67
- spinner.success('...done.')
68
- spinner = _spin_msg 'Making request to OSS Index server'
79
+ spin.success('...done.')
80
+ spin = @spinner.spin_msg 'Making request to OSS Index server'
69
81
 
70
82
  begin
71
83
  server_response = @client.get_vulns(coordinates)
72
- spinner.success('...done.')
84
+ spin.success('...done.')
73
85
  rescue SocketError => e
74
- spinner.stop('...request failed.')
86
+ spin.stop('...request failed.')
75
87
  _print_err 'Socket error getting data from OSS Index server.'
76
88
  rescue RestClient::RequestFailed => e
77
- spinner.stop('...request failed.')
89
+ spin.stop('...request failed.')
78
90
  _print_err "Error getting data from OSS Index server:#{e.response}."
79
91
  rescue RestClient::ResourceNotFound => e
80
- spinner.stop('...request failed.')
92
+ spin.stop('...request failed.')
81
93
  _print_err 'Error getting data from OSS Index server. Resource not found.'
82
94
  rescue Errno::ECONNREFUSED => e
83
- spinner.stop('...request failed.')
95
+ spin.stop('...request failed.')
84
96
  _print_err 'Error getting data from OSS Index server. Connection refused.'
85
97
  rescue StandardError => e
86
- spinner.stop('...request failed.')
98
+ spin.stop('...request failed.')
87
99
  _print_err 'UNKNOWN Error getting data from OSS Index server.'
88
100
  end
89
101
  [server_response, dependencies, reverse_dependencies]
@@ -95,17 +107,6 @@ module Chelsea
95
107
  $stderr.reopen('/dev/null', 'w')
96
108
  end
97
109
 
98
- def _spin_msg(msg)
99
- format = "[#{@pastel.green(':spinner')}] " + @pastel.white(msg)
100
- spinner = TTY::Spinner.new(
101
- format,
102
- success_mark: @pastel.green('+'),
103
- hide_cursor: true
104
- )
105
- spinner.auto_spin
106
- spinner
107
- end
108
-
109
110
  def _print_err(s)
110
111
  puts @pastel.red.bold(s)
111
112
  end
@@ -1,8 +1,12 @@
1
1
  require 'rest-client'
2
2
  require 'json'
3
+ require 'pastel'
4
+
5
+ require_relative 'spinner'
3
6
 
4
7
  module Chelsea
5
8
  class IQClient
9
+
6
10
  DEFAULT_OPTIONS = {
7
11
  public_application_id: 'testapp',
8
12
  server_url: 'http://localhost:8070',
@@ -12,17 +16,26 @@ module Chelsea
12
16
  }
13
17
  def initialize(options: DEFAULT_OPTIONS)
14
18
  @options = options
19
+ @pastel = Pastel.new
20
+ @spinner = Chelsea::Spinner.new
15
21
  end
16
22
 
17
- def submit_sbom(sbom)
18
- @internal_application_id = _get_internal_application_id()
23
+ def post_sbom(sbom)
24
+ spin = @spinner.spin_msg "Submitting sbom to Nexus IQ Server"
25
+ @internal_application_id = _get_internal_application_id
19
26
  resource = RestClient::Resource.new(
20
27
  _api_url,
21
28
  user: @options[:username],
22
29
  password: @options[:auth_token]
23
30
  )
24
- _headers['Content-Type'] = 'application/xml'
25
- resource.post sbom.to_s, _headers
31
+ res = resource.post sbom.to_s, _headers.merge(content_type: 'application/xml')
32
+ unless res.code != 202
33
+ spin.success("...done.")
34
+ status_url(res)
35
+ else
36
+ spin.stop('...request failed.')
37
+ nil
38
+ end
26
39
  end
27
40
 
28
41
  def status_url(res)
@@ -30,16 +43,89 @@ module Chelsea
30
43
  res['statusUrl']
31
44
  end
32
45
 
46
+ def poll_status(url)
47
+ spin = @spinner.spin_msg "Polling Nexus IQ Server for results"
48
+ loop do
49
+ begin
50
+ res = _poll_iq_server(url)
51
+ if res.code == 200
52
+ spin.success("...done.")
53
+ _handle_response(res)
54
+ break
55
+ end
56
+ rescue
57
+ sleep(1)
58
+ end
59
+ end
60
+ end
61
+
33
62
  private
34
63
 
64
+ def _handle_response(res)
65
+ res = JSON.parse(res.body)
66
+ unless res['policyAction'] == 'Failure'
67
+ puts @pastel.white.bold("Hi! Chelsea here, no policy violations for this audit!")
68
+ puts @pastel.white.bold("Report URL: #{res['reportHtmlUrl']}")
69
+ exit 0
70
+ else
71
+ puts @pastel.red.bold("Hi! Chelsea here, you have some policy violations to clean up!")
72
+ puts @pastel.red.bold("Report URL: #{res['reportHtmlUrl']}")
73
+ exit 1
74
+ end
75
+ end
76
+
77
+ def _poll_iq_server(status_url)
78
+ resource = RestClient::Resource.new(
79
+ "#{@options[:server_url]}/#{status_url}",
80
+ user: @options[:username],
81
+ password: @options[:auth_token]
82
+ )
83
+
84
+ resource.get _headers
85
+ end
86
+
87
+ def status(status_url)
88
+ resource = RestClient::Resource.new(
89
+ "#{@options[:server_url]}/#{status_url}",
90
+ user: @options[:username],
91
+ password: @options[:auth_token]
92
+ )
93
+ resource.get _headers
94
+ end
95
+
96
+ def _status_url(res)
97
+ res = JSON.parse(res.body)
98
+ res['statusUrl']
99
+ end
100
+
101
+ private
102
+
103
+ def _poll_status
104
+ return unless @status_url
105
+
106
+ loop do
107
+ begin
108
+ res = check_status(@status_url)
109
+ if res.code == 200
110
+ puts JSON.parse(res.body)
111
+ break
112
+ end
113
+ rescue RestClient::ResourceNotFound => _e
114
+ print '.'
115
+ sleep(1)
116
+ end
117
+ end
118
+ end
119
+
35
120
  def _get_internal_application_id
36
121
  resource = RestClient::Resource.new(
37
122
  _internal_application_id_api_url,
38
- user: @username,
39
- password: @auth_token
123
+ user: @options[:username],
124
+ password: @options[:auth_token]
40
125
  )
41
- res = JSON.parse(resource.get(_headers))
42
- res['applications'][0]['id']
126
+ res = resource.get _headers
127
+ body = JSON.parse(res)
128
+ body['applications'][0]['id']
43
129
  end
44
130
 
45
131
  def _headers
@@ -47,7 +133,7 @@ module Chelsea
47
133
  end
48
134
 
49
135
  def _api_url
50
- "#{@options[:server_url]}/api/v2/scan/applications/#{@@internal_application_id}/sources/chelsea"
136
+ "#{@options[:server_url]}/api/v2/scan/applications/#{@internal_application_id}/sources/chelsea"
51
137
  end
52
138
 
53
139
  def _internal_application_id_api_url
@@ -6,9 +6,13 @@ require_relative 'db'
6
6
 
7
7
  module Chelsea
8
8
  class OSSIndex
9
- def initialize(oss_index_user_name: '', oss_index_user_token: '')
10
- @oss_index_user_name = oss_index_user_name
11
- @oss_index_user_token = oss_index_user_token
9
+ DEFAULT_OPTIONS = {
10
+ oss_index_username: '',
11
+ oss_index_user_token: ''
12
+ }
13
+ def initialize(options: DEFAULT_OPTIONS)
14
+ @oss_index_user_name = options[:oss_index_user_name]
15
+ @oss_index_user_token = options[:oss_index_user_token]
12
16
  @db = DB.new
13
17
  end
14
18
 
@@ -26,6 +30,7 @@ module Chelsea
26
30
  cached_server_response = cached_server_response.concat(res_json)
27
31
  @db.save_values_to_db(res_json)
28
32
  end
33
+
29
34
  cached_server_response
30
35
  end
31
36
 
@@ -0,0 +1,21 @@
1
+ require 'tty-spinner'
2
+ require 'pastel'
3
+
4
+ module Chelsea
5
+ class Spinner
6
+ def initialize()
7
+ @pastel = Pastel.new
8
+ end
9
+
10
+ def spin_msg(msg)
11
+ format = "[#{@pastel.green(':spinner')}] " + @pastel.white(msg)
12
+ spinner = TTY::Spinner.new(
13
+ format,
14
+ success_mark: @pastel.green('+'),
15
+ hide_cursor: true
16
+ )
17
+ spinner.auto_spin
18
+ spinner
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Chelsea
2
- VERSION = '0.0.11'.freeze
2
+ VERSION = '0.0.12'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chelsea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Allister Beharry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-17 00:00:00.000000000 Z
11
+ date: 2020-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-font
@@ -120,14 +120,14 @@ dependencies:
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '10.0'
123
+ version: '12.3'
124
124
  type: :development
125
125
  prerelease: false
126
126
  version_requirements: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '10.0'
130
+ version: '12.3'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: rspec
133
133
  requirement: !ruby/object:Gem::Requirement
@@ -170,6 +170,20 @@ dependencies:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
172
  version: 3.8.3
173
+ - !ruby/object:Gem::Dependency
174
+ name: byebug
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: 11.1.2
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: 11.1.2
173
187
  description:
174
188
  email:
175
189
  - allister.beharry@gmail.com
@@ -217,6 +231,7 @@ files:
217
231
  - lib/chelsea/gems.rb
218
232
  - lib/chelsea/iq_client.rb
219
233
  - lib/chelsea/oss_index.rb
234
+ - lib/chelsea/spinner.rb
220
235
  - lib/chelsea/version.rb
221
236
  homepage: https://github.com/sonatype-nexus-community/chelsea
222
237
  licenses: