podrpt 0.1.0 → 1.0.1

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: 912c6a263f85b22b28f5ae69765387bf330eecf5900b9c9a895bace0436622c4
4
- data.tar.gz: 229b466805159032d6f09fff9d6af0478dd19f144c84bdf813f27fa398ee450c
3
+ metadata.gz: 3b806f0000eb01912678830776a5ef3a61582d552bd0d648eb5b3cfdb3896394
4
+ data.tar.gz: e590ece5be99ae5d5c63df07de580009e149f03ccc06c2d838a8fe687aa6216c
5
5
  SHA512:
6
- metadata.gz: 158bf9f7c90ede213f086a9294f1e900c69f0cc3239227d60176a16671ecc0c81d67d69f1356fd90c8df7548ec75aae271f80896ef60a3128e67979f9f333d19
7
- data.tar.gz: 7e1d97dfdab3508f6b54097855fa45fea86b1ac68f0ed732dbcfc7d7a46da75abb1b252237f0fde882631e39282a39a815a0ec461627982efc5fe91a1811bc39
6
+ metadata.gz: 5bb4f467def5f841df6e848eed1f1eca29d7968763bbe65c144529c96dd77eb55e67f54e761811b6adade103cf8ab07b928d8a89901290b14bf747c15794c305
7
+ data.tar.gz: 1056100004bb690b1db1c591738b0c02e0e439061435dea82ccf182ff7e23d60b5da2e59af8eff04b43802c43ca54b90c22b58e09d95795037610e127a122835
data/.DS_Store ADDED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2025-09-27
3
+ ## [1.0.1] - 2025-09-28
4
4
 
5
- - Initial release
5
+ - first release of podrpt with all the features so far
data/README.md CHANGED
@@ -4,38 +4,55 @@ Podrpt is a command-line tool written in Ruby to analyze and report outdated Coc
4
4
 
5
5
  It leverages the native CocoaPods API for high-performance analysis, avoiding slow external process calls for version fetching.
6
6
 
7
- Features
8
- - Fast Analysis: Uses the native CocoaPods gem API instead of shelling out to pod search.
7
+ **Features**
8
+ - **Fast Analysis**: Uses the native CocoaPods gem API instead of shelling out to pod search.
9
9
 
10
- - Outdated Pod Detection: Compares versions in your Podfile.lock against the latest public releases.
10
+ - **Outdated Pod Detection**: Compares versions in your **Podfile.lock** against the latest public releases.
11
11
 
12
- - Risk Assessment: Assign custom risk scores and owner teams to dependencies via a PodsRisk.yaml file.
12
+ - **Risk Assessment**: Assign custom risk scores and owner teams to dependencies via a PodsRisk.yaml file.
13
13
 
14
- - Dependency Filtering: Use an PodsAllowlist.yaml file to filter out transitive dependencies and focus only on the pods you directly manage.
14
+ - **Dependency Filtering**: Use an PodsAllowlist.yaml file to filter out transitive dependencies and focus only on the pods you directly manage.
15
15
 
16
- - Slack Notifications: Delivers a clean, formatted report directly to a Slack channel, perfect for CI/CD pipelines.
16
+ - **Slack Notifications**: Delivers a clean, formatted report directly to a Slack channel, perfect for CI/CD pipelines.
17
17
 
18
- - CI/CD Focused: Designed to run in automated environments without leaving behind unnecessary file artifacts.
18
+ - **CI/CD Focused**: Designed to run in automated environments without leaving behind unnecessary file artifacts.
19
19
 
20
20
  - Interactive Setup: A simple init command to generate all necessary configuration files.-
21
21
 
22
22
  ## Installation
23
23
 
24
24
  Add this line to your application's Gemfile:
25
- --> gem 'podrpt', '~> 0.1.0'
25
+ ```ruby
26
+ gem 'podrpt', '~> 1.0.0'
27
+ ```
26
28
 
27
29
  And then execute:
30
+ ```sh
28
31
  bundle install
32
+ ```
29
33
 
30
34
  ## Setup
31
35
  After installing the gem, you need to generate the configuration files in your project's root directory. Navigate to your iOS project's root folder and run the interactive setup command:
32
36
 
37
+ And then execute:
38
+ ```sh
33
39
  bundle exec podrpt init
40
+ ```
34
41
 
35
42
  This command will:
36
43
 
37
- 1 Create a sample PodsRisk.yaml file for defining risk scores.
38
- 2 Create a sample PodsAllowlist.yaml file for filtering dependencies.
44
+ 1 Create a sample **PodsRisk.yaml** file for defining risk scores.
45
+ ```yaml
46
+ default:
47
+ risk: 500
48
+ owners: []
49
+ pods:
50
+ XPTO:
51
+ risk: 100
52
+ owners: ['core-team']
53
+ ```
54
+
55
+ 2 Create a sample **PodsAllowlist.yaml** file for filtering dependencies.
39
56
  3 Prompt you for your Slack Incoming Webhook URL and save it securely in a .podrpt.yml file (which should be added to your .gitignore).
40
57
 
41
58
  After running init, customize the generated .yaml files to fit your project's needs.
@@ -43,7 +60,16 @@ After running init, customize the generated .yaml files to fit your project's ne
43
60
  ## Usage
44
61
  To run an analysis and send a report to Slack, simply execute the run command:
45
62
 
63
+ And then execute:
64
+ ```sh
46
65
  bundle exec podrpt run
66
+ ```
67
+
68
+ If you want to see what will be sent and what URL is configured, use this command:
69
+
70
+ ```sh
71
+ bundle exec podrpt run --dry-run
72
+ ```
47
73
 
48
74
  The report will only include outdated pods by default.
49
75
 
data/lib/podrpt/cli.rb ADDED
@@ -0,0 +1,142 @@
1
+ # lib/podrpt/cli.rb
2
+ module Podrpt
3
+ class CLI
4
+ def self.start(args)
5
+ command = args.shift || 'run'
6
+ case command
7
+ when 'run'
8
+ run_reporter(args)
9
+ when 'init'
10
+ initialize_configuration
11
+ when '--version', '-v'
12
+ puts Podrpt::VERSION
13
+ else
14
+ puts "Comando desconhecido: '#{command}'. Use 'run' ou 'init'."
15
+ exit 1
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def self.initialize_configuration
22
+ puts "🚀 Iniciando a configuração do Podrpt..."
23
+
24
+ pod_names_to_configure = []
25
+ project_dir = Dir.pwd
26
+ lockfile_path = File.join(project_dir, 'Podfile.lock')
27
+
28
+ if File.exist?(lockfile_path)
29
+ puts "📄 `Podfile.lock` encontrado. Analisando pods para pré-popular os arquivos..."
30
+ begin
31
+ # Analisa o lockfile para obter a lista completa de pods externos
32
+ analyzer = Podrpt::LockfileAnalyzer.new(project_dir)
33
+ all_pods_versions = analyzer.pod_versions
34
+ classified_pods = analyzer.classify_pods
35
+
36
+ # Filtramos apenas para pods que não são de desenvolvimento local
37
+ external_pods_filter = classified_pods[:spec_repo].dup
38
+ external_pods_filter -= classified_pods[:dev_path]
39
+
40
+ pods_to_configure = all_pods_versions.slice(*external_pods_filter.to_a)
41
+ pod_names_to_configure = pods_to_configure.keys
42
+
43
+ rescue => e
44
+ puts "⚠️ Erro ao analisar o `Podfile.lock`: #{e.message}. Os arquivos serão criados com exemplos."
45
+ end
46
+ else
47
+ puts "⚠️ `Podfile.lock` não encontrado. Os arquivos serão criados com exemplos."
48
+ end
49
+
50
+ # Cria os arquivos de configuração, passando a lista de pods encontrados
51
+ Podrpt::Configuration.create_allowlist_file(pod_names: pod_names_to_configure)
52
+ Podrpt::Configuration.create_risk_file(pod_names: pod_names_to_configure)
53
+
54
+ puts "\nAgora, por favor, informe a URL do seu Incoming Webhook do Slack:"
55
+ print "> "
56
+ url = $stdin.gets.chomp
57
+ Podrpt::Configuration.save_slack_url(url)
58
+ puts "\nConfiguração concluída! Edite os arquivos .yaml conforme necessário e execute 'podrpt run'."
59
+ end
60
+
61
+ # O método `run_reporter` e seus auxiliares permanecem os mesmos da última versão funcional
62
+ def self.run_reporter(args)
63
+ options = parse_run_options(args)
64
+ analyzer = Podrpt::LockfileAnalyzer.new(options.project_dir)
65
+ all_pods_versions = analyzer.pod_versions
66
+ classified_pods = analyzer.classify_pods
67
+
68
+ initial_filter = classified_pods[:spec_repo].dup
69
+ initial_filter -= classified_pods[:dev_path]
70
+ current_pods = all_pods_versions.slice(*initial_filter.to_a)
71
+
72
+ allowlist_config = load_allowlist(File.join(options.project_dir, options.allowlist_yaml))
73
+ pods_for_report = apply_allowlist_filter(current_pods, allowlist_config)
74
+ puts "[podrpt] Totais lock: #{all_pods_versions.size} | Pré-allowlist: #{current_pods.size} | Relatório final: #{pods_for_report.size}"
75
+ options.total_pods_count = pods_for_report.size
76
+
77
+ risk_config = load_risk_config(File.join(options.project_dir, options.risk_yaml))
78
+ if options.sync_risk_yaml
79
+ sync_risk_yaml(File.join(options.project_dir, options.risk_yaml), pods_for_report, risk_config)
80
+ end
81
+
82
+ version_fetcher = Podrpt::VersionFetcher.new(options)
83
+ latest_versions = version_fetcher.fetch_latest_versions_in_bulk(pods_for_report.keys)
84
+
85
+ final_analysis = []
86
+ pods_for_report.sort_by { |name, _| name.downcase }.each do |name, version|
87
+ pod_risk_info = risk_config['pods'][name] || risk_config['default']
88
+ analysis = Podrpt::PodAnalysis.new(name: name, current_version: version, latest_version: latest_versions[name], risk: pod_risk_info['risk'], owners: pod_risk_info['owners'] || [])
89
+ if options.only_outdated && !is_outdated(analysis.current_version, analysis.latest_version)
90
+ next
91
+ end
92
+ final_analysis << analysis
93
+ end
94
+
95
+ reporter = Podrpt::ReportGenerator.new(final_analysis, options)
96
+ report_text = reporter.build_report_text
97
+
98
+ Podrpt::SlackNotifier.notify(options.slack_webhook_url, report_text)
99
+ end
100
+
101
+ def self.parse_run_options(args)
102
+ options = Podrpt::Options.new(
103
+ project_dir: Dir.pwd,
104
+ risk_yaml: 'PodsRisk.yaml',
105
+ allowlist_yaml: 'PodsAllowlist.yaml',
106
+ only_outdated: true,
107
+ trunk_workers: 8,
108
+ slack_webhook_url: ENV['SLACK_WEBHOOK_URL'] || Podrpt::Configuration.load_slack_url
109
+ )
110
+ OptionParser.new do |opts|
111
+ opts.banner = "Usage: podrpt run [options]"
112
+ opts.on("--project-dir DIR", "Diretório do projeto") { |v| options.project_dir = v }
113
+ opts.on("--slack-webhook-url URL", "URL do Webhook (sobrescreve config)") { |v| options.slack_webhook_url = v }
114
+ opts.on("--show-all", "Mostra todos os pods") { |v| options.only_outdated = false }
115
+ opts.on("--sync-risk-yaml", "Sincroniza PodsRisk.yaml") { |v| options.sync_risk_yaml = v }
116
+ end.parse!(args)
117
+
118
+ unless options.slack_webhook_url
119
+ puts "❌ ERRO: URL do Slack não configurada. Rode 'podrpt init'."
120
+ exit 1
121
+ end
122
+ options
123
+ end
124
+
125
+ def self.load_allowlist(path); return {} unless File.exist?(path); config = YAML.load_file(path); config&.key?('allowlist') ? config['allowlist'] : {}; rescue; {}; end
126
+ def self.apply_allowlist_filter(all_pods, allowlist_config)
127
+ return all_pods if allowlist_config.nil? || allowlist_config.empty?
128
+ allowed_sets = allowlist_config.transform_values(&:to_set); vendor_prefixes = allowed_sets.keys
129
+ 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 }
130
+ end
131
+ def self.load_risk_config(path)
132
+ return { 'default' => { 'risk' => 500, 'owners' => [] }, 'pods' => {} } unless File.exist?(path)
133
+ config = YAML.load_file(path) || {}; config['default'] ||= { 'risk' => 500, 'owners' => [] }; config['pods'] ||= {}; config
134
+ end
135
+ def self.sync_risk_yaml(path, pods, config)
136
+ pods.keys.sort_by(&:downcase).each { |name| config['pods'][name] ||= config['default'].dup }
137
+ File.write(path, config.to_yaml)
138
+ puts "[podrpt] PodsRisk.yaml sincronizado com #{config['pods'].size} pods."
139
+ end
140
+ def self.is_outdated(current, latest); latest && !latest.empty? && Podrpt::VersionComparer.compare(latest, current) > 0; end
141
+ end
142
+ end
@@ -0,0 +1,81 @@
1
+ # lib/podrpt/configuration.rb
2
+ require 'yaml'
3
+
4
+ module Podrpt
5
+ class Configuration
6
+ CONFIG_FILE = '.podrpt.yml'.freeze
7
+ RISK_FILE = 'PodsRisk.yaml'.freeze
8
+ ALLOWLIST_FILE = 'PodsAllowlist.yaml'.freeze
9
+
10
+ def self.save_slack_url(url)
11
+ config = File.exist?(CONFIG_FILE) ? YAML.load_file(CONFIG_FILE) || {} : {}
12
+ config['slack_webhook_url'] = url
13
+ File.write(CONFIG_FILE, config.to_yaml)
14
+ puts "✅ URL do Slack salva em #{CONFIG_FILE}"
15
+ end
16
+
17
+ def self.load_slack_url
18
+ return nil unless File.exist?(CONFIG_FILE)
19
+ YAML.load_file(CONFIG_FILE)['slack_webhook_url']
20
+ end
21
+
22
+ def self.create_risk_file(pod_names: [])
23
+ return if File.exist?(RISK_FILE)
24
+
25
+ bands_config = { 'green_max' => 400, 'yellow_max' => 700 }
26
+ default_config = { 'owners' => [], 'risk' => 500 }
27
+
28
+ pods_hash = {}
29
+ if pod_names.empty?
30
+ puts "⚠️ Nenhum pod encontrado para pré-popular. Criando arquivo de risco com um exemplo."
31
+ pods_hash['Firebase'] = { 'owners' => ['core-team'], 'risk' => 100 }
32
+ else
33
+ pod_names.sort_by(&:downcase).each do |name|
34
+ pods_hash[name] = YAML.load(default_config.to_yaml)
35
+ end
36
+ end
37
+
38
+ final_structure = {
39
+ 'bands' => bands_config,
40
+ 'default' => default_config,
41
+ 'pods' => pods_hash
42
+ }
43
+
44
+ File.write(RISK_FILE, final_structure.to_yaml)
45
+ puts "✅ Arquivo '#{RISK_FILE}' criado e pré-populado com #{pods_hash.count} pods."
46
+ end
47
+
48
+ def self.create_allowlist_file(pod_names: [])
49
+ return if File.exist?(ALLOWLIST_FILE)
50
+
51
+ header = <<~YAML
52
+ # The allowlist is used to filter transitive dependencies (sub-dependencies)
53
+ # and focus only on the pods you manage directly in your Podfile.
54
+ #
55
+ # For each "group" (e.g., Firebase), only the pods listed here will appear in the report.
56
+ # Pods that don't belong to any group (e.g., Alamofire) will appear by default.
57
+ #
58
+ # Uncomment and adjust the examples below, or create your own groups.
59
+ YAML
60
+
61
+ example_content = {
62
+ 'allowlist' => {
63
+ 'Firebase' => ['FirebaseAnalytics', 'FirebaseCrashlytics']
64
+ }
65
+ }
66
+
67
+ project_pods_comment = pod_names.sort_by(&:downcase).map { |name| "# - #{name}" }.join("\n")
68
+
69
+ final_content = header + example_content.to_yaml
70
+ unless pod_names.empty?
71
+ final_content += "\n# --- Pods Encontrados no seu Projeto (descomente para usar) ---\n"
72
+ final_content += "# allowlist:\n"
73
+ final_content += "# MeuGrupo:\n"
74
+ final_content += project_pods_comment
75
+ end
76
+
77
+ File.write(ALLOWLIST_FILE, final_content)
78
+ puts "✅ Arquivo de exemplo '#{ALLOWLIST_FILE}' criado."
79
+ end
80
+ end
81
+ 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Podrpt
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.1"
5
5
  end
@@ -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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: podrpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Alves
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-27 00:00:00.000000000 Z
11
+ date: 2025-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocoapods
@@ -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,15 @@ 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
86
95
  - sig/podrpt.rbs
87
96
  homepage: https://github.com/swiftdrew/podrpt
88
97
  licenses: