dependency-timeline-audit 0.0.1 → 0.0.3

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: 3b9e17ee55c32c28cf3b8217bbf7ddab45ceafbc611046361ab649b5ede37d42
4
- data.tar.gz: 0a9e935a6e14de9350d1cf9036774da6d5a0e2a8d71967c3d57f581ab3c22bf1
3
+ metadata.gz: 3ac86eaf5319d8a10dc42fd001c0f32a15a5421bd861ce24faf2b4bcb462493d
4
+ data.tar.gz: 9a67aac946d8265cb908d521ed90f2de25fe245bb0f141766faae266e4fb678d
5
5
  SHA512:
6
- metadata.gz: fe745f2316cd6df1ec6022898fd5a6ccb45baf22bfe1f088430a330bd40f58e73b5656515b150ebec40c12423cf829b18b3a9ef8181eec0ddbc592fa9b161f3d
7
- data.tar.gz: f2e7b8e9524a8e9caac06d708e25bdc07c3d90a625a45be93bd0c79d8d3c5446614814a53247c7da6b63628dd7fbc9296033e7753120999b50fdb866c1933839
6
+ metadata.gz: af4097c658cefeb2853b4fe6d4e1f517a976a7d6bde91eaf69660cd9da95f80f0cf392f3986b7298dafbaa34259b526f501c5170d481dcf2e0be84d306a68853
7
+ data.tar.gz: 70a6bbfd8e7c40c39c427133d8e528a00fc54d10bdb68784aadac12a3ad60b1fc1c34aca1fe73d4e88b9fe852282b50f3a527aeca69be8498bff84c6926c78ac
@@ -1,5 +1,34 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'dependency-timeline-audit'
4
+ require 'optparse'
4
5
 
5
- DependencyTimelineAudit::Check.check
6
+ # See: https://docs.ruby-lang.org/en/master/OptionParser.html
7
+
8
+ begin
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: dependency-timeline-audit [options]\n"
12
+
13
+ opts.on('-i', '--interactive-ignore', 'Allows interactively generating an ignore file')
14
+ opts.on('-v', '--verbose', 'Provides more verbose output')
15
+ opts.on('--lockfile=LOCKFILE', 'Allows overwriting where the lockfile is located (default: "Gemfile.lock")')
16
+ opts.on('--no-lockfile', "Don't use a lockfile")
17
+ opts.on('--outdated-threshold=YEARS', Integer, 'Allows overwriting the number of years before a gem is considered outdated (default: 1)')
18
+ opts.on_tail('-h', '--help', 'Prints this help') do
19
+ puts opts
20
+ exit
21
+ end
22
+ opts.on('-V', '--version', 'Prints the version of dependency-timeline-audit') do
23
+ puts "Dependency Timeline Audit (Ruby) - version: #{DependencyTimelineAudit.gem_version}"
24
+ exit
25
+ end
26
+ end.parse!(into: options)
27
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
28
+ puts e.message
29
+ exit(1)
30
+ end
31
+
32
+ config = DependencyTimelineAudit::Config.new(options)
33
+ checker = DependencyTimelineAudit::Check.new(config: config)
34
+ checker.check
@@ -0,0 +1,57 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module DependencyTimelineAudit
5
+ class API
6
+ API_URL = 'https://rubygems.org/api/v1/versions/'
7
+ CACHE_DIRECTORY = "#{Dir.pwd}/.dependency-timeline-audit/cache/ruby/"
8
+ EXCEPTIONS_DIRECTORY = "#{Dir.pwd}/.dependency-timeline-audit/exceptions/ruby/"
9
+ @@gem_cache = {}
10
+
11
+ def self.fetch_gem_info(gem_name)
12
+ if !cached?(gem_name) || cache_outdated?(gem_name)
13
+ update_cache(gem_name)
14
+ end
15
+
16
+ gem_cache(gem_name)
17
+ end
18
+
19
+ def self.cached?(gem_name)
20
+ File.exist?(gem_cache_file(gem_name))
21
+ end
22
+
23
+ # TODO: Implement cache_outdated? method using config.cache_expires_after
24
+ def self.cache_outdated?(gem_name)
25
+ false
26
+ end
27
+
28
+ def self.gem_cache_file(gem_name)
29
+ File.join(CACHE_DIRECTORY, "#{gem_name}.json")
30
+ end
31
+
32
+ def self.update_cache(gem_name)
33
+ response = rubygems_api_response(gem_name)
34
+ gem_cache = GemCache.new(JSON.parse(response))
35
+
36
+ FileUtils.mkdir_p(CACHE_DIRECTORY) unless File.directory?(CACHE_DIRECTORY)
37
+ File.open(gem_cache_file(gem_name), 'w') do |file|
38
+ file.write(JSON.pretty_generate(gem_cache.to_h))
39
+ end
40
+
41
+ gem_cache(gem_name)
42
+ end
43
+
44
+ def self.rubygems_api_response(gem_name)
45
+ url = URI("#{API_URL}#{gem_name}.json")
46
+ Net::HTTP.get(url)
47
+ end
48
+
49
+ def self.gem_cache(gem_name)
50
+ if @@gem_cache[gem_name].nil? && cached?(gem_name)
51
+ @@gem_cache[gem_name] = GemCache.new(JSON.parse(File.read(gem_cache_file(gem_name))))
52
+ end
53
+
54
+ @@gem_cache[gem_name]
55
+ end
56
+ end
57
+ end
@@ -1,31 +1,30 @@
1
1
  require 'date'
2
- require 'active_support/all'
3
2
 
4
3
  module DependencyTimelineAudit
5
4
  class Check
6
- # TODO: activesupport is kinda hefty for just grabbing 1.year.ago, remove
7
- def self.outdated_threshold
8
- 1.year.ago
5
+ attr_reader :config
6
+
7
+ def initialize(config:)
8
+ @config = config
9
9
  end
10
10
 
11
- def self.check(lockfile = 'Gemfile.lock')
11
+ def check
12
12
  outdated_versions = []
13
13
  locked_gems.each do |gem|
14
- lock_released_at = GemInfo.version_created_at(gem[:name], gem[:locked_version])
15
- latest_version = GemInfo.latest_version(gem[:name])
16
- outdated_versions.push(gem[:name]) if gem_outdated?(lock_released_at)
17
- print_info(gem, lock_released_at, latest_version)
14
+ outdated_versions.push(gem) if gem.locked_version.outdated?
15
+ gem.print_info if config.verbose
18
16
  end
19
17
 
18
+ print "\n" if config.verbose
19
+
20
20
  if outdated_versions.any?
21
- set_text_color_red
22
- puts "\nOutdated gems detected!"
21
+ TextFormat.color = :red
22
+ puts "Outdated gems detected!"
23
23
  puts " - #{outdated_versions.join(', ')}"
24
24
 
25
25
  exit(1) # Failure
26
26
  else
27
- reset_text_style
28
- puts "\nAll gems are within the accepted threshold!"
27
+ puts "All gems are within the accepted threshold!"
29
28
 
30
29
  exit(0) # Success
31
30
  end
@@ -33,61 +32,15 @@ module DependencyTimelineAudit
33
32
 
34
33
  private
35
34
 
36
- def self.gem_outdated?(released_at)
37
- released_at <= outdated_threshold
38
- end
39
-
40
- def self.print_info(gem, lock_released_at, latest_version)
41
- puts "Gem: \e[1m#{gem[:name]}\e[0m"
42
- set_text_color(lock_released_at, gem[:locked_version] == latest_version[:version])
43
- puts " - Locked to: #{gem[:locked_version]} (Released: #{format_date(lock_released_at)})"
44
- set_text_color(latest_version[:created_at])
45
- puts " - Latest: #{latest_version[:version]} (Released: #{format_date(latest_version[:created_at])})"
46
- reset_text_style
47
- end
48
-
49
- def self.set_text_color(released_at, using_latest = true)
50
- if gem_outdated?(released_at)
51
- set_text_color_red
52
- else
53
- if using_latest
54
- set_text_color_green
35
+ def locked_gems
36
+ lockfile_parser = Bundler::LockfileParser.new(File.read(config.lockfile))
37
+ lockfile_parser.specs.map do |spec|
38
+ if spec.source.is_a?(Bundler::Source::Git)
39
+ Gem.new(config: config, name: spec.name, locked_version: spec.source.revision)
55
40
  else
56
- set_text_color_yellow
41
+ Gem.new(config: config, name: spec.name, locked_version: spec.version)
57
42
  end
58
43
  end
59
44
  end
60
-
61
- def self.set_text_bold
62
- print "\e[1m"
63
- end
64
-
65
- def self.set_text_color_red
66
- print "\e[31m"
67
- end
68
-
69
- def self.set_text_color_green
70
- print "\e[32m"
71
- end
72
-
73
- def self.set_text_color_yellow
74
- print "\e[33m"
75
- end
76
-
77
- def self.reset_text_style
78
- print "\e[0m"
79
- end
80
-
81
- def self.locked_gems
82
- lockfile = Bundler::LockfileParser.new(File.read('Gemfile.lock'))
83
- lockfile.specs.map do |gem|
84
- { name: gem.name, locked_version: gem.version.to_s }
85
- end
86
- end
87
-
88
- def self.format_date(date_string)
89
- date = Date.parse(date_string)
90
- date.strftime("%Y-%m-%d")
91
- end
92
45
  end
93
46
  end
@@ -0,0 +1,27 @@
1
+ require 'active_support/all'
2
+
3
+ module DependencyTimelineAudit
4
+ class Config
5
+ attr_reader :verbose, :lockfile, :outdated_threshold
6
+
7
+ def initialize(options)
8
+ @verbose = value_or_default(options[:verbose], false)
9
+ @lockfile = value_or_default(options[:lockfile], 'Gemfile.lock')
10
+ @outdated_threshold = value_or_default(options[:outdated_threshold], 3)
11
+
12
+ # FIXME: There is probably a better way to handle the guard clauses and type casting
13
+ if @outdated_threshold.to_i <= 0
14
+ raise InvalidOption, "Outdated Threshold must be an integer greater than 0"
15
+ end
16
+
17
+ # TODO: activesupport is kinda hefty for just grabbing X.years.ago, remove
18
+ @outdated_threshold = @outdated_threshold.to_i.years.ago
19
+ end
20
+
21
+ private
22
+
23
+ def value_or_default(value, default)
24
+ !value.nil? ? value : default
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module DependencyTimelineAudit
2
+ class Gem
3
+ attr_reader :name, :locked_version, :latest_version, :config
4
+
5
+ def initialize(config:, name:, locked_version:)
6
+ @config = config
7
+ @name = name
8
+ @locked_version = GemVersion.new(gem: self, name: locked_version)
9
+ @latest_version = GemVersion.new(gem: self, name: api.latest_version)
10
+ end
11
+
12
+ def to_s
13
+ name.to_s
14
+ end
15
+
16
+ def print_info
17
+ puts "Gem: #{TextFormat.bold}#{name}#{TextFormat.reset}"
18
+ TextFormat.color = locked_version.color
19
+ puts " - Locked to: #{locked_version.name} (Released: #{format_date(locked_version.released_at)})"
20
+ TextFormat.color = latest_version.color
21
+ puts " - Latest: #{latest_version.name} (Released: #{format_date(latest_version.released_at)})"
22
+ TextFormat.reset!
23
+ end
24
+
25
+ private
26
+
27
+ def api
28
+ API.fetch_gem_info(name)
29
+ end
30
+
31
+ def format_date(date_string)
32
+ return 'Unknown' if date_string.nil?
33
+ date = Date.parse(date_string)
34
+ date.strftime("%Y-%m-%d")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ module DependencyTimelineAudit
2
+ class GemCache
3
+ attr_reader :versions
4
+
5
+ def initialize(gem_info)
6
+ @versions = gem_info || []
7
+ end
8
+
9
+ def latest_version
10
+ latest = versions.first # The first entry is the latest version
11
+ version_number = latest['number']
12
+ end
13
+
14
+ # Find the version that matches the requested version string
15
+ def version(version_number)
16
+ version_info = versions.find { |v| v['number'] == version_number }
17
+ GemVersionCache.new(version_info)
18
+ end
19
+
20
+ def to_h
21
+ versions
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ module DependencyTimelineAudit
2
+ class GemVersion
3
+ attr_reader :gem, :config, :name
4
+
5
+ def initialize(gem:, name:)
6
+ @gem = gem
7
+ @config = gem.config
8
+ @name = name.to_s
9
+ end
10
+
11
+ def ==(other)
12
+ return name == other.name if other.is_a?(GemVersion)
13
+
14
+ nil
15
+ end
16
+
17
+ def to_s
18
+ name.to_s
19
+ end
20
+
21
+ # If release date is unknown, leave it as not outdated
22
+ def outdated?
23
+ return false if released_at.nil?
24
+ released_at <= config.outdated_threshold
25
+ end
26
+
27
+ def latest?
28
+ gem.latest_version == self
29
+ end
30
+
31
+ def color
32
+ return :red if outdated?
33
+ return :green if latest?
34
+ :yellow
35
+ end
36
+
37
+ def released_at
38
+ api.released_at
39
+ end
40
+
41
+ private
42
+
43
+ def api
44
+ API.fetch_gem_info(gem.name).version(name)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ module DependencyTimelineAudit
2
+ class GemVersionCache
3
+ def initialize(version_info)
4
+ @version_info = version_info || {}
5
+ end
6
+
7
+ def released_at
8
+ @version_info['created_at']
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ module DependencyTimelineAudit
2
+ class TextFormat
3
+ class << self
4
+ def color=(color)
5
+ case color
6
+ when :red
7
+ print red
8
+ when :green
9
+ print green
10
+ when :yellow
11
+ print yellow
12
+ else
13
+ raise ArgumentError, "Unknown color: #{color}"
14
+ end
15
+ end
16
+
17
+ def style=(style)
18
+ case style
19
+ when :bold
20
+ print bold
21
+ else
22
+ raise ArgumentError, "Unknown style: #{style}"
23
+ end
24
+ end
25
+
26
+ def reset!
27
+ print reset
28
+ end
29
+
30
+ def reset
31
+ "\e[0m"
32
+ end
33
+
34
+ def bold
35
+ "\e[1m"
36
+ end
37
+
38
+ def red
39
+ "\e[31m"
40
+ end
41
+
42
+ def green
43
+ "\e[32m"
44
+ end
45
+
46
+ def yellow
47
+ "\e[33m"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,7 +2,7 @@ module DependencyTimelineAudit
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- PATCH = 1
5
+ PATCH = 3
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].join('.')
8
8
  end
@@ -1,9 +1,15 @@
1
1
  module DependencyTimelineAudit
2
- autoload :Check, 'dependency-timeline-audit/check'
3
- autoload :GemInfo, 'dependency-timeline-audit/gem_info'
4
- autoload :VERSION, 'dependency-timeline-audit/version'
2
+ autoload :API, 'dependency-timeline-audit/api'
3
+ autoload :Check, 'dependency-timeline-audit/check'
4
+ autoload :Config, 'dependency-timeline-audit/config'
5
+ autoload :GemCache, 'dependency-timeline-audit/gem_cache'
6
+ autoload :GemVersionCache, 'dependency-timeline-audit/gem_version_cache'
7
+ autoload :GemVersion, 'dependency-timeline-audit/gem_version'
8
+ autoload :Gem, 'dependency-timeline-audit/gem'
9
+ autoload :TextFormat, 'dependency-timeline-audit/text_format'
10
+ autoload :VERSION, 'dependency-timeline-audit/version'
5
11
 
6
12
  def self.gem_version
7
- Gem::Version.new VERSION::STRING
13
+ ::Gem::Version.new VERSION::STRING
8
14
  end
9
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependency-timeline-audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Buker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-24 00:00:00.000000000 Z
11
+ date: 2024-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -47,8 +47,14 @@ extra_rdoc_files: []
47
47
  files:
48
48
  - bin/dependency-timeline-audit
49
49
  - lib/dependency-timeline-audit.rb
50
+ - lib/dependency-timeline-audit/api.rb
50
51
  - lib/dependency-timeline-audit/check.rb
51
- - lib/dependency-timeline-audit/gem_info.rb
52
+ - lib/dependency-timeline-audit/config.rb
53
+ - lib/dependency-timeline-audit/gem.rb
54
+ - lib/dependency-timeline-audit/gem_cache.rb
55
+ - lib/dependency-timeline-audit/gem_version.rb
56
+ - lib/dependency-timeline-audit/gem_version_cache.rb
57
+ - lib/dependency-timeline-audit/text_format.rb
52
58
  - lib/dependency-timeline-audit/version.rb
53
59
  homepage: https://github.com/CloudSecurityAlliance/Dependency-Timeline-Audit
54
60
  licenses:
@@ -1,41 +0,0 @@
1
- require 'net/http'
2
- require 'json'
3
-
4
- module DependencyTimelineAudit
5
- # Define a class for interacting with the RubyGems API
6
- class GemInfo
7
- API_URL = 'https://rubygems.org/api/v1/versions/'
8
- @@gem_cache = {}
9
-
10
- # Method to fetch the gem data and cache it
11
- def self.fetch_gem_data(gem_name)
12
- # Check if gem info is already cached
13
- unless @@gem_cache[gem_name]
14
- url = URI("#{API_URL}#{gem_name}.json")
15
- response = Net::HTTP.get(url)
16
- @@gem_cache[gem_name] = JSON.parse(response)
17
- end
18
-
19
- # Return cached gem info
20
- @@gem_cache[gem_name]
21
- end
22
-
23
- # Method to fetch the latest version and its created_at timestamp
24
- def self.latest_version(gem_name)
25
- versions = fetch_gem_data(gem_name)
26
- latest = versions.first # The first entry is the latest version
27
- version_number = latest['number']
28
- created_at = latest['created_at']
29
- { version: version_number, created_at: created_at }
30
- end
31
-
32
- # Method to fetch the created_at timestamp for a specific version
33
- def self.version_created_at(gem_name, version)
34
- versions = fetch_gem_data(gem_name)
35
- # Find the version that matches the requested version string
36
- version_info = versions.find { |v| v['number'] == version }
37
-
38
- version_info['created_at']
39
- end
40
- end
41
- end