Sometimes it's useful to know who exactly is relying on your CPAN distribution - for example, if you're planning an incompatible API change for a module, and wish to contact people using it to give them advance notice. MetaCPAN handily includes a "reverse dependencies" link for every distribution. However, sometimes that might not be enough; you may want recursive results. In which case you need to dig around in the MetaCPAN API.

Thankfully there's a little module called MetaCPAN::API that helps you do just that. Here's how I've used it (plus Moo) to retrieve recursive dependencies starting at a seed...

  use v5.14;
  
  package MetaCPANx::RevDeps
  {
    use Moo;
    use List::MoreUtils qw(part);
    
    has seed => (
      is       => 'ro',
      required => 1,
      coerce   => sub {
        ref($_[0])
          ? $_[0]
          : 'MetaCPANx::RevDeps::Release'->new(distribution => $_[0]);
      },
    );
    
    has dependents => (
      is       => 'ro',
      builder  => sub { +{} },
    );
    
    sub add
    {
      my $self = shift;
      my $hash = $self->dependents;
      
      # prioritise higher version numbers; non-developer releases
      my @all =
        map  { sort { $b->version_numified <=> $a->version_numified } @$_ }
        part { $_->maturity eq 'developer' } @_;
      
      my @to_crawl;
      
      for my $dist (@all)
      {
        unless (exists $hash->{ $dist->distribution })
        {
          $hash->{ $dist->distribution } = $dist;
          push @to_crawl, $dist;
        }
      }
      
      for my $dist (@to_crawl)
      {
        $self->add( $dist->fetch_direct_dependents );
      }
    }
    
    sub BUILD
    {
      my $self = shift;
      $self->add( $self->seed->fetch_direct_dependents );
    }
  };
  
  package MetaCPANx::RevDeps::Release
  {
    use Moo;
    
    use overload q[""] => sub {
      my $self = shift;
      join q[ ], grep defined, $self->distribution, $self->version;
    };
    
    has distribution => (
      is       => 'ro',
      required => 1,
    );
    
    has abstract => (
      is       => 'ro',
      coerce   => sub { $_[0] =~ s/\s+/ /rsmg },
    );
    
    has [qw/ author date maturity version version_numified /] => (
      is       => 'ro',
    );
    
    has _metacpan => (
      is       => 'lazy',
      builder  => sub {
        require MetaCPAN::API;
        'MetaCPAN::API'->new;
      },
    );
    
    sub TO_JSON
    {
      my %hash = %{+shift};
      delete $hash{$_} for qw/ _metacpan /;
      \%hash;
    }
    
    sub fetch_direct_dependents
    {
      my $self  = shift;
      my $class = ref($self);
      
      my $data = $self->_metacpan->post(
        '/search/reverse_dependencies/' . $self->distribution,
        {
          query => {
            filtered => {
              query  => { 'match_all' => {} },
              filter => {
                and => [
                  { term => { 'release.status'     => 'latest' } },
                  { term => { 'release.authorized' => \1 } },
                ],
              },
            },
          },
          size => 5000,
          from => 0,
        },
      ) or return;
      
      return map {
        $class->new(
          %{ $_->{_source} },
          _metacpan => $self->_metacpan,
        )
      } @{ $data->{hits}{hits} };
    }
  };
  
  my $collection = 'MetaCPANx::RevDeps'->new(seed => 'MooX-late');
  
  for my $key (sort keys %{ $collection->dependents })
  {
    my $dist = $collection->dependents->{$key};
    printf("%s (%s) - %s\n", $dist, $dist->author, $dist->abstract);
  }

Caveats:

  • MetaCPAN includes build-time and testing dependencies, as well as optional dependencies in its data.
  • If you run this script on one of Moose's dependencies (including its optional dependencies), prepare to wait a very long time for results!