m2-market/solutions/mm-pdf-report/payload/scripts/generate.sh

145 lines
4 KiB
Bash
Executable file

#!/usr/bin/env bash
# mm-pdf generate — render Markdown → HTML → PDF using nasr-m2o container
set -euo pipefail
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
STYLE="${MM_PDF_STYLE:-dark}"
OUT=""
OUT_HTML=""
CONTAINER=""
INPUT=""
usage() {
echo "Usage: mm-pdf generate <input.md> [--out output.pdf] [--style dark|purple|light] [--container NAME]"
exit 1
}
while [[ $# -gt 0 ]]; do
case $1 in
--out) OUT="$2"; shift 2 ;;
--style) STYLE="$2"; shift 2 ;;
--container) CONTAINER="$2"; shift 2 ;;
--help|-h) usage ;;
*) INPUT="$1"; shift ;;
esac
done
[[ -z "$INPUT" ]] && usage
[[ ! -f "$INPUT" ]] && { echo "Error: $INPUT not found"; exit 1; }
INPUT_ABS="$(realpath "$INPUT")"
BASENAME="${INPUT_ABS%.md}"
OUT="${OUT:-${BASENAME}.pdf}"
OUT_ABS="$(realpath -m "$OUT")"
OUT_HTML_ABS="${OUT_ABS%.pdf}.html"
# Auto-discover container
if [[ -z "$CONTAINER" ]]; then
CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "nasr-m2o|m2o" | head -1)
[[ -z "$CONTAINER" ]] && { echo "Error: no m2o container running. Start nasr-m2o or pass --container"; exit 1; }
fi
echo "Using container: $CONTAINER"
# Copy files into container
TMP="/tmp/mm-pdf-$$"
docker exec "$CONTAINER" mkdir -p "$TMP"
docker cp "$INPUT_ABS" "$CONTAINER:$TMP/input.md"
if [[ -f "$SKILL_DIR/templates/${STYLE}.css" ]]; then
docker cp "$SKILL_DIR/templates/${STYLE}.css" "$CONTAINER:$TMP/style.css"
else
docker cp "$SKILL_DIR/templates/dark.css" "$CONTAINER:$TMP/style.css"
fi
# Step 1: Markdown → styled HTML (via Node/marked)
echo "Step 1: Converting Markdown → HTML..."
docker exec "$CONTAINER" bash -c "
cd $TMP
# Install marked locally in tmp dir so require() can find it
[ -d node_modules/marked ] || npm install marked --prefix . -q 2>/dev/null
node - <<'JSEOF'
const fs = require('fs');
const path = require('path');
const { marked } = require('marked');
const mdRaw = fs.readFileSync('input.md', 'utf8');
const css = fs.readFileSync('style.css', 'utf8');
// Strip YAML frontmatter
let body = mdRaw;
const fm = {};
const fmMatch = mdRaw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
if (fmMatch) {
fmMatch[1].split('\n').forEach(line => {
const i = line.indexOf(':');
if (i > 0) fm[line.slice(0,i).trim().toLowerCase()] = line.slice(i+1).trim();
});
body = mdRaw.slice(fmMatch[0].length);
}
// Extract first H1 as title
let title = fm.title || '';
if (!title) {
const h1 = body.match(/^#\s+(.+)$/m);
if (h1) { title = h1[1].trim(); body = body.replace(h1[0], ''); }
}
title = title || 'Machine.Machine';
const subtitle = fm.subtitle || fm.description || '';
const dateLine = [fm.client, fm.date].filter(Boolean).join(' \u00a0·\u00a0 ');
marked.setOptions({ gfm: true, breaks: false });
const contentHtml = marked.parse(body);
const coverHtml = \`<div class=\"cover\">
<div class=\"logo\">MACHINE.MACHINE</div>
<div>
<h1 class=\"title\">\${title}</h1>
\${subtitle ? '<div class=\"subtitle\">' + subtitle + '</div>' : ''}
</div>
\${dateLine ? '<div class=\"meta\">' + dateLine + '</div>' : ''}
</div>\`;
const html = \`<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>\${title}</title>
<style>\${css}</style>
</head>
<body>
\${coverHtml}
<div class=\"body-content\">
\${contentHtml}
</div>
</body>
</html>\`;
fs.writeFileSync('output.html', html);
console.log('HTML written:', html.length, 'bytes');
JSEOF
"
echo "Step 2: Rendering HTML → PDF..."
docker exec "$CONTAINER" bash -c "
cd $TMP
google-chrome \
--headless \
--no-sandbox \
--disable-gpu \
--disable-web-security \
--print-to-pdf=output.pdf \
--print-to-pdf-no-header \
--run-all-compositor-stages-before-draw \
--virtual-time-budget=5000 \
'file://$TMP/output.html'
"
# Copy both outputs back
docker cp "$CONTAINER:$TMP/output.html" "$OUT_HTML_ABS"
docker cp "$CONTAINER:$TMP/output.pdf" "$OUT_ABS"
docker exec "$CONTAINER" rm -rf "$TMP"
echo ""
echo "HTML: $OUT_HTML_ABS ($(du -h "$OUT_HTML_ABS" | cut -f1))"
echo "PDF: $OUT_ABS ($(du -h "$OUT_ABS" | cut -f1))"