dependency-timeline-audit 0.0.2 → 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: d4fb437a64c2b990372ff5137b6deb79d5405f5522e81b02830108cc43596155
4
- data.tar.gz: 9edf8bfe737bb3991802e93c01d272aa786b4bd1dd8a6733c6de17099175b013
3
+ metadata.gz: 3ac86eaf5319d8a10dc42fd001c0f32a15a5421bd861ce24faf2b4bcb462493d
4
+ data.tar.gz: 9a67aac946d8265cb908d521ed90f2de25fe245bb0f141766faae266e4fb678d
5
5
  SHA512:
6
- metadata.gz: 9e19239773413b37e23d54c21dd22cbbc65f65102f778d5437676da683fb76a202bf197f7b01e6ce874f8ce6761e53ccd7d9e2d9985a4096d46e9a86ae63e769
7
- data.tar.gz: e52bc4921722fc2be009aed94f7009917d4034ccc48c4f130c4fac5651af46e482d58765fd0171c87ecdd2abb2431d479926de3be4b6dc9d8c08684cabf37374
6
+ metadata.gz: af4097c658cefeb2853b4fe6d4e1f517a976a7d6bde91eaf69660cd9da95f80f0cf392f3986b7298dafbaa34259b526f501c5170d481dcf2e0be84d306a68853
7
+ data.tar.gz: 70a6bbfd8e7c40c39c427133d8e528a00fc54d10bdb68784aadac12a3ad60b1fc1c34aca1fe73d4e88b9fe852282b50f3a527aeca69be8498bff84c6926c78ac
@@ -13,6 +13,7 @@ begin
13
13
  opts.on('-i', '--interactive-ignore', 'Allows interactively generating an ignore file')
14
14
  opts.on('-v', '--verbose', 'Provides more verbose output')
15
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")
16
17
  opts.on('--outdated-threshold=YEARS', Integer, 'Allows overwriting the number of years before a gem is considered outdated (default: 1)')
17
18
  opts.on_tail('-h', '--help', 'Prints this help') do
18
19
  puts opts
@@ -28,7 +29,6 @@ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
28
29
  exit(1)
29
30
  end
30
31
 
31
- DependencyTimelineAudit::Check.check(
32
- lockfile: options[:lockfile],
33
- verbose: options[:verbose]
34
- )
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,32 +1,29 @@
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', verbose: true)
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) if verbose
14
+ outdated_versions.push(gem) if gem.locked_version.outdated?
15
+ gem.print_info if config.verbose
18
16
  end
19
17
 
20
- print "\n" if verbose
18
+ print "\n" if config.verbose
21
19
 
22
20
  if outdated_versions.any?
23
- set_text_color_red
21
+ TextFormat.color = :red
24
22
  puts "Outdated gems detected!"
25
23
  puts " - #{outdated_versions.join(', ')}"
26
24
 
27
25
  exit(1) # Failure
28
26
  else
29
- reset_text_style
30
27
  puts "All gems are within the accepted threshold!"
31
28
 
32
29
  exit(0) # Success
@@ -35,61 +32,15 @@ module DependencyTimelineAudit
35
32
 
36
33
  private
37
34
 
38
- def self.gem_outdated?(released_at)
39
- released_at <= outdated_threshold
40
- end
41
-
42
- def self.print_info(gem, lock_released_at, latest_version)
43
- puts "Gem: \e[1m#{gem[:name]}\e[0m"
44
- set_text_color(lock_released_at, gem[:locked_version] == latest_version[:version])
45
- puts " - Locked to: #{gem[:locked_version]} (Released: #{format_date(lock_released_at)})"
46
- set_text_color(latest_version[:created_at])
47
- puts " - Latest: #{latest_version[:version]} (Released: #{format_date(latest_version[:created_at])})"
48
- reset_text_style
49
- end
50
-
51
- def self.set_text_color(released_at, using_latest = true)
52
- if gem_outdated?(released_at)
53
- set_text_color_red
54
- else
55
- if using_latest
56
- 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)
57
40
  else
58
- set_text_color_yellow
41
+ Gem.new(config: config, name: spec.name, locked_version: spec.version)
59
42
  end
60
43
  end
61
44
  end
62
-
63
- def self.set_text_bold
64
- print "\e[1m"
65
- end
66
-
67
- def self.set_text_color_red
68
- print "\e[31m"
69
- end
70
-
71
- def self.set_text_color_green
72
- print "\e[32m"
73
- end
74
-
75
- def self.set_text_color_yellow
76
- print "\e[33m"
77
- end
78
-
79
- def self.reset_text_style
80
- print "\e[0m"
81
- end
82
-
83
- def self.locked_gems
84
- lockfile = Bundler::LockfileParser.new(File.read('Gemfile.lock'))
85
- lockfile.specs.map do |gem|
86
- { name: gem.name, locked_version: gem.version.to_s }
87
- end
88
- end
89
-
90
- def self.format_date(date_string)
91
- date = Date.parse(date_string)
92
- date.strftime("%Y-%m-%d")
93
- end
94
45
  end
95
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 = 2
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.2
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-26 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.present? ? version_info['created_at'] : nil
39
- end
40
- end
41
- end