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 +4 -4
 - data/.DS_Store +0 -0
 - data/CHANGELOG.md +2 -2
 - data/README.md +36 -10
 - data/lib/podrpt/cli.rb +142 -0
 - data/lib/podrpt/configuration.rb +81 -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
 - metadata +11 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 3b806f0000eb01912678830776a5ef3a61582d552bd0d648eb5b3cfdb3896394
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: e590ece5be99ae5d5c63df07de580009e149f03ccc06c2d838a8fe687aa6216c
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 5bb4f467def5f841df6e848eed1f1eca29d7968763bbe65c144529c96dd77eb55e67f54e761811b6adade103cf8ab07b928d8a89901290b14bf747c15794c305
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 1056100004bb690b1db1c591738b0c02e0e439061435dea82ccf182ff7e23d60b5da2e59af8eff04b43802c43ca54b90c22b58e09d95795037610e127a122835
         
     | 
    
        data/.DS_Store
    ADDED
    
    | 
         Binary file 
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        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 
     | 
| 
      
 7 
     | 
    
         
            +
            **Features**
         
     | 
| 
      
 8 
     | 
    
         
            +
            - **Fast Analysis**: Uses the native CocoaPods gem API instead of shelling out to pod search.
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
            - Outdated Pod Detection 
     | 
| 
      
 10 
     | 
    
         
            +
            - **Outdated Pod Detection**: Compares versions in your **Podfile.lock** against the latest public releases.
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            - Risk Assessment 
     | 
| 
      
 12 
     | 
    
         
            +
            - **Risk Assessment**: Assign custom risk scores and owner teams to dependencies via a PodsRisk.yaml file.
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            - Dependency Filtering 
     | 
| 
      
 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 
     | 
| 
      
 16 
     | 
    
         
            +
            - **Slack Notifications**: Delivers a clean, formatted report directly to a Slack channel, perfect for CI/CD pipelines.
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
     | 
    
         
            -
            - CI/CD Focused 
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 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
         
     | 
    
        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
         
     | 
    
        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 
     | 
| 
      
 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- 
     | 
| 
      
 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:
         
     |