File Coverage

blib/lib/Dist/Zilla/Plugin/SyncCPANfile.pm
Criterion Covered Total %
statement 66 67 98.5
branch 17 20 85.0
condition 7 10 70.0
subroutine 11 11 100.0
pod 0 2 0.0
total 101 110 91.8


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::SyncCPANfile;
2              
3             # ABSTRACT: Sync a cpanfile with the prereqs listed in dist.ini
4              
5             #use v5.10;
6              
7 4     4   10689001 use strict;
  4         26  
  4         137  
8 4     4   30 use warnings;
  4         11  
  4         111  
9              
10 4     4   25 use version;
  4         9  
  4         37  
11              
12 4     4   959 use Moose;
  4         450088  
  4         32  
13 4     4   28502 use namespace::autoclean;
  4         11  
  4         30  
14 4     4   345 use Path::Tiny;
  4         10  
  4         321  
15 4     4   2242 use CPAN::Audit;
  4         1246419  
  4         3778  
16              
17             with qw(
18             Dist::Zilla::Role::AfterBuild
19             );
20              
21             has cpan_audit => (
22             is => 'ro',
23             isa => 'Bool',
24             default => 0,
25             );
26              
27             has filename => (
28             is => 'ro',
29             isa => 'Str',
30             default => 'cpanfile',
31             );
32            
33             has comment => (
34             is => 'ro',
35             isa => 'ArrayRef[Str]',
36             default => sub {
37             [
38             ( sprintf 'This file is generated by %s v%s', __PACKAGE__, __PACKAGE__->VERSION // '<internal>' ),
39             'Do not edit this file directly. To change prereqs, edit the `dist.ini` file.',
40             ]
41             }
42             );
43              
44 11     11 0 3313744 sub mvp_multivalue_args { qw( comment ) }
45            
46             sub after_build {
47 11     11 0 1268569 my ($self) = @_;
48              
49 11         61 my $content = $self->_get_cpanfile();
50              
51             # need to write it to disk if we're in a
52             # phase that is not filemunge
53 11         2102 path( $self->filename )->spew_raw( $content );
54             }
55              
56             sub _get_cpanfile {
57 11     11   46 my ($self) = @_;
58              
59 11         119 my $audit = CPAN::Audit->new;
60              
61 11         260741 my $zilla = $self->zilla;
62 11         527 my $prereqs = $zilla->prereqs;
63            
64 11         172 my @types = qw(requires recommends suggests conflicts);
65 11         50 my @phases = qw(runtime build test configure develop);
66            
67 11         34 my $str = join "\n", ( map { "# $_" } @{ $self->comment } ), '', '';
  20         152  
  11         398  
68 11         53 for my $phase (@phases) {
69 55 100       491 my $prefix = $phase eq 'runtime' ? '' : (sprintf "\non '%s' => sub {\n", $phase );
70 55 100       137 my $postfix = $phase eq 'runtime' ? '' : "};\n";
71 55 100       116 my $indent = $phase eq 'runtime' ? '' : ' ';
72              
73 55         113 for my $type (@types) {
74 220         1378 my $req = $prereqs->requirements_for($phase, $type);
75              
76 220 100       19647 next unless $req->required_modules;
77              
78 11         117 $str .= $prefix;
79            
80 11         37 for my $module ( sort $req->required_modules ) {
81 12   100     135 my $version = $req->requirements_for_module( $module ) || 0;
82              
83 12 100       1254 if ( $self->cpan_audit ) {
84 2         8 my $min_version = _audit( $audit, $module, $version );
85 2   33     49 my $vuln_version_requested = $min_version && (
86             version->new( $version ) < version->new( $min_version )
87             );
88              
89 2 100 66     24 if ( $version == 0 && $vuln_version_requested ) {
    50          
90 1         5 $version = $min_version;
91             }
92             elsif ( $vuln_version_requested ) {
93 1         13 $self->log( "Prereq $module $version is vulnerable" );
94             }
95             }
96              
97 12         140 $str .= sprintf qq~%s%s "%s" => "%s";\n~,
98             $indent,
99             $type,
100             $module,
101             $version;
102             }
103              
104 11         41 $str .= $postfix;
105             }
106             }
107              
108 11         98863 return $str;
109             }
110              
111             sub _audit {
112 2     2   7 my ($audit, $module, $version) = @_;
113              
114 2         8 my $result = $audit->command( 'module', $module, $version );
115 2 50       156475 my ($module_data) = values %{ $result->{dists} || {} };
  2         13  
116              
117 2         6 my @versions;
118 2 50       3 for my $advisory ( @{ $module_data->{advisories} || [] } ) {
  2         8  
119 5   100     37 my ($fixed_version) = ( $advisory->{fixed_versions} // '' ) =~ m{(v?[0-9]+(?:\.[0-9]+){0,2})};
120 5 100       14 next if !$fixed_version;
121              
122 2         15 my $version_object = version->new( $fixed_version );
123 2         8 push @versions, $version_object;
124             }
125              
126 2         8 my ($min_version) = sort { $a <=> $b } @versions;
  0         0  
127 2         14 return $min_version;
128             }
129              
130             __PACKAGE__->meta->make_immutable;
131              
132             1;
133              
134             __END__
135              
136             =pod
137              
138             =encoding utf-8
139              
140             =head1 NAME
141              
142             Dist::Zilla::Plugin::SyncCPANfile - Sync a cpanfile with the prereqs listed in dist.ini
143              
144             =head1 VERSION
145              
146             version 0.03
147              
148             =head1 SYNOPSIS
149              
150             # in dist.ini
151             [SyncCPANfile]
152              
153             # configure it yourself
154             [SyncCPANfile]
155             filename = my-cpanfile
156             comment = This is my cpanfile
157              
158             Unlike L<Dist::Zilla::Plugin::CPANFile> this plugin does not
159             add a I<cpanfile> to the distribution but to the "disk".
160              
161             =head1 CONFIG
162              
163             =head2 filename
164              
165             With this config you can change the filename for the file. It defaults
166             to I<cpanfile>.
167              
168             [SyncCPANfile]
169             filename = my-cpanfile
170              
171             =head2 comment
172              
173             The default comment says, that the I<cpanfile> was generated by this plugin.
174             You can define your own comment.
175              
176             [SyncCPANfile]
177             comment = This is my cpanfile
178             comment = line 2
179              
180             =head2 cpan_audit
181              
182             When I<cpan_audit> is enabled, the required module version is not defined (or 0),
183             and the module has vulnerabilities, the "fixed version" storied in L<CPAN::Audit>
184             is used as a minimum version.
185              
186             [SyncCPANfile]
187             cpan_audit = 1
188              
189             [Prereqs]
190             ExtUtils::MakeMaker = 0
191              
192             L<ExtUtils::MakeMaker> has a vulnerability in versions E<lt>= 7.21. As the minimum
193             version in the I<dist.ini> is 0 and I<cpan_audit> is enabled, the I<cpanfile>
194             will use 7.22 as the minimum version (as of June 2023).
195              
196             As this depends on the I<CPAN::Audit> database, you should update I<CPAN::Audit>
197             regularly.
198              
199             For dependencies where a minimum version is defined and the defined version is
200             vulnerable a warning is shown.
201              
202             =head1 SEE ALSO
203              
204             L<Dist::Zilla::Plugin::CPANFile>, L<Dist::Zilla::Plugin::GitHubREADME::Badge>
205              
206             =for Pod::Coverage after_build mvp_multivalue_args
207              
208             =head1 AUTHOR
209              
210             Renee Baecker <reneeb@cpan.org>
211              
212             =head1 COPYRIGHT AND LICENSE
213              
214             This software is Copyright (c) 2021 by Renee Baecker.
215              
216             This is free software, licensed under:
217              
218             The Artistic License 2.0 (GPL Compatible)
219              
220             =cut