podrpt 0.1.0 → 1.0.0
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 +4 -4
- data/.DS_Store +0 -0
- data/CHANGELOG.md +2 -2
- data/lib/podrpt/cli.rb +120 -0
- data/lib/podrpt/configuration.rb +38 -0
- data/lib/podrpt/lockfile_analyzer.rb +32 -0
- data/lib/podrpt/models.rb +14 -0
- data/lib/podrpt/report_generator.rb +35 -0
- data/lib/podrpt/slack_notifier.rb +39 -0
- data/lib/podrpt/version.rb +1 -1
- data/lib/podrpt/version_comparer.rb +18 -0
- data/lib/podrpt/version_fetcher.rb +29 -0
- data/podrpt-0.1.0.gem +0 -0
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 716eda643906d4b60d7d4e7baba1e004440c84ae01452a16b118800ca48a22a6
|
|
4
|
+
data.tar.gz: b2e8b2fd267002cf11476c6b9c35916801ba9c04b87ded24699fead6eb79c4db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8341dd94f68516d6c296ab9b2ba2139f18f52443bcd64c78a50953a286c0b2995f2623483cb050508561ea04a6eef779456a2d2c1b282cfa549e5e12217c7a4b
|
|
7
|
+
data.tar.gz: 3ee27c63e944aa812e46d9c27cb3ff2390787bfd70ad5d1972d97c8e54da947ae4c2844cbc2ccb865c87b793059b0d77b1a5d16e7441f5549aa7c54f9731dd71
|
data/.DS_Store
ADDED
|
Binary file
|
data/CHANGELOG.md
CHANGED
data/lib/podrpt/cli.rb
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module Podrpt
|
|
2
|
+
class CLI
|
|
3
|
+
def self.start(args)
|
|
4
|
+
command = args.shift || 'run'
|
|
5
|
+
case command
|
|
6
|
+
when 'run'
|
|
7
|
+
run_reporter(args)
|
|
8
|
+
when 'init'
|
|
9
|
+
initialize_configuration
|
|
10
|
+
when '--version', '-v'
|
|
11
|
+
puts Podrpt::VERSION
|
|
12
|
+
else
|
|
13
|
+
puts "Unknown command: '#{command}'. Use 'run' or 'init'."
|
|
14
|
+
exit 1
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def self.initialize_configuration
|
|
21
|
+
puts "🚀 Starting podrpt configuration..."
|
|
22
|
+
Podrpt::Configuration.create_risk_file
|
|
23
|
+
Podrpt::Configuration.create_allowlist_file
|
|
24
|
+
|
|
25
|
+
puts "\nNow, please enter the URL where the Webhook will be sent to Slack:"
|
|
26
|
+
print "> "
|
|
27
|
+
url = $stdin.gets.chomp
|
|
28
|
+
if url.empty?
|
|
29
|
+
puts "❌ An error occurred and this step was skipped, talk to the gem admin"
|
|
30
|
+
else
|
|
31
|
+
Podrpt::Configuration.save_slack_url(url)
|
|
32
|
+
end
|
|
33
|
+
puts "\nSetup complete! Edit the .yaml files and run 'podrpt run'."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.run_reporter(args)
|
|
37
|
+
options = parse_run_options(args)
|
|
38
|
+
|
|
39
|
+
analyzer = Podrpt::LockfileAnalyzer.new(options.project_dir)
|
|
40
|
+
all_pods_versions = analyzer.pod_versions
|
|
41
|
+
classified_pods = analyzer.classify_pods
|
|
42
|
+
|
|
43
|
+
initial_filter = classified_pods[:spec_repo].dup
|
|
44
|
+
initial_filter -= classified_pods[:dev_path]
|
|
45
|
+
current_pods = all_pods_versions.slice(*initial_filter.to_a)
|
|
46
|
+
|
|
47
|
+
allowlist_config = load_allowlist(File.join(options.project_dir, options.allowlist_yaml))
|
|
48
|
+
pods_for_report = apply_allowlist_filter(current_pods, allowlist_config)
|
|
49
|
+
puts "[podrpt] Lock totals: #{all_pods_versions.size} | Pre-allowlist: #{current_pods.size} | Final report: #{pods_for_report.size}"
|
|
50
|
+
options.total_pods_count = pods_for_report.size
|
|
51
|
+
|
|
52
|
+
risk_config = load_risk_config(File.join(options.project_dir, options.risk_yaml))
|
|
53
|
+
if options.sync_risk_yaml
|
|
54
|
+
sync_risk_yaml(File.join(options.project_dir, options.risk_yaml), pods_for_report, risk_config)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
version_fetcher = Podrpt::VersionFetcher.new(options)
|
|
58
|
+
latest_versions = version_fetcher.fetch_latest_versions_in_bulk(pods_for_report.keys)
|
|
59
|
+
|
|
60
|
+
final_analysis = []
|
|
61
|
+
pods_for_report.sort_by { |name, _| name.downcase }.each do |name, version|
|
|
62
|
+
pod_risk_info = risk_config['pods'][name] || risk_config['default']
|
|
63
|
+
analysis = Podrpt::PodAnalysis.new(name: name, current_version: version, latest_version: latest_versions[name], risk: pod_risk_info['risk'], owners: pod_risk_info['owners'] || [])
|
|
64
|
+
if options.only_outdated && !is_outdated(analysis.current_version, analysis.latest_version)
|
|
65
|
+
next
|
|
66
|
+
end
|
|
67
|
+
final_analysis << analysis
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
reporter = Podrpt::ReportGenerator.new(final_analysis, options)
|
|
71
|
+
report_text = reporter.build_report_text
|
|
72
|
+
|
|
73
|
+
Podrpt::SlackNotifier.notify(options.slack_webhook_url, report_text, dry_run: options.dry_run)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.parse_run_options(args)
|
|
77
|
+
options = Podrpt::Options.new(
|
|
78
|
+
project_dir: Dir.pwd,
|
|
79
|
+
risk_yaml: 'PodsRisk.yaml',
|
|
80
|
+
allowlist_yaml: 'PodsAllowlist.yaml',
|
|
81
|
+
only_outdated: true,
|
|
82
|
+
trunk_workers: 8,
|
|
83
|
+
slack_webhook_url: ENV['SLACK_WEBHOOK_URL'] || Podrpt::Configuration.load_slack_url,
|
|
84
|
+
dry_run: false
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
OptionParser.new do |opts|
|
|
88
|
+
opts.banner = "Usage: podrpt run [options]"
|
|
89
|
+
opts.on("--project-dir DIR", "Project DIR") { |v| options.project_dir = v }
|
|
90
|
+
opts.on("--slack-webhook-url URL", "URL Webhook (overwriting config)") { |v| options.slack_webhook_url = v }
|
|
91
|
+
opts.on("--show-all", "Show all pods") { |v| options.only_outdated = false }
|
|
92
|
+
opts.on("--sync-risk-yaml", "Sync PodsRisk.yaml") { |v| options.sync_risk_yaml = v }
|
|
93
|
+
opts.on("--dry-run", "Simulates sending to Slack, printing the payload in the terminal") { |v| options.dry_run = v }
|
|
94
|
+
end.parse!(args)
|
|
95
|
+
|
|
96
|
+
unless options.slack_webhook_url || options.dry_run
|
|
97
|
+
puts "❌ ERROR: Slack URL not configured. Run 'podrpt init' or use --dry-run."
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
options
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.load_allowlist(path); return {} unless File.exist?(path); config = YAML.load_file(path); config&.key?('allowlist') ? config['allowlist'] : {}; rescue; {}; end
|
|
104
|
+
def self.apply_allowlist_filter(all_pods, allowlist_config)
|
|
105
|
+
return all_pods if allowlist_config.nil? || allowlist_config.empty?
|
|
106
|
+
allowed_sets = allowlist_config.transform_values(&:to_set); vendor_prefixes = allowed_sets.keys
|
|
107
|
+
all_pods.select { |pod_name, _| matched_prefix = vendor_prefixes.find { |p| pod_name.start_with?(p) }; matched_prefix ? allowed_sets[matched_prefix].include?(pod_name) : true }
|
|
108
|
+
end
|
|
109
|
+
def self.load_risk_config(path)
|
|
110
|
+
return { 'default' => { 'risk' => 500, 'owners' => [] }, 'pods' => {} } unless File.exist?(path)
|
|
111
|
+
config = YAML.load_file(path) || {}; config['default'] ||= { 'risk' => 500, 'owners' => [] }; config['pods'] ||= {}; config
|
|
112
|
+
end
|
|
113
|
+
def self.sync_risk_yaml(path, pods, config)
|
|
114
|
+
pods.keys.sort_by(&:downcase).each { |name| config['pods'][name] ||= config['default'].dup }
|
|
115
|
+
File.write(path, config.to_yaml)
|
|
116
|
+
puts "[podrpt] PodsRisk.yaml synced with #{config['pods'].size} pods."
|
|
117
|
+
end
|
|
118
|
+
def self.is_outdated(current, latest); latest && !latest.empty? && Podrpt::VersionComparer.compare(latest, current) > 0; end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Podrpt
|
|
4
|
+
class Configuration
|
|
5
|
+
CONFIG_FILE = '.podrpt.yml'.freeze
|
|
6
|
+
RISK_FILE = 'PodsRisk.yaml'.freeze
|
|
7
|
+
ALLOWLIST_FILE = 'PodsAllowlist.yaml'.freeze
|
|
8
|
+
|
|
9
|
+
def self.save_slack_url(url)
|
|
10
|
+
config = File.exist?(CONFIG_FILE) ? YAML.load_file(CONFIG_FILE) || {} : {}
|
|
11
|
+
config['slack_webhook_url'] = url
|
|
12
|
+
File.write(CONFIG_FILE, config.to_yaml)
|
|
13
|
+
puts "✅ Slack URL saved to #{CONFIG_FILE}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.load_slack_url
|
|
17
|
+
return nil unless File.exist?(CONFIG_FILE)
|
|
18
|
+
YAML.load_file(CONFIG_FILE)['slack_webhook_url']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.create_risk_file
|
|
22
|
+
return if File.exist?(RISK_FILE)
|
|
23
|
+
content = {
|
|
24
|
+
'default' => { 'risk' => 500, 'owners' => [] },
|
|
25
|
+
'pods' => { '' => { 'risk' => 0, 'owners' => [''] } }
|
|
26
|
+
}.to_yaml
|
|
27
|
+
File.write(RISK_FILE, content)
|
|
28
|
+
puts "✅ Risk file '#{RISK_FILE}' created."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.create_allowlist_file
|
|
32
|
+
return if File.exist?(ALLOWLIST_FILE)
|
|
33
|
+
content = { 'allowlist' => { '' => [''] } }.to_yaml
|
|
34
|
+
File.write(ALLOWLIST_FILE, content)
|
|
35
|
+
puts "✅ Allow list '#{ALLOWLIST_FILE}' created."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Podrpt
|
|
2
|
+
class LockfileAnalyzer
|
|
3
|
+
def initialize(project_dir)
|
|
4
|
+
lockfile_path = File.join(project_dir, 'Podfile.lock')
|
|
5
|
+
raise "ERRO: #{lockfile_path} not found." unless File.exist?(lockfile_path)
|
|
6
|
+
@lockfile = Pod::Lockfile.from_file(Pathname.new(lockfile_path))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def pod_versions
|
|
10
|
+
pod_versions_map = {}
|
|
11
|
+
@lockfile.pod_names.each do |pod_name|
|
|
12
|
+
root_name = Pod::Specification.root_name(pod_name)
|
|
13
|
+
pod_versions_map[root_name] ||= @lockfile.version(pod_name).to_s
|
|
14
|
+
end
|
|
15
|
+
pod_versions_map
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def classify_pods
|
|
19
|
+
lockfile_hash = @lockfile.to_hash
|
|
20
|
+
all_pods = (@lockfile.pod_names || []).map { |n| Pod::Specification.root_name(n) }.to_set
|
|
21
|
+
git_pods = Set.new
|
|
22
|
+
dev_pods = Set.new
|
|
23
|
+
(lockfile_hash['EXTERNAL SOURCES'] || {}).each do |name, details|
|
|
24
|
+
root_name = Pod::Specification.root_name(name)
|
|
25
|
+
git_pods.add(root_name) if details.key?(:git)
|
|
26
|
+
dev_pods.add(root_name) if details.key?(:path)
|
|
27
|
+
end
|
|
28
|
+
spec_repo_pods = all_pods - git_pods - dev_pods
|
|
29
|
+
{ spec_repo: spec_repo_pods, git_source: git_pods, dev_path: dev_pods }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# lib/podrpt/models.rb
|
|
2
|
+
|
|
3
|
+
module Podrpt
|
|
4
|
+
Options = Struct.new(
|
|
5
|
+
:project_dir, :risk_yaml, :allowlist_yaml, :trunk_workers,
|
|
6
|
+
:only_outdated, :sync_risk_yaml, :total_pods_count, :slack_webhook_url,
|
|
7
|
+
:dry_run,
|
|
8
|
+
keyword_init: true
|
|
9
|
+
)
|
|
10
|
+
PodAnalysis = Struct.new(
|
|
11
|
+
:name, :current_version, :latest_version,
|
|
12
|
+
:risk, :owners, keyword_init: true
|
|
13
|
+
)
|
|
14
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Podrpt
|
|
2
|
+
class ReportGenerator
|
|
3
|
+
def initialize(pods_analysis, options)
|
|
4
|
+
@pods = pods_analysis
|
|
5
|
+
@options = options
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def build_report_text
|
|
9
|
+
outdated_count = @pods.count { |p| is_outdated?(p.current_version, p.latest_version) }
|
|
10
|
+
total_pods_in_report = @options.total_pods_count
|
|
11
|
+
header = "_Generated: #{Time.now.utc.iso8601}_\n" \
|
|
12
|
+
"_Outdated: #{outdated_count}/#{total_pods_in_report}_"
|
|
13
|
+
|
|
14
|
+
h_pod, h_ver, h_risk, h_owners = "Pod", "Versions", "Risk", "Owners"
|
|
15
|
+
pod_names = @pods.map(&:name); versions = @pods.map { |p| versions_cell(p) }; risks = @pods.map { |p| risk_cell(p) }; owners = @pods.map { |p| p.owners.empty? ? "—" : p.owners.join(', ') }
|
|
16
|
+
w_pod = [h_pod.length, pod_names.map(&:length).max || 0].max; w_ver = [h_ver.length, versions.map(&:length).max || 0].max; w_risk = [h_risk.length, risks.map(&:length).max || 0].max; w_owners = [h_owners.length, owners.map(&:length).max || 0].max
|
|
17
|
+
|
|
18
|
+
row_formatter = ->(c1, c2, c3, c4) { "| #{c1.ljust(w_pod)} | #{c2.ljust(w_ver)} | #{c3.rjust(w_risk)} | #{c4.ljust(w_owners)} |" }
|
|
19
|
+
|
|
20
|
+
lines = [header, ""]; lines << row_formatter.call(h_pod, h_ver, h_risk, h_owners)
|
|
21
|
+
sep_pod = ":-" + ("-" * (w_pod - 1)); sep_ver = ":-" + ("-" * (w_ver - 1)); sep_risk = ("-" * (w_risk - 1)) + ":"; sep_owners = ":-" + ("-" * (w_owners - 1))
|
|
22
|
+
lines << "|#{sep_pod}|#{sep_ver}|#{sep_risk}|#{sep_owners}|"
|
|
23
|
+
@pods.each_with_index { |_, i| lines << row_formatter.call(pod_names[i], versions[i], risks[i], owners[i]) }
|
|
24
|
+
|
|
25
|
+
lines.join("\n")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def is_outdated?(current, latest); latest && !latest.empty? && Podrpt::VersionComparer.compare(latest, current) > 0; end
|
|
31
|
+
def versions_cell(pod); current, latest = pod.current_version, pod.latest_version; return "#{current} (latest unknown)" if latest.nil? || latest.empty?; is_outdated?(current, latest) ? "#{current} -> #{latest}" : "#{current} (latest)"; end
|
|
32
|
+
def risk_cell(pod); "#{pod.risk} #{risk_emoji(pod.risk)}"; end
|
|
33
|
+
def risk_emoji(risk_value); return "🟢" if risk_value.nil?; case risk_value; when ...401 then "🟢"; when 401..700 then "🟡"; else "🔴"; end; end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# lib/podrpt/slack_notifier.rb
|
|
2
|
+
|
|
3
|
+
module Podrpt
|
|
4
|
+
class SlackNotifier
|
|
5
|
+
def self.notify(webhook_url, report_text, dry_run: false)
|
|
6
|
+
if dry_run
|
|
7
|
+
puts "\n--- SLACK NOTIFICATION DRY RUN ---"
|
|
8
|
+
puts "Target URL: #{webhook_url || 'Nenhuma URL fornecida'}"
|
|
9
|
+
puts "--- Payload ---"
|
|
10
|
+
puts report_text
|
|
11
|
+
puts "----------------------------------"
|
|
12
|
+
puts "Dry run completed. No notification was sent."
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unless webhook_url && !webhook_url.empty?
|
|
17
|
+
puts "ERRO: Slack URL not provided. Logging out."
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts "Sending report to Slack..."
|
|
22
|
+
headers = { 'Content-Type' => 'application/json' }
|
|
23
|
+
payload = { text: "```\n#{report_text}\n```" }.to_json
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
response = HTTParty.post(webhook_url, body: payload, headers: headers)
|
|
27
|
+
if response.success?
|
|
28
|
+
puts "Report sent successfully!"
|
|
29
|
+
else
|
|
30
|
+
puts "ERROR sending to Slack. Status: #{response.code}, Response: #{response.body}"
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
rescue => e
|
|
34
|
+
puts "Connection ERROR when sending to Slack: #{e.message}"
|
|
35
|
+
exit 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/podrpt/version.rb
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Podrpt
|
|
2
|
+
module VersionComparer
|
|
3
|
+
def self.tokenize(version); (version || '').to_s.scan(/[A-Za-z]+|\d+/).map { |t| t.match?(/\d+/) ? t.to_i : t.downcase }; end
|
|
4
|
+
def self.compare(a, b)
|
|
5
|
+
ta, tb = tokenize(a), tokenize(b)
|
|
6
|
+
[ta.length, tb.length].max.times do |i|
|
|
7
|
+
va, vb = ta[i] || 0, tb[i] || 0
|
|
8
|
+
if va.is_a?(vb.class)
|
|
9
|
+
next if va == vb
|
|
10
|
+
return va > vb ? 1 : -1
|
|
11
|
+
else
|
|
12
|
+
return va.is_a?(Integer) ? 1 : -1
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
0
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Podrpt
|
|
2
|
+
class VersionFetcher
|
|
3
|
+
def initialize(options)
|
|
4
|
+
@options = options
|
|
5
|
+
@sources_manager = Pod::Config.instance.sources_manager
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def fetch_latest_versions_in_bulk(pod_names)
|
|
9
|
+
return {} if pod_names.empty?
|
|
10
|
+
puts "Discovering the latest version for #{pod_names.length} pods..."
|
|
11
|
+
results = Concurrent::Map.new
|
|
12
|
+
pool = Concurrent::ThreadPoolExecutor.new(max_threads: @options.trunk_workers)
|
|
13
|
+
pod_names.each { |name| pool.post { results[name] = find_latest_version(name) } }
|
|
14
|
+
pool.shutdown
|
|
15
|
+
pool.wait_for_termination
|
|
16
|
+
Hash[results.each_pair.to_a]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def find_latest_version(pod_name)
|
|
22
|
+
set = @sources_manager.search(Pod::Dependency.new(pod_name))
|
|
23
|
+
set&.highest_version.to_s
|
|
24
|
+
rescue => e
|
|
25
|
+
warn " WARNING: Failed to fetch version for #{pod_name}: #{e.message}"
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/podrpt-0.1.0.gem
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: podrpt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Alves
|
|
@@ -74,6 +74,7 @@ executables:
|
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
|
+
- ".DS_Store"
|
|
77
78
|
- CHANGELOG.md
|
|
78
79
|
- CODE_OF_CONDUCT.md
|
|
79
80
|
- Gemfile
|
|
@@ -82,7 +83,16 @@ files:
|
|
|
82
83
|
- Rakefile
|
|
83
84
|
- bin/podrpt
|
|
84
85
|
- lib/podrpt.rb
|
|
86
|
+
- lib/podrpt/cli.rb
|
|
87
|
+
- lib/podrpt/configuration.rb
|
|
88
|
+
- lib/podrpt/lockfile_analyzer.rb
|
|
89
|
+
- lib/podrpt/models.rb
|
|
90
|
+
- lib/podrpt/report_generator.rb
|
|
91
|
+
- lib/podrpt/slack_notifier.rb
|
|
85
92
|
- lib/podrpt/version.rb
|
|
93
|
+
- lib/podrpt/version_comparer.rb
|
|
94
|
+
- lib/podrpt/version_fetcher.rb
|
|
95
|
+
- podrpt-0.1.0.gem
|
|
86
96
|
- sig/podrpt.rbs
|
|
87
97
|
homepage: https://github.com/swiftdrew/podrpt
|
|
88
98
|
licenses:
|