Created
February 27, 2023 09:52
-
-
Save mtancoigne/1d1315af5fb576d09dc1dca275c6a643 to your computer and use it in GitHub Desktop.
Find outdated gems in sub-directories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
# Find projects with outdated dependencies | |
# | |
# Usage: | |
# airsec <gem> | |
# airsec <gem> [[min version] max version] | |
# | |
# Examples: | |
# airsec rails # Finds projects with outdated version of the "rails" gem | |
# airsec rails 7 # Finds projects with version of the "rails" gem < 7.0.0 | |
# airsec rails 6 7.0.2 # Finds projects with version of the "rails" gem between 6.0.0 and 7.0.2 | |
require 'json' | |
require 'net/https' | |
require 'uri/https' | |
# Ignored paths | |
IGNORES = [ | |
'node_modules/', # Some node modules have gemfiles | |
# 'unmonitored/project' | |
] | |
## | |
# Methods to find Gem lockfiles with specific outdated gem versions, or gems in a given range | |
class AirSec | |
attr_reader :directory, :gem, :max_version, :min_version, :ignores, :list | |
## | |
# @param directory [String] Working directory | |
# @param gem [String] Gem name, as found on rubygems | |
# @param min_version [String|nil] Optional minimum version (inclusive) | |
# @param max_version [String|nil] Optional maximum version (exclusive). Defaults to last version on Rubygem | |
# @param ignores [Array<String>] Optional list of paths to ignore | |
def initialize(directory, gem, min_version: nil, max_version: nil, ignores: []) | |
@last_version = nil | |
@list = {} | |
@directory = directory | |
@gem = gem | |
@min_version = min_version | |
@max_version = max_version || last_version | |
@ignores = ignores | |
limits = ["< #{@max_version}"] | |
limits << ">= #{min_version}" if min_version | |
@dependency = Gem::Dependency.new gem, limits | |
end | |
def find | |
Dir.glob(File.join(@directory, '**', 'Gemfile.lock')).sort.each do |node| | |
path = File.dirname node | |
next if File.directory?(node) || ignore?(path) | |
version = find_version node | |
next if skip? version | |
add version, path | |
end | |
@list = @list.keys.sort.to_h { |k| [k, @list[k]] } | |
end | |
def last_version | |
return @last_version if @last_version | |
uri = URI.parse("https://rubygems.org/api/v1/gems/#{@gem}.json") | |
response = Net::HTTP.get_response(uri) | |
raise "Gem '#{@gem}' not found" if response.code == 404 | |
hash = JSON.parse(response.body) | |
@last_version = Gem::Version.new hash['version'] | |
end | |
def summarize | |
puts "Directories using \"#{@gem}\" gem, in version #{@min_version ? ">= #{@min_version}, " : ''}< #{@max_version}#{@last_version ? ' (latest)' : ''}:" | |
@list.each_pair do |version, paths| | |
puts "\033[31m#{version}\033[39m" | |
paths.each { |path| puts " - #{path}" } | |
end | |
end | |
def to_json(*args) | |
@list.to_json(args) | |
end | |
private | |
def skip?(version) | |
version.nil? || [email protected]?(@gem, version) | |
end | |
def add(version, path) | |
@list[version] ||= [] | |
@list[version] << path | |
end | |
def find_version(file_path) | |
line = File.readlines(file_path, chomp: true).find { |l| matches? l } | |
return unless line | |
match = matches? line | |
return unless match | |
match[:version] | |
end | |
def matches?(line) | |
line.match(/^ #{@gem} \((?<version>.*)\)/) | |
end | |
def ignore?(path) | |
@ignores.each { |string| return true if path.include? string } | |
false | |
end | |
end | |
raise 'Invalid args amount' if ARGV.count < 1 || ARGV.count > 3 | |
gem = ARGV.shift | |
max = ARGV.pop | |
min = ARGV.pop | |
finder = AirSec.new '.', gem, min_version: min, max_version: max, ignores: IGNORES | |
finder.find | |
finder.summarize |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment