ASPEER-MakeMaker-Markdown-Publish

 view release on metacpan or  search on metacpan

t/01-targets.t  view on Meta::CPAN

#!perl

use strict;
use warnings;
use lib 'lib';

use Config;
use Cwd qw(abs_path getcwd);
use File::Path qw(make_path);
use File::Temp qw(tempdir);
use JSON::PP qw(decode_json);
use MIME::Base64 qw(decode_base64);
use Test::More;

use ASPEER::MakeMaker::Markdown::Publish::MM;


sub blurp {

    my ($fn, $text)=@_;
    open(my $output_fh, '>', $fn) || die "unable to write $fn: $!";
    print {$output_fh} $text;
    close($output_fh) || die "unable to close $fn: $!";
    return 1;

}


sub slurp {

    my ($fn)=@_;
    open(my $input_fh, '<', $fn) || die "unable to read $fn: $!";
    local $/=undef;
    my $text=<$input_fh>;
    close($input_fh) || die "unable to close $fn: $!";
    return $text;

}


#  Locate the adapter and shared parent before entering a disposable project
#
my $cwd=getcwd();
my $adapter_lib_dn=abs_path('lib');
my $common_pm_fn=abs_path($INC{'ASPEER/MakeMaker/MM.pm'});
$common_pm_fn=~s{[/\\]ASPEER[/\\]MakeMaker[/\\]MM\.pm$}{};
my $temporary_dn=tempdir(CLEANUP => 1);
chdir($temporary_dn) || die "unable to chdir $temporary_dn: $!";
make_path('lib/Sample', 'doc', 'local lib/Markdown');
my $local_lib_dn=abs_path('local lib');


#  The runtime publisher stub records exactly what the generated target passes
#
blurp('local lib/Markdown/Publish.pm', <<'PUBLISH_STUB');
package Markdown::Publish;
use JSON::PP qw(encode_json);
sub new {my ($class, $config_hr)=@_; return bless({config => $config_hr}, $class)}
sub run {
    my ($self, $action)=@_;
    open(my $output_fh, '>>', 'target.log') || die "unable to write target log: $!";
    print {$output_fh} encode_json({config => $self->{'config'}, action => $action}), "\n";
    close($output_fh) || die "unable to close target log: $!";
    return 1;
}
1;
PUBLISH_STUB
blurp('lib/Sample.pm', "package Sample;\nour \$VERSION='0.001';\n1;\n");
blurp('lib/Sample.pm.md', "# NAME\n\nSample - generated documentation target fixture\n");
blurp('README.md', "# Sample\n\nGenerated documentation target fixture.\n");
blurp('MANIFEST', "lib/Sample.pm\nlib/Sample.pm.md\nREADME.md\ndoc/guide.md\n");
blurp('doc/guide.md', "# Guide\n\nText.\n");
blurp('Makefile.PL', <<'MAKEFILE_PL');
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
    NAME         => 'Sample',
    VERSION_FROM => 'lib/Sample.pm',
    META_MERGE   => {
        'meta-spec' => {version => 2},
        x_documentation => {
            publish => {
                module  => 'Markdown::Publish::MkDocs',
                sources => ['doc'],
                base    => '/sample-docs/',
                output  => 'public',
                config  => 'doc/mkdocs/custom.yml',
                address => '127.0.0.1:8123',
                cloudflare => {config => 'doc/wrangler.jsonc'},
            },
        },
    },
);
MAKEFILE_PL


#  Generate a real Makefile through the public plugin import
#
local $ENV{'PERL5LIB'}=join(
    $Config{'path_sep'},
    grep {defined($_) && length($_)}
        ($local_lib_dn, $adapter_lib_dn, $common_pm_fn, $ENV{'PERL5LIB'})
);
is(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'Makefile.PL succeeds with publication plugin');
my $makefile=slurp('Makefile');
like($makefile, qr/^PERLRUN\s*=.*-MASPEER::MakeMaker::Markdown::Pod.*-MASPEER::MakeMaker::Markdown::Publish/m,
    'generated commands reload documentation before publication integration');
like($makefile, qr/^PUBLISH_PM_TARGET=\$\(PERLRUN\) -M\$\(PUBLISH_PM\).*-e /m,
    'target command explicitly reloads the publication dispatcher');
unlike($makefile, qr/^MM_PREFIX\s*=/m,
    'private Makefile prefix is not emitted');

foreach my $target_ar (
    [build => 'publish_build'], [serve => 'publish_serve'],
    [gh => 'publish_gh'], ['gh-push' => 'publish_gh-push'],
    [cloudflare => 'publish_cloudflare']
) {
    my ($action, $target)=@{$target_ar};
    like($makefile, qr/^\Q$target\E ::$/m,
        "$target target generated");
    like($makefile,
        qr/^\s*\@\$\(PUBLISH_PM_TARGET\) publish $action$/m,
        "$action target delegates explicitly");
}
unlike($makefile, qr/^mkdocs_build ::$/m, 'backend-specific targets are absent');
my @doc_target=($makefile=~/^doc :: readme$/mg);
is(scalar(@doc_target), 1, 'publication import generates one doc target');


#  Decode the private macro to prove values came from live META_MERGE input
#
my ($encoded)=$makefile=~/^PUBLISH_CONFIG\s*=\s*(\S+)$/m;
ok(defined($encoded) && length($encoded), 'publication configuration macro generated');
my $config_hr=decode_json(decode_base64($encoded));
is_deeply($config_hr->{'sources'}, ['doc'], 'source directories encoded');
is($config_hr->{'base'}, '/sample-docs/', 'publication base encoded');
is($config_hr->{'module'}, 'Markdown::Publish::MkDocs',
    'selected module encoded');
is($config_hr->{'config'}, 'doc/mkdocs/custom.yml',
    'MkDocs configuration location encoded');
is($config_hr->{'address'}, '127.0.0.1:8123',
    'MkDocs customization encoded');
is_deeply($config_hr->{'cloudflare'}, {config => 'doc/wrangler.jsonc'},
    'Cloudflare Worker configuration encoded');
ok(!exists($config_hr->{'name'}),
    'generated Makefile retains the user publication settings');


#  Execute generated targets through make and inspect the delegated calls
#
my $make=$Config{'make'} || 'make';
is(system($make, 'doc'), 0, 'generated documentation target succeeds');
like(slurp('lib/Sample.pm'), qr/^=head1 NAME$/m,
    'generated documentation target processes the sidecar');
is(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'Makefile regenerates after documentation updates VERSION_FROM');
is(system($make, 'publish_build'), 0, 'generated build target succeeds');
is(system($make, 'publish_serve'), 0, 'generated local server target delegates');
is(system($make, 'publish_gh'), 0,
    'generated local publication target delegates');
is(system($make, 'publish_gh-push'), 0,
    'generated publication push target delegates');
is(system($make, 'publish_cloudflare'), 0,
    'generated static-assets deployment target delegates');
my @target=map {decode_json($_)} grep {length($_)} split(/\n/, slurp('target.log'));
is_deeply(
    [map {$_->{'action'}} @target],
    ['build', 'serve', 'gh', 'gh-push', 'cloudflare'],
    'generated targets dispatch the selected action'
);
my $default_config_hr={%{$config_hr}, name => 'Sample'};
is_deeply($target[0]{'config'}, $default_config_hr,
    'distribution module name supplies the default site title');


#  An explicit publication name overrides the distribution module name
#
my $configured=slurp('Makefile.PL');
$configured=~s/(publish\s*=>\s*\{\n)/$1                name    => 'Sample documentation',\n/ ||
    die "unable to construct named publication fixture";
blurp('Makefile.PL', $configured);
is(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'Makefile.PL accepts an explicit publication name');
is(system($make, 'publish_build'), 0,
    'generated build target accepts the explicit publication name');
@target=map {decode_json($_)} grep {length($_)} split(/\n/, slurp('target.log'));
is($target[-1]{'config'}{'name'}, 'Sample documentation',
    'explicit publication name overrides the default');


#  Explicit and composed Pod imports remain idempotent
#
my $explicit=slurp('Makefile.PL');
$explicit=~s/(use ExtUtils::MakeMaker;\n)/$1use ASPEER::MakeMaker::Markdown::Pod;\n/ ||
    die "unable to construct explicit Pod import fixture";
blurp('Makefile.PL', $explicit);
is(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'explicit Pod and command-line publication imports succeed together');
$makefile=slurp('Makefile');
@doc_target=($makefile=~/^doc :: readme$/mg);
is(scalar(@doc_target), 1, 'combined imports generate one doc target');


#  Invalid custom metadata is rejected while generating the Makefile
#
my $invalid=slurp('Makefile.PL');
$invalid=~s/x_documentation\s*=>\s*\{/x_documentation => 'invalid', disabled => {/ ||
    die "unable to construct invalid metadata fixture";
blurp('Makefile.PL', $invalid);
isnt(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'invalid x_documentation metadata rejected');


#  Publication targets work when MakeMaker metadata supplies no settings
#
blurp('Makefile.PL', <<'MAKEFILE_PL');
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
    NAME         => 'Sample',
    VERSION_FROM => 'lib/Sample.pm',
);
MAKEFILE_PL
is(system($^X, '-MASPEER::MakeMaker::Markdown::Publish', 'Makefile.PL'), 0,
    'Makefile.PL accepts omitted publication metadata');
is(system($make, 'publish_cloudflare'), 0,
    'Cloudflare target delegates without publication metadata');
@target=map {decode_json($_)} grep {length($_)} split(/\n/, slurp('target.log'));
is_deeply($target[-1]{'config'}, {name => 'Sample'},
    'Cloudflare target passes only the default distribution name');

chdir($cwd) || die "unable to restore cwd $cwd: $!";
done_testing();



( run in 0.582 second using v1.01-cache-2.11-cpan-eca53f54451 )