lib/ES/Util.pm (558 lines of code) (raw):
package ES::Util;
use strict;
use warnings;
use v5.10;
use File::Copy::Recursive qw(rcopy rmove);
use Capture::Tiny qw(capture tee);
use Encode qw(decode_utf8);
use Path::Class qw(dir file);
use Parallel::ForkManager();
binmode( STDOUT, ':encoding(utf8)' );
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
run $Opts
build_chunked build_single
proc_man
timestamp
write_html_redirect
write_nginx_redirects
write_nginx_test_config
write_nginx_preview_config
start_web_resources_watcher
start_preview
build_web_resources
);
our $Opts = { procs => 3, lang => 'en' };
#===================================
sub build_chunked {
#===================================
my ( $index, $raw_dest, $dest, %opts ) = @_;
my $single = 0;
my $chunk = $opts{chunk} || 0;
my $version = $opts{version} || '';
my $multi = $opts{multi} || 0;
my $lenient = $opts{lenient} || '';
my $lang = $opts{lang} || 'en';
my $edit_urls = $opts{edit_urls};
my $section = $opts{section_title} || '';
my $subject = $opts{subject} || '';
my $private = $opts{private} || '';
my $resources = $opts{resource} || [];
my $noindex = $opts{noindex} || '';
my $page_header = custom_header($index) || $opts{page_header} || '';
my $latest = $opts{latest};
my $respect_edit_url_overrides = $opts{respect_edit_url_overrides} || '';
my $alternatives = $opts{alternatives} || [];
my $alternatives_summary = $raw_dest->file('alternatives_summary.json');
my $branch = $opts{branch};
my $roots = $opts{roots};
my $relativize = $opts{relativize};
die "Can't find index [$index]" unless -f $index;
$dest->rmtree;
$dest->mkpath;
$raw_dest->rmtree;
$raw_dest->mkpath;
my ( $output, $died );
my $chunks_path = dir("$raw_dest/.chunked");
$chunks_path->mkpath;
# Emulate asciidoc_dir because we use it to find shared asciidoc files
# but asciidoctor doesn't support it.
my $asciidoc_dir = dir('resources/asciidoc-8.6.8/')->absolute;
# We use the admonishment images from asciidoc so add it as a resource
# so we can find them
push @$resources, $asciidoc_dir;
eval {
$output = run(
'asciidoctor', '-v', '--trace',
'-r' => dir('resources/asciidoctor/lib/extensions.rb')->absolute,
'-b' => 'html5',
'-d' => 'book',
'-a' => 'showcomments=1',
'-a' => "lang=$lang",
'-a' => "source_branch=$branch",
# Use ` to delimit monospaced literals because our docs
# expect that
'-a' => 'compat-mode=legacy',
!$private ? () : ( '-a' => "private_edit_urls" ),
!$edit_urls ? () : ( '-a' => "edit_urls=" .
edit_urls_for_asciidoctor($edit_urls) ),
# Disable warning on missing attributes because we have
# missing attributes!
# '-a' => 'attribute-missing=warn',
'-a' => 'asciidoc-dir=' . $asciidoc_dir,
'-a' => 'resources=' . join(',', @$resources),
$latest ? () : ('-a' => "migration-warnings=false"),
$respect_edit_url_overrides ? ('-a' => "respect_edit_url_overrides=true") : (),
@{ $alternatives } ? (
'-a' => _format_alternatives($alternatives),
'-a' => "alternative_language_report=$raw_dest/alternatives_report.json",
'-a' => "alternative_language_summary=$alternatives_summary",
) : (),
$relativize ? ('-a' => 'relativize-link=https://www.elastic.co/') : (),
roots_opts( $roots ),
# Turn off style options because we'll provide our own
'-a' => 'stylesheet!',
'-a' => 'icons!',
# Turn off asciidoctor's default footer because we make our own
'-a' => 'nofooter',
# Pass chunking down
'-a' => 'chunk_level=' . ( $chunk + 1 ),
# Render the table of contents
'-a' => 'toc',
# Lock the destination file name to one we expect
'--out-file' => 'index.html',
# Asciidoctor doesn't pass the destination directory down to
# the converter so we do so here explicitly
'-a' => 'outdir=' . $raw_dest,
# Add some metadata
'-a' => 'dc.type=Learn/Docs/' . $section,
'-a' => 'dc.subject=' . $subject,
'-a' => 'dc.identifier=' . $version,
$multi ? ( '-a' => "title-extra= [$version]" ) : (),
$noindex ? ('-a' => 'noindex') : (),
$page_header ? ('-a' => "page-header=$page_header") : (),
'--destination-dir=' . $raw_dest,
docinfo($index),
$index
);
1;
} or do { $output = $@; $died = 1; };
_check_build_error( $output, $died, $lenient );
# Extract the TOC from the index.html page *before* we (potentially) replace
# the TOC on the index.html page with a custom title page.
extract_toc_from_index( $raw_dest );
_customize_title_page( $index, $raw_dest->file('index.html'), $single );
finish_build( $index->parent, $raw_dest, $dest, $lang, 0 );
}
#===================================
sub build_single {
#===================================
my ( $index, $raw_dest, $dest, %opts ) = @_;
my $single = 1;
my $type = $opts{type} || 'book';
my $toc = $opts{toc} || '';
my $lenient = $opts{lenient} || '';
my $version = $opts{version} || '';
my $multi = $opts{multi} || 0;
my $lang = $opts{lang} || 'en';
my $edit_urls = $opts{edit_urls};
my $section = $opts{section_title} || '';
my $subject = $opts{subject} || '';
my $private = $opts{private} || '';
my $noindex = $opts{noindex} || '';
my $resources = $opts{resource} || [];
my $page_header = custom_header($index) || $opts{page_header} || '';
my $latest = $opts{latest};
my $respect_edit_url_overrides = $opts{respect_edit_url_overrides} || '';
my $alternatives = $opts{alternatives} || [];
my $alternatives_summary = $raw_dest->file('alternatives_summary.json');
my $branch = $opts{branch};
my $roots = $opts{roots};
my $relativize = $opts{relativize};
my $extra = $opts{extra} || 0;
die "Can't find index [$index]" unless -f $index;
unless ( $opts{is_toc} ) {
# Usually books live in their own directory so we can just `rm -rf`
# those directories and start over. But the Table of Contents for all
# vrsions of a book is written to the directory that contains all of
# the versions of that book. `rm -rf`ed there we'd lose all of the
# versions of the book. So we just don't.
$dest->rmtree;
$dest->mkpath;
$raw_dest->rmtree;
$raw_dest->mkpath;
}
my ( $output, $died );
# Emulate asciidoc_dir because we use it to find shared asciidoc files
# but asciidoctor doesn't support it.
my $asciidoc_dir = dir('resources/asciidoc-8.6.8/')->absolute;
# We use the admonishment images from asciidoc so add it as a resource
# so we can find them
push @$resources, $asciidoc_dir;
eval {
$output = run(
'asciidoctor', '-v', '--trace',
'-r' => dir('resources/asciidoctor/lib/extensions.rb')->absolute,
'-b' => 'html5',
'-d' => $type,
'-a' => 'showcomments=1',
'-a' => "lang=$lang",
'-a' => "source_branch=$branch",
$private || !$edit_urls ? () : ( '-a' => "edit_urls=" .
edit_urls_for_asciidoctor($edit_urls) ),
'-a' => 'asciidoc-dir=' . $asciidoc_dir,
'-a' => 'resources=' . join(',', @$resources),
$latest ? () : ('-a' => "migration-warnings=false"),
$respect_edit_url_overrides ? ('-a' => "respect_edit_url_overrides=true") : (),
@{ $alternatives } ? (
'-a' => _format_alternatives($alternatives),
'-a' => "alternative_language_report=$raw_dest/alternatives_report.json",
'-a' => "alternative_language_summary=$alternatives_summary",
) : (),
# Disable warning on missing attributes because we have
# missing attributes!
# '-a' => 'attribute-missing=warn',
$relativize ? ('-a' => 'relativize-link=https://www.elastic.co/') : (),
roots_opts( $roots ),
# Turn off style options because we'll provide our own
'-a' => 'stylesheet!',
'-a' => 'icons!',
# Turn off asciidoctor's default footer because we make our own
'-a' => 'nofooter',
# Add some metadata
'-a' => 'dc.type=Learn/Docs/' . $section,
'-a' => 'dc.subject=' . $subject,
'-a' => 'dc.identifier=' . $version,
$multi ? ( '-a' => "title-extra= [$version]" ) : (),
$noindex ? ('-a' => 'noindex') : (),
$page_header ? ('-a' => "page-header=$page_header") : (),
# Turn on asciidoctor's table of contents generation if we want a TOC
$toc ? ('-a' => 'toc') : (),
'--destination-dir=' . $raw_dest,
docinfo($index),
$index
);
1;
} or do { $output = $@; $died = 1; };
_check_build_error( $output, $died, $lenient );
my $base_name = $index->basename;
$base_name =~ s/\.[^.]+$/.html/;
my $html_file = $raw_dest->file('index.html');
if ( $base_name ne 'index.html' ) {
my $src = $raw_dest->file($base_name);
rename $src, $html_file
or die "Couldn't rename <$src> to <index.html>: $!";
}
_customize_title_page( $index, $html_file, $single );
if ( $extra ) {
my $contents = $html_file->slurp( iomode => '<:encoding(UTF-8)' );
$contents =~ s{<!--EXTRA-->}{<!--EXTRA-->\n<div id="extra">\n$extra\n</div>} or
die "Couldn't add toc_extra to $contents";
$html_file->spew( iomode => '>:utf8', $contents );
}
finish_build( $index->parent, $raw_dest, $dest, $lang, $opts{is_toc} );
}
#===================================
# Customize a book's title page.
#
# By default, the title page shows just the table of contents.
#
# If a file exists named `<index>-custom-title-page.html`, the table of contents
# will be replaced by the contents of this file. Otherwise, if a file named
# `<index>-extra-title-page.html` exists, its contents will be added above the
# table of contents. (Note that the `<index>` may be different based on the
# book's "index" file.
#===================================
sub _customize_title_page {
#===================================
my ( $index, $html_file, $single ) = @_;
my $index_basename = $index->basename;
(my $custom_title_page = $index_basename) =~ s/(\.x)?\.a(scii)?doc$/-custom-title-page.html/ || die;
$custom_title_page = $index->parent->file( $custom_title_page );
(my $extra_title_page = $index_basename) =~ s/(\.x)?\.a(scii)?doc$/-extra-title-page.html/ || die;
$extra_title_page = $index->parent->file( $extra_title_page );
if ( -e $custom_title_page ) {
die "Using a custom title page is incompatible with --single" if $single;
die "Cannot have both custom and extra title pages for the same source file" if -e $extra_title_page;
my $custom_contents = $custom_title_page->slurp( iomode => '<:encoding(UTF-8)' );
my $contents = $html_file->slurp( iomode => '<:encoding(UTF-8)' );
$contents =~ s|<!--START_TOC-->.*<!--END_TOC-->|$custom_contents|sm or
die "Couldn't set custom contents in file";
$html_file->spew( iomode => '>:utf8', $contents );
}
elsif ( -e $extra_title_page ) {
my $extra_contents = $extra_title_page->slurp( iomode => '<:encoding(UTF-8)' );
my $contents = $html_file->slurp( iomode => '<:encoding(UTF-8)' );
# Wrapping the extra in a div is a relic of docbook. But we're trying
# to emulate docbook so here we are.
$contents =~ s|</h1></div>|</h1></div>\n<div>\n$extra_contents\n</div>| or
die "Couldn't add extra-title-page to $contents";
$html_file->spew( iomode => '>:utf8', $contents );
}
}
#===================================
sub _check_build_error {
#===================================
my ( $output, $died, $lenient ) = @_;
$output =~ s/INFO: possible invalid reference: /WARNING: invalid reference: /;
my @lines = split "\n", $output;
my @build_warnings = grep {/^(a2x|asciidoc(tor)?): (WARNING|ERROR):/} @lines;
my $warned = @build_warnings;
return unless $died || $warned;
my @warn = grep { /(WARNING|ERROR):/ || !/^(a2x|asciidoc(tor)?): / } @lines;
if ( $died || $warned && !$lenient ) {
die join "\n", ( '', @warn, '' );
}
warn join "\n", ( '', @warn, '' );
}
#===================================
sub run (@) {
#===================================
my @args = @_;
my ( $out, $err, $ok );
if ( $Opts->{verbose} ) {
say 'Running: ' . join(' ', map { "\"$_\"" } @args);
( $out, $err, $ok ) = tee { system(@args) == 0 };
}
else {
( $out, $err, $ok ) = capture { system(@args) == 0 };
}
my $combined = "$out\n$err";
$combined =~ s/^\s+|\s+$//g;
return $combined if $ok;
my $git_dir = $ENV{GIT_DIR} ? "in GIT_DIR $ENV{GIT_DIR}" : "";
die "Error executing: @args $git_dir\n---out---\n$out\n---err---\n$err\n---------\n"
unless $ok;
return $combined;
}
#===================================
sub finish_build {
#===================================
my ( $source, $raw_dest, $dest, $lang, $is_toc ) = @_;
# Write a file with the book's language into the raw directory so the
# templating can apply it now *and* on the fly later
$raw_dest->file('lang')->spew( iomode => '>:utf8', "$lang\n" );
# Apply template to HTML files
run 'node', 'template/cli.js', '--template', 'resources/web/template.html',
'--source', $raw_dest, '--dest', $dest,
$is_toc ? ('--tocmode') : ();
my $snippets_dest = $dest->subdir('snippets');
my $snippets_src;
}
#===================================
sub extract_toc_from_index {
#===================================
my $dir = shift;
my $html
= $dir->file('index.html')->slurp( 'iomode' => '<:encoding(UTF-8)' );
$html =~ s/^.+<!--START_TOC-->\n?//s;
$html =~ s/<!--END_TOC-->.*$//s;
$dir->file('toc.html')->spew( iomode => '>:utf8', $html );
}
#===================================
sub roots_opts {
#===================================
my $roots = shift;
my @result;
for ( keys %$roots ) {
push @result, ( '-a', $_ . '-root=' . $$roots{ $_ } );
}
return @result;
}
#===================================
sub docinfo {
#===================================
my $index = shift;
my $name = $index->basename;
$name =~ s/\.[^.]+$//;
my $docinfo = $index->dir->file("$name-docinfo.xml");
return -e $docinfo ? ( -a => 'docinfo' ) : ();
}
#===================================
sub custom_header {
#===================================
my $index = shift;
my $custom = $index->dir->file('page_header.html');
return unless -e $custom;
return scalar $custom->slurp( iomode => '<:encoding(UTF-8)' );
}
#===================================
sub edit_urls_for_asciidoctor {
#===================================
my $edit_urls = shift;
# We'd be better off using a csv library for this but we don't want to add
# more dependencies to the pl until we go docker-only.
return join("\n", map { "$_,$edit_urls->{$_}" } keys %{$edit_urls});
}
#===================================
sub _format_alternatives {
#===================================
my $alternatives = shift;
# We'd be better off using a csv library for this but it'll be ok for now.
return 'alternative_language_lookups=' . join(
"\n",
map { $_->{source_lang} . ',' . $_->{alternative_lang} . ',' . $_->{dir} } @{ $alternatives }
);
}
#===================================
sub write_html_redirect {
#===================================
my ( $dir, $url ) = @_;
my $html = <<"HTML";
<html>
<head>
<meta http-equiv="refresh" content="0; url=$url">
<meta name="robots" content="noindex">
</head>
<body>
Redirecting to <a href="$url">$url</a>.
</body>
</html>
HTML
$dir->file('index.html')->spew( iomode => '>:utf8', $html );
}
#===================================
# Write the redirects managed by nginx and run a basic self test on them.
#
# dest - file to which to write the redirecs : Path::Class::file
# docs_dir - directory containing generated docs : Path::Class::dir
# temp_dir - directory for writing temporary files : Path::Class::file
#===================================
sub write_nginx_redirects {
#===================================
my ( $dest, $docs_dir, $temp_dir ) = @_;
my $redirects = dir('resources')->file('legacy_redirects.conf')
->slurp( iomode => "<:encoding(UTF-8)" );
# Today we just have a list of redirects built long ago that we include
# in the generated docs. In the future we'll generate the redirects from
# the docs *somehow*.
$redirects =~ s/^(#.+)?\n//gm;
$dest->spew( iomode => '>:utf8', $redirects );
my $test_nginx_conf = $temp_dir->file( 'nginx.conf' );
write_nginx_test_config( $test_nginx_conf, $docs_dir, $dest, 0, 0 );
run( qw(nginx -t -c), $test_nginx_conf );
}
#===================================
# Build an nginx config file useful for serving the docs locally or running
# a self test on the redirects.
#
# dest - file to which to write the test config : Path::Class::file
# docs_dir - directory containing generated docs : Path::Class::dir
# redirects_file - file containing redirects or 0 if there aren't
# - any redirects : Path::Class::file||0
# waching_web - Truthy if we are watching web resources.
# preview_enabled - Truthy if the preview application is running and we should
# delegate to that.
#===================================
sub write_nginx_test_config {
#===================================
my ( $dest, $docs_dir, $redirects_file, $watching_web, $preview_enabled ) = @_;
my $redirects_line = $redirects_file ? "include $redirects_file;\n" : '';
my $web_conf;
if ( $watching_web ) {
$web_conf = <<"CONF"
rewrite ^/guide/static/docs-v1\\.js(.*)\$ /guide/static/docs_js/index-v1.js\$1 last;
location ^~ /guide/static/jquery.js {
alias /node_modules/jquery/dist/jquery.js;
types {
application/javascript js;
}
}
location ^~ /guide/static/ {
proxy_pass http://0.0.0.0:1234;
}
CONF
} else {
$web_conf = '';
}
my $guide_conf;
if ( $preview_enabled ) {
$guide_conf = <<"CONF"
location ~/(guide|diff) {
proxy_pass http://0.0.0.0:3000;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_cache_bypass \$http_upgrade;
proxy_buffering off;
gzip on;
add_header 'Access-Control-Allow-Origin' '*';
if (\$request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'kbn-xsrf-token';
}
}
CONF
} else {
$guide_conf = <<"CONF"
location /guide {
alias $docs_dir;
add_header 'Access-Control-Allow-Origin' '*';
if (\$request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'kbn-xsrf-token';
}
}
CONF
}
my $nginx_conf = <<"CONF";
daemon off;
error_log /dev/stdout info;
pid /run/nginx/nginx.pid;
events {
worker_connections 64;
}
http {
error_log /dev/stdout crit;
log_format short '[\$time_local] "\$request" \$status';
access_log /dev/stdout short;
server {
listen 8000;
location = / {
return 301 /guide/index.html;
}
$web_conf
$guide_conf
location / {
alias /docs_build/resources/web/static/;
autoindex off;
}
types {
application/javascript js;
image/gif gif;
image/jpeg jpg;
image/jpeg jpeg;
image/svg+xml svg;
text/css css;
text/html html;
}
rewrite ^/assets/(.+)\$ https://www.elastic.co/assets/\$1 permanent;
rewrite ^/gdpr-data\$ https://www.elastic.co/gdpr-data permanent;
rewrite ^/static/(.+)\$ https://www.elastic.co/static/\$1 permanent;
$redirects_line
}
}
CONF
$dest->spew( iomode => '>:utf8', $nginx_conf );
}
#===================================
# Build an nginx config file useful for serving a preview of all built docs.
#
# dest - file to which to write the test config : Path::Class::file
#===================================
sub write_nginx_preview_config {
#===================================
my ( $dest ) = @_;
my $preview_conf = <<"CONF";
proxy_pass http://0.0.0.0:3000;
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_cache_bypass \$http_upgrade;
proxy_buffering off;
gzip on;
add_header 'Access-Control-Allow-Origin' '*';
if (\$request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'kbn-xsrf-token';
}
CONF
# We log the X-Opaque-Id which is a header that Elasticsearch uses to mark
# requests with an id that is opaque to Elasticsearch. Presumably this is
# a standard. Either way we follow along. We use it in our tests so we can
# figure out which request came from which test. That is the only reason
# we *need* it right now. Presumably we'll find some other use for it later
# though. Think of it as a distributed trace id.
my $nginx_conf = <<"CONF";
daemon off;
error_log /dev/stdout info;
pid /run/nginx/nginx.pid;
events {
worker_connections 64;
}
http {
error_log /dev/stdout crit;
log_format short '\$http_x_opaque_id \$http_host \$request \$status';
access_log /dev/stdout short;
server {
listen 8000;
location = /liveness {
return 200 "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn.";
}
location = /robots.txt {
return 200 "User-agent: *\nDisallow: /\n";
}
location ~/(guide|diff) {
$preview_conf
}
location / {
alias /docs_build/resources/web/static/;
try_files \$uri \@preview;
autoindex off;
}
location \@preview {
$preview_conf
}
rewrite ^/assets/(.+)\$ https://www.elastic.co/assets/\$1 permanent;
rewrite ^/gdpr-data\$ https://www.elastic.co/gdpr-data permanent;
rewrite ^/static/(.+)\$ https://www.elastic.co/static/\$1 permanent;
}
}
CONF
$dest->spew( iomode => '>:utf8', $nginx_conf );
}
#===================================
sub proc_man {
#===================================
my ( $procs, $finish ) = @_;
my $pm = Parallel::ForkManager->new($procs);
$pm->set_waitpid_blocking_sleep(0.1);
$pm->run_on_finish(
sub {
if ( $_[1] ) {
kill -9, $pm->running_procs();
kill 9, $pm->running_procs();
die "Child exited with $_[1]";
}
$finish->(@_) if $finish;
}
);
return $pm;
}
#===================================
sub timestamp {
#===================================
my ( $sec, $min, $hour, $mday, $mon, $year )
= gmtime( @_ ? shift() : time() );
$year += 1900;
$mon++;
sprintf "%04d-%02d-%02dT%02d:%02d:%02d+00:00", $year, $mon, $mday, $hour,
$min, $sec;
}
#===================================
sub start_web_resources_watcher {
#===================================
my $parcel_pid = fork;
return $parcel_pid if $parcel_pid;
close STDIN;
open( STDIN, "</dev/null" );
exec( qw(/node_modules/parcel/bin/cli.js serve
--public-url /guide/static/
--hmr-port 8001
-d /tmp/parcel/
resources/web/docs_js/index-v1.js resources/web/styles-v1.pcss) );
}
#===================================
sub start_preview {
#===================================
my ( $command, $root, $default_template, $ignore_host ) = @_;
my $preview_pid = fork;
return $preview_pid if $preview_pid;
close STDIN;
open( STDIN, "</dev/null" );
exec( qw(node --max-old-space-size=128 /docs_build/preview/cli.js),
$command, $root,
'--default-template', $default_template,
( $ignore_host ? ('--ignore-host') : () )
);
}
#===================================
sub build_web_resources {
#===================================
my ( $dest ) = @_;
my $parcel_out = dir('/tmp/parcel');
my $compiled_js = $parcel_out->file('docs_js/index-v1.js');
my $compiled_css = $parcel_out->file('styles-v1.css');
unless ( -e $compiled_js && -e $compiled_css ) {
# We write the compiled js and css to /tmp so we can use them on
# subsequent runs in the same container. This doesn't come up when you
# build docs either with --doc or --all *but* it comes up all the time
# when you run the integration tests and saves about 1.5 seconds on
# every docs build.
say "Compiling web resources";
run '/node_modules/parcel/bin/cli.js', 'build',
'--public-url', '/guide/static/',
'--experimental-scope-hoisting', '--no-source-maps',
'-d', $parcel_out,
'resources/web/docs_js/index-v1.js', 'resources/web/styles-v1.pcss';
die "Parcel didn't make $compiled_js" unless -e $compiled_js;
die "Parcel didn't make $compiled_css" unless -e $compiled_css;
}
my $static_dir = $dest->subdir( 'raw' )->subdir( 'static' );
$static_dir->mkpath;
my $js = $static_dir->file( 'docs-v1.js' );
my $css = $static_dir->file( 'styles-v1.css' );
my $js_licenses = file( 'resources/web/docs.js.licenses' );
my $css_licenses = file( 'resources/web/styles.css.licenses' );
$js->spew(
iomode => '>:utf8',
$js_licenses->slurp( iomode => '<:encoding(UTF-8)' ) . $compiled_js->slurp( iomode => '<:encoding(UTF-8)' )
);
$css->spew(
iomode => '>:utf8',
$css_licenses->slurp( iomode => '<:encoding(UTF-8)' ) . $compiled_css->slurp( iomode => '<:encoding(UTF-8)' )
);
for ( $parcel_out->children ) {
next unless /.+\.woff2?/;
rcopy( $_, $static_dir );
}
rcopy( '/node_modules/jquery/dist/jquery.min.js', $static_dir->file( 'jquery.js' ) );
# The public site can't ready anything from the raw directory so we have to
# copy the static files to html as well.
my $templated_dir = $dest->subdir( 'html' )->subdir( 'static' );
$templated_dir->mkpath;
rcopy( $static_dir, $templated_dir );
# Copy the template to the root of the repo so we can apply it on the fly.
# NOTE: We only apply it on the fly for preview right now.
for ( qw(template air_gapped_template) ) {
my $template_source = file("resources/web/$_.html");
my $template = $dest->file("$_.html");
rcopy( $template_source, $template );
}
}
1