chelsea 0.0.11 → 0.0.12

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.
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: