
Md2pdf Export
- 10 installs
- 849 repo stars
- Updated August 1, 2026
- wentorai/research-claw
Helps with ai & agent building tasks during AI-assisted development.
About
md2pdf-export is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- md2pdf-export
- AI & Agent Building
- AI-coding skill
Md2pdf Export by the numbers
- 10 all-time installs (skills.sh)
- Ranked #11,959 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wentorai/research-claw --skill md2pdf-exportAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10 |
|---|---|
| repo stars | ★ 849 |
| Last updated | August 1, 2026 |
| Repository | wentorai/research-claw ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Markdown → PDF/PNG/JPEG Export (Puppeteer Method)
Architecture Overview
The conversion pipeline mirrors Markdown Preview Enhanced's chromeExport():
Markdown (.md)
│
▼ [markdown-it + plugins]
Standalone HTML (with embedded CSS, math, diagrams)
│
▼ [Puppeteer launches headless Chrome]
Chrome loads the HTML via file:// protocol
│
├──▶ page.pdf() → PDF output
├──▶ page.screenshot() → PNG output
└──▶ page.screenshot() → JPEG outputThe key insight: Chrome IS the rendering engine. What Chrome renders is what you get — true WYSIWYG.
---
Step 0: Environment Check & Setup
Before any conversion, verify the environment is ready. Run the setup script:
bash skills/md2pdf-export/scripts/setup-env.shThis script is idempotent — safe to run multiple times. It handles:
- Detecting OS (macOS / Ubuntu / Debian / Alpine / CentOS)
- Installing Node.js (via nvm, prefers LTS)
- Installing npm packages globally:
puppeteer(which bundles Chromium) - Verifying Chrome/Chromium is launchable in headless mode
- Printing a diagnostic summary
If the user's system already has Node.js ≥ 18 and puppeteer installed, the script skips those steps.
Manual Setup (if the script fails)
For environments where the automated script doesn't work (e.g. restricted Docker containers):
# 1. Install Node.js >= 18
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc # or ~/.zshrc
nvm install --lts
# 2. Install puppeteer (bundles Chromium automatically)
npm install -g puppeteer
# 3. For Linux headless environments, install system dependencies
# Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y \
ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 \
libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 \
libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 \
libxfixes3 libxrandr2 xdg-utils wget
# 4. Verify
node -e "const p = require('puppeteer'); p.launch({headless:'new'}).then(b => { console.log('OK'); b.close(); })"Using an Existing Chrome Installation
If the system already has Chrome/Chromium installed, set the environment variable to skip Chromium download:
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome # or /usr/bin/chromium-browser---
Step 1: Convert Markdown to HTML
Use markdown-it with plugins for a feature-rich render. The conversion script at skills/md2pdf-export/scripts/md2pdf.js handles this internally, but the logic is:
1. Read the .md file 2. Parse YAML front-matter (for puppeteer config overrides) 3. Render Markdown → HTML via markdown-it with these plugins:
markdown-it-texmath+ KaTeX for math ($...$, $$...$$)markdown-it-highlightjsfor syntax highlightingmarkdown-it-anchor+markdown-it-toc-done-rightfor TOCmarkdown-it-footnotefor footnotes
4. Wrap in a full HTML document with:
- A CSS theme (GitHub-light by default, configurable)
- Embedded styles for print (
@media printrules) - All resources inlined (no external dependencies)
Step 2: Launch Headless Chrome & Export
The script then:
1. Launches Puppeteer with headless: 'new' (new headless mode) 2. Creates a new page, sets viewport (for PNG/JPEG) or paper size (for PDF) 3. Loads the HTML via page.setContent() or page.goto('file://...') 4. Waits for rendering to complete (waitUntil: 'networkidle0') 5. Calls the appropriate Puppeteer API:
- PDF:
page.pdf({ path, format, printBackground, margin, ... }) - PNG:
page.screenshot({ path, fullPage: true, type: 'png' }) - JPEG:
page.screenshot({ path, fullPage: true, type: 'jpeg', quality })
6. Closes the browser
---
Running the Conversion
Basic Usage
# PDF (default)
node skills/md2pdf-export/scripts/md2pdf.js input.md
# PNG
node skills/md2pdf-export/scripts/md2pdf.js input.md --type png
# JPEG with quality
node skills/md2pdf-export/scripts/md2pdf.js input.md --type jpeg --quality 90
# Custom output path
node skills/md2pdf-export/scripts/md2pdf.js input.md -o output.pdf
# A3 landscape
node skills/md2pdf-export/scripts/md2pdf.js input.md --format A3 --landscapeFront-Matter Configuration
Users can embed Puppeteer config directly in the Markdown file's YAML front-matter:
---
puppeteer:
format: A4
landscape: false
margin:
top: "20mm"
right: "20mm"
bottom: "20mm"
left: "20mm"
printBackground: true
displayHeaderFooter: true
headerTemplate: '<span style="font-size:8px;width:100%;text-align:center;"><span class="title"></span></span>'
footerTemplate: '<span style="font-size:8px;width:100%;text-align:center;"><span class="pageNumber"></span> / <span class="totalPages"></span></span>'
screenshot:
fullPage: true
width: 1200
deviceScaleFactor: 2
export_on_save:
puppeteer: true
---CLI Options Reference
| Option | Default | Description |
|---|---|---|
--type | pdf | Output type: pdf, png, jpeg |
-o, --output | <input>.pdf | Output file path |
--format | A4 | Paper format (A3, A4, A5, Letter, Legal, Tabloid) |
--landscape | false | Landscape orientation |
--no-background | Disable printing background graphics | |
--margin | 10mm | Uniform margin (or use --margin-top etc.) |
--quality | 80 | JPEG quality (1-100) |
--scale | 1 | Device scale factor for screenshots |
--width | 1200 | Viewport width for screenshots |
--theme | github | CSS theme: github, github-dark, minimal |
--chrome-path | auto | Path to Chrome/Chromium executable |
--wait | 0 | Extra wait time in ms after page load |
--toc | false | Generate table of contents |
---
Programmatic Usage (Node.js)
For integration into agent pipelines:
const { convertMarkdown } = require('skills/md2pdf-export/scripts/md2pdf.js');
const result = await convertMarkdown({
inputPath: './paper.md',
outputPath: './paper.pdf',
type: 'pdf', // 'pdf' | 'png' | 'jpeg'
puppeteerConfig: {
format: 'A4',
printBackground: true,
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
},
theme: 'github',
chromePath: null, // auto-detect
waitTimeout: 1000,
toc: true,
});
console.log(`Exported to: ${result.outputPath}`);---
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "Chromium revision is not downloaded" | Puppeteer can't find Chrome | Run npx puppeteer browsers install chrome or set PUPPETEER_EXECUTABLE_PATH |
| Blank PDF | Page not fully rendered before export | Increase --wait timeout, or check for async content |
| Missing fonts (CJK, etc.) | System fonts not installed | Install fonts-noto-cjk (Ubuntu) or noto-fonts-cjk (Arch) |
| "No usable sandbox" on Linux | Chrome sandboxing in Docker | Add --no-sandbox arg (already handled by the script in Docker detection) |
| Math not rendering | KaTeX CSS not loaded | Ensure markdown-it-texmath + KaTeX are in dependencies |
| Mermaid diagrams missing | Client-side JS not executed | Use --wait 2000 to give Mermaid time to render |
---
Advanced: Batch Conversion
For converting an entire directory of Markdown files:
# Convert all .md files in a directory to PDF
for f in docs/*.md; do
node skills/md2pdf-export/scripts/md2pdf.js "$f" -o "output/$(basename "${f%.md}.pdf")"
done---
Reference Files
references/puppeteer-config.md— Full Puppeteerpage.pdf()andpage.screenshot()API optionsreferences/themes.md— Available CSS themes and how to create custom ones
Puppeteer Configuration Reference
page.pdf() Options
These options map directly to Puppeteer's page.pdf() API and can be set via:
- CLI flags (e.g.
--format A4) - YAML front-matter in the .md file (under
puppeteer:) - Programmatic
puppeteerConfigobject
| Option | Type | Default | Description |
|---|---|---|---|
path | string | auto | Output file path |
format | string | "A4" | Paper format. Values: Letter, Legal, Tabloid, Ledger, A0-A6 |
width | string/number | — | Paper width (overrides format). E.g. "8.5in", "210mm" |
height | string/number | — | Paper height (overrides format) |
landscape | boolean | false | Paper orientation |
scale | number | 1 | Scale of the webpage rendering. Range: 0.1 to 2 |
printBackground | boolean | true | Print background graphics (colors, images) |
displayHeaderFooter | boolean | false | Display header and footer |
headerTemplate | string | — | HTML template for header. Can use classes: date, title, url, pageNumber, totalPages |
footerTemplate | string | — | HTML template for footer (same classes as header) |
margin.top | string | "10mm" | Top margin. Accepts CSS units: px, in, cm, mm |
margin.right | string | "10mm" | Right margin |
margin.bottom | string | "10mm" | Bottom margin |
margin.left | string | "10mm" | Left margin |
preferCSSPageSize | boolean | false | Give CSS @page size priority over format |
pageRanges | string | "" | Paper ranges to print, e.g. "1-5, 8, 11-13" |
omitBackground | boolean | false | Hides default white background for transparent PDFs |
timeout | number | 30000 | Maximum time in ms to wait for PDF generation |
Header/Footer Template Variables
Inside headerTemplate and footerTemplate, these CSS classes are auto-populated:
.date— formatted print date.title— document title.url— document URL.pageNumber— current page number.totalPages— total number of pages
Example footer:
<div style="font-size:8px; width:100%; text-align:center; color:#888;">
<span class="title"></span> — Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>Note: Header/footer templates must use inline styles. Font size must be explicitly set.
---
page.screenshot() Options
Used when exporting to PNG or JPEG.
| Option | Type | Default | Description |
|---|---|---|---|
path | string | auto | Output file path |
type | string | "png" | "png" or "jpeg" |
quality | number | — | JPEG quality (1-100). Only for JPEG type |
fullPage | boolean | true | Capture full scrollable page vs. viewport only |
clip | object | — | Clip region: { x, y, width, height } |
omitBackground | boolean | false | Transparent background (PNG only) |
encoding | string | "binary" | "binary" or "base64" |
Viewport Settings for Screenshots
Set viewport before screenshot to control output dimensions:
await page.setViewport({
width: 1200, // Viewport width in px
height: 800, // Viewport height (only matters if fullPage=false)
deviceScaleFactor: 2, // 2x for Retina-quality output
});---
Front-Matter Configuration
All Puppeteer options can be set in the Markdown file's YAML front-matter:
---
title: "My Document"
puppeteer:
format: A4
landscape: false
printBackground: true
displayHeaderFooter: true
margin:
top: "25mm"
bottom: "25mm"
left: "15mm"
right: "15mm"
headerTemplate: '<span style="font-size:8px;"></span>'
footerTemplate: >
<div style="font-size:8px;width:100%;text-align:center;">
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>
timeout: 3000
args:
- "--no-sandbox"
screenshot:
fullPage: true
width: 1440
deviceScaleFactor: 2
quality: 90
export_on_save:
puppeteer: true
puppeteer: ["pdf", "png"]
---Front-matter values take precedence over CLI flags, which take precedence over defaults.
---
Chrome Launch Options
| Option | Environment Variable | Description |
|---|---|---|
| Executable path | PUPPETEER_EXECUTABLE_PATH | Path to Chrome/Chromium binary |
| Skip download | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD | Don't download bundled Chromium |
| Cache dir | PUPPETEER_CACHE_DIR | Where to store downloaded browsers |
--no-sandbox | — | Required in Docker/rootless environments |
--disable-setuid-sandbox | — | Required alongside --no-sandbox |
--disable-dev-shm-usage | — | Use /tmp instead of /dev/shm (Docker) |
--disable-gpu | — | Disable GPU hardware acceleration |
--font-render-hinting=none | — | Better font rendering on Linux |
CSS Themes Reference
Built-in Themes
The converter ships with three built-in themes:
github (default)
- Light theme mimicking GitHub's Markdown rendering
- Sans-serif font stack: system UI → Helvetica → Arial
- Max width 980px, centered
- Subtle borders on h1/h2, striped tables
- Best for: documentation, READMEs, technical reports
github-dark
- Dark variant of the GitHub theme
- Background:
#0d1117, text:#c9d1d9 - Appropriate for dark-mode documents or presentations
- Best for: slides, developer documentation for dark-mode readers
minimal
- Serif body font (Georgia), clean and readable
- Narrower width (700px) for better readability
- Lighter borders, more whitespace
- Best for: essays, papers, articles, long-form content
Creating a Custom Theme
A theme is just a CSS string. To add a custom theme:
Option 1: Extend md2pdf.js
const { THEMES, convertMarkdown } = require('./md2pdf.js');
// Add custom theme
THEMES['academic'] = `
body {
font-family: "Computer Modern", "Latin Modern Roman", serif;
font-size: 12pt;
line-height: 1.5;
max-width: 6.5in;
margin: 0 auto;
padding: 1in;
}
h1 { font-size: 1.5em; text-align: center; margin-bottom: 2em; }
h2 { font-size: 1.2em; margin-top: 1.5em; }
/* ... */
`;
convertMarkdown({ inputPath: 'paper.md', theme: 'academic' });Option 2: Use front-matter CSS injection
---
puppeteer:
format: A4
---
<style>
body { font-family: "Noto Serif CJK SC", serif; }
h1 { color: #c41e3a; }
</style>
# My DocumentThe <style> tag in Markdown body is rendered as-is into the HTML, so it overrides the base theme.
Option 3: Print-specific CSS
Use @media print to apply styles only in PDF output:
---
puppeteer:
format: A4
---
<style>
@media print {
body { font-size: 10pt; }
.no-print { display: none; }
h1 { page-break-before: always; }
h1:first-of-type { page-break-before: avoid; }
}
</style>Page Break Control
Insert page breaks in your Markdown:
Some content on page 1.
<!-- pagebreak -->
This starts on a new page.CSS-based page breaks also work:
h1 { page-break-before: always; }
table { page-break-inside: avoid; }#!/usr/bin/env node
// ============================================================================
// md2pdf-export: Markdown → PDF/PNG/JPEG Converter
// Uses the same Markdown → HTML → Headless Chrome pipeline as
// Markdown Preview Enhanced (crossnote/mume)
// ============================================================================
const fs = require('fs');
const path = require('path');
// ---------------------------------------------------------------------------
// Dependency resolution: try local node_modules first, then global
// ---------------------------------------------------------------------------
function tryRequire(name) {
try {
return require(name);
} catch {
// Try from script's local node_modules
const localPath = path.join(__dirname, 'node_modules', name);
return require(localPath);
}
}
const matter = tryRequire('gray-matter');
const markdownIt = tryRequire('markdown-it');
const highlightjs = tryRequire('markdown-it-highlightjs');
const texmath = tryRequire('markdown-it-texmath');
const katex = tryRequire('katex');
const anchor = tryRequire('markdown-it-anchor');
const tocPlugin = tryRequire('markdown-it-toc-done-right');
const footnote = tryRequire('markdown-it-footnote');
const puppeteer = tryRequire('puppeteer');
// ---------------------------------------------------------------------------
// CSS Themes
// ---------------------------------------------------------------------------
const THEMES = {
github: `
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 16px;
line-height: 1.6;
color: #24292e;
max-width: 980px;
margin: 0 auto;
padding: 2em;
}
h1, h2, h3, h4, h5, h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; }
h1 { font-size: 2em; padding-bottom: 0.3em; border-bottom: 1px solid #eaecef; }
h2 { font-size: 1.5em; padding-bottom: 0.3em; border-bottom: 1px solid #eaecef; }
h3 { font-size: 1.25em; }
code { background: #f6f8fa; padding: 0.2em 0.4em; border-radius: 3px; font-size: 85%; }
pre { background: #f6f8fa; padding: 16px; border-radius: 6px; overflow: auto; }
pre code { background: none; padding: 0; font-size: 85%; }
blockquote { padding: 0 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; margin: 0 0 16px 0; }
table { border-collapse: collapse; width: 100%; margin-bottom: 16px; }
table th, table td { padding: 6px 13px; border: 1px solid #dfe2e5; }
table tr:nth-child(2n) { background: #f6f8fa; }
table th { font-weight: 600; background: #f1f3f5; }
img { max-width: 100%; }
hr { border: none; border-top: 1px solid #eaecef; margin: 24px 0; }
a { color: #0366d6; text-decoration: none; }
ul, ol { padding-left: 2em; }
.footnotes { margin-top: 2em; border-top: 1px solid #eaecef; padding-top: 1em; font-size: 0.9em; }
.toc { background: #f6f8fa; padding: 1em 1.5em; border-radius: 6px; margin-bottom: 2em; }
.toc ul { list-style: none; padding-left: 1em; }
.toc > ul { padding-left: 0; }
.toc a { color: #0366d6; }
`,
'github-dark': `
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: #c9d1d9;
background: #0d1117;
max-width: 980px;
margin: 0 auto;
padding: 2em;
}
h1, h2, h3, h4, h5, h6 { color: #e6edf3; margin-top: 24px; margin-bottom: 16px; font-weight: 600; }
h1, h2 { padding-bottom: 0.3em; border-bottom: 1px solid #21262d; }
code { background: #161b22; padding: 0.2em 0.4em; border-radius: 3px; font-size: 85%; }
pre { background: #161b22; padding: 16px; border-radius: 6px; overflow: auto; }
pre code { background: none; padding: 0; }
blockquote { color: #8b949e; border-left: 0.25em solid #30363d; padding: 0 1em; }
table { border-collapse: collapse; width: 100%; }
table th, table td { padding: 6px 13px; border: 1px solid #30363d; }
table tr:nth-child(2n) { background: #161b22; }
table th { background: #21262d; }
a { color: #58a6ff; }
hr { border: none; border-top: 1px solid #21262d; }
`,
minimal: `
body {
font-family: "Georgia", "Times New Roman", serif;
font-size: 17px;
line-height: 1.8;
color: #333;
max-width: 700px;
margin: 0 auto;
padding: 2em;
}
h1, h2, h3, h4, h5, h6 { font-family: "Helvetica Neue", Arial, sans-serif; margin-top: 2em; }
h1 { font-size: 1.8em; }
h2 { font-size: 1.4em; }
code { font-family: "SF Mono", Monaco, Consolas, monospace; background: #f5f5f5; padding: 0.15em 0.3em; border-radius: 2px; font-size: 0.85em; }
pre { background: #f5f5f5; padding: 1em; border-radius: 4px; overflow: auto; }
pre code { background: none; padding: 0; }
blockquote { border-left: 3px solid #ccc; padding-left: 1em; color: #666; font-style: italic; }
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
table th, table td { padding: 8px 12px; border-bottom: 1px solid #ddd; text-align: left; }
table th { font-weight: bold; }
a { color: #1a0dab; }
img { max-width: 100%; }
`,
};
// ---------------------------------------------------------------------------
// KaTeX CSS (inlined to avoid external dependency at render time)
// ---------------------------------------------------------------------------
function getKatexCSS() {
try {
const katexCSSPath = require.resolve('katex/dist/katex.min.css');
return fs.readFileSync(katexCSSPath, 'utf-8');
} catch {
// Fallback: minimal katex styling
return `
.katex { font-size: 1.1em; }
.katex-display { display: block; margin: 1em 0; text-align: center; }
`;
}
}
// ---------------------------------------------------------------------------
// highlight.js CSS
// ---------------------------------------------------------------------------
function getHighlightCSS() {
try {
const hljsPath = path.join(
path.dirname(require.resolve('highlight.js/package.json')),
'styles', 'github.css'
);
return fs.readFileSync(hljsPath, 'utf-8');
} catch {
return '';
}
}
// ---------------------------------------------------------------------------
// Markdown → HTML
// ---------------------------------------------------------------------------
function renderMarkdown(content, options = {}) {
const md = markdownIt({
html: true,
linkify: true,
typographer: true,
breaks: false,
});
// Plugins
md.use(highlightjs, { auto: true, code: true });
md.use(texmath, { engine: katex, delimiters: 'dollars', katexOptions: { throwOnError: false } });
md.use(anchor, { permalink: false });
md.use(footnote);
if (options.toc) {
md.use(tocPlugin, {
containerClass: 'toc',
listType: 'ul',
});
// Prepend TOC placeholder
content = '${toc}\n\n' + content;
}
return md.render(content);
}
// ---------------------------------------------------------------------------
// Build full HTML document
// ---------------------------------------------------------------------------
function buildHTML(markdownBody, options = {}) {
const theme = THEMES[options.theme] || THEMES.github;
const katexCSS = getKatexCSS();
const highlightCSS = getHighlightCSS();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${options.title || 'Document'}</title>
<style>${katexCSS}</style>
<style>${highlightCSS}</style>
<style>${theme}</style>
<style>
/* Print-specific styles */
@media print {
body { margin: 0; padding: 1em; }
pre { white-space: pre-wrap; word-wrap: break-word; }
a[href]::after { content: none !important; }
}
/* Pagebreak support: <!-- pagebreak --> renders as <hr class="pagebreak"> */
hr.pagebreak {
page-break-after: always;
border: none;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
${markdownBody}
</body>
</html>`;
}
// ---------------------------------------------------------------------------
// Core conversion function
// ---------------------------------------------------------------------------
async function convertMarkdown(options) {
const {
inputPath,
outputPath: userOutputPath,
type = 'pdf',
theme = 'github',
chromePath = null,
waitTimeout = 0,
toc = false,
puppeteerConfig = {},
screenshotConfig = {},
noSandbox = false,
} = options;
// 1. Read & parse front-matter
const raw = fs.readFileSync(inputPath, 'utf-8');
const { data: frontMatter, content: markdownContent } = matter(raw);
// 2. Merge front-matter puppeteer config with CLI config
const fmPuppeteer = frontMatter.puppeteer || {};
const fmScreenshot = frontMatter.screenshot || {};
// 3. Render Markdown → HTML
const title = frontMatter.title || path.basename(inputPath, path.extname(inputPath));
const htmlBody = renderMarkdown(markdownContent, { toc });
// Support <!-- pagebreak --> syntax
const htmlBodyProcessed = htmlBody.replace(
/<!--\s*pagebreak\s*-->/gi,
'<hr class="pagebreak">'
);
const fullHTML = buildHTML(htmlBodyProcessed, { theme, title });
// 4. Determine output path
const ext = type === 'pdf' ? '.pdf' : type === 'png' ? '.png' : '.jpeg';
const outputPath = userOutputPath || inputPath.replace(/\.md$/i, ext);
// 5. Launch Puppeteer
const isContainer = fs.existsSync('/.dockerenv') ||
(fs.existsSync('/proc/1/cgroup') &&
fs.readFileSync('/proc/1/cgroup', 'utf-8').includes('docker'));
const launchArgs = [];
if (isContainer || noSandbox) {
launchArgs.push('--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage');
}
// Merge user-provided puppeteer args
if (fmPuppeteer.args) {
launchArgs.push(...fmPuppeteer.args);
}
const launchOptions = {
headless: 'new',
args: launchArgs,
};
if (chromePath) {
launchOptions.executablePath = chromePath;
} else if (process.env.PUPPETEER_EXECUTABLE_PATH) {
launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
}
const browser = await puppeteer.launch(launchOptions);
try {
const page = await browser.newPage();
// 6. Set viewport for screenshots
if (type !== 'pdf') {
const width = fmScreenshot.width || screenshotConfig.width || 1200;
const deviceScaleFactor = fmScreenshot.deviceScaleFactor || screenshotConfig.scale || 2;
await page.setViewport({ width, height: 800, deviceScaleFactor });
}
// 7. Load HTML
await page.setContent(fullHTML, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// 8. Extra wait (for async rendering like Mermaid)
const wait = fmPuppeteer.timeout || waitTimeout;
if (wait > 0) {
await new Promise(r => setTimeout(r, wait));
}
// 9. Export
if (type === 'pdf') {
// Build PDF options
const pdfOpts = {
path: outputPath,
format: fmPuppeteer.format || puppeteerConfig.format || 'A4',
landscape: fmPuppeteer.landscape || puppeteerConfig.landscape || false,
printBackground: fmPuppeteer.printBackground !== undefined
? fmPuppeteer.printBackground
: (puppeteerConfig.printBackground !== undefined ? puppeteerConfig.printBackground : true),
margin: fmPuppeteer.margin || puppeteerConfig.margin || {
top: '10mm', right: '10mm', bottom: '10mm', left: '10mm',
},
scale: fmPuppeteer.scale || puppeteerConfig.scale || 1,
};
// Header/Footer
if (fmPuppeteer.displayHeaderFooter || puppeteerConfig.displayHeaderFooter) {
pdfOpts.displayHeaderFooter = true;
pdfOpts.headerTemplate = fmPuppeteer.headerTemplate || puppeteerConfig.headerTemplate || '<span></span>';
pdfOpts.footerTemplate = fmPuppeteer.footerTemplate || puppeteerConfig.footerTemplate ||
'<div style="font-size:8px;width:100%;text-align:center;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>';
}
await page.pdf(pdfOpts);
} else {
// Screenshot
const screenshotOpts = {
path: outputPath,
type: type,
fullPage: fmScreenshot.fullPage !== undefined ? fmScreenshot.fullPage : true,
};
if (type === 'jpeg') {
screenshotOpts.quality = fmScreenshot.quality || screenshotConfig.quality || 80;
}
await page.screenshot(screenshotOpts);
}
console.log(`✅ Exported: ${outputPath}`);
return { outputPath, type, success: true };
} finally {
await browser.close();
}
}
// ---------------------------------------------------------------------------
// CLI
// ---------------------------------------------------------------------------
function parseArgs(argv) {
const args = argv.slice(2);
const opts = {
inputPath: null,
outputPath: null,
type: 'pdf',
theme: 'github',
chromePath: null,
waitTimeout: 0,
toc: false,
noSandbox: false,
puppeteerConfig: {},
screenshotConfig: {},
};
let i = 0;
while (i < args.length) {
const arg = args[i];
switch (arg) {
case '--type':
opts.type = args[++i];
break;
case '-o': case '--output':
opts.outputPath = args[++i];
break;
case '--format':
opts.puppeteerConfig.format = args[++i];
break;
case '--landscape':
opts.puppeteerConfig.landscape = true;
break;
case '--no-background':
opts.puppeteerConfig.printBackground = false;
break;
case '--margin':
{ const m = args[++i];
opts.puppeteerConfig.margin = { top: m, right: m, bottom: m, left: m };
}
break;
case '--margin-top':
opts.puppeteerConfig.margin = opts.puppeteerConfig.margin || {};
opts.puppeteerConfig.margin.top = args[++i];
break;
case '--margin-bottom':
opts.puppeteerConfig.margin = opts.puppeteerConfig.margin || {};
opts.puppeteerConfig.margin.bottom = args[++i];
break;
case '--margin-left':
opts.puppeteerConfig.margin = opts.puppeteerConfig.margin || {};
opts.puppeteerConfig.margin.left = args[++i];
break;
case '--margin-right':
opts.puppeteerConfig.margin = opts.puppeteerConfig.margin || {};
opts.puppeteerConfig.margin.right = args[++i];
break;
case '--quality':
opts.screenshotConfig.quality = parseInt(args[++i], 10);
break;
case '--scale':
opts.screenshotConfig.scale = parseFloat(args[++i]);
break;
case '--width':
opts.screenshotConfig.width = parseInt(args[++i], 10);
break;
case '--theme':
opts.theme = args[++i];
break;
case '--chrome-path':
opts.chromePath = args[++i];
break;
case '--wait':
opts.waitTimeout = parseInt(args[++i], 10);
break;
case '--toc':
opts.toc = true;
break;
case '--no-sandbox':
opts.noSandbox = true;
break;
case '--header-footer':
opts.puppeteerConfig.displayHeaderFooter = true;
break;
case '--footer-template':
opts.puppeteerConfig.footerTemplate = args[++i];
break;
case '--header-template':
opts.puppeteerConfig.headerTemplate = args[++i];
break;
case '-h': case '--help':
printHelp();
process.exit(0);
break;
default:
if (!arg.startsWith('-') && !opts.inputPath) {
opts.inputPath = arg;
} else {
console.error(`Unknown option: ${arg}`);
process.exit(1);
}
}
i++;
}
return opts;
}
function printHelp() {
console.log(`
md2pdf-export — Convert Markdown to PDF/PNG/JPEG via Puppeteer
Usage:
node md2pdf.js <input.md> [options]
Options:
--type <pdf|png|jpeg> Output type (default: pdf)
-o, --output <path> Output file path (default: input.pdf)
--format <format> Paper format: A3, A4, A5, Letter, Legal, Tabloid
--landscape Landscape orientation
--no-background Disable background graphics in PDF
--margin <size> Uniform margin (e.g. "20mm")
--margin-top <size> Top margin
--margin-bottom <size> Bottom margin
--margin-left <size> Left margin
--margin-right <size> Right margin
--quality <1-100> JPEG quality (default: 80)
--scale <factor> Device scale factor for screenshots (default: 2)
--width <px> Viewport width for screenshots (default: 1200)
--theme <name> CSS theme: github, github-dark, minimal
--chrome-path <path> Path to Chrome/Chromium executable
--wait <ms> Extra wait time after page load
--toc Generate table of contents
--no-sandbox Disable Chrome sandbox (Docker)
--header-footer Show header and footer in PDF
--footer-template <html> Custom footer HTML template
--header-template <html> Custom header HTML template
-h, --help Show this help
`);
}
// ---------------------------------------------------------------------------
// Module exports + CLI entry
// ---------------------------------------------------------------------------
module.exports = { convertMarkdown, renderMarkdown, buildHTML, THEMES };
if (require.main === module) {
const opts = parseArgs(process.argv);
if (!opts.inputPath) {
console.error('Error: No input file specified');
printHelp();
process.exit(1);
}
if (!fs.existsSync(opts.inputPath)) {
console.error(`Error: File not found: ${opts.inputPath}`);
process.exit(1);
}
convertMarkdown(opts)
.then(() => process.exit(0))
.catch(err => {
console.error('Export failed:', err.message);
process.exit(1);
});
}
{
"name": "md2pdf-export",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "md2pdf-export",
"version": "1.0.0",
"dependencies": {
"gray-matter": "^4.0.3",
"katex": "^0.16.0",
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-highlightjs": "^4.1.0",
"markdown-it-texmath": "^1.0.0",
"markdown-it-toc-done-right": "^4.2.0",
"puppeteer": ">=21.0.0"
},
"bin": {
"md2pdf": "md2pdf.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@puppeteer/browsers": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz",
"integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.4.3",
"extract-zip": "^2.0.1",
"progress": "^2.0.3",
"proxy-agent": "^6.5.0",
"semver": "^7.7.4",
"tar-fs": "^3.1.1",
"yargs": "^17.7.2"
},
"bin": {
"browsers": "lib/cjs/main-cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"license": "MIT"
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT",
"peer": true
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT",
"peer": true
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"optional": true,
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/ast-types": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/b4a": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
"integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.6.tgz",
"integrity": "sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz",
"integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==",
"license": "Apache-2.0",
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.10.0.tgz",
"integrity": "sha512-DOPZF/DDcDruKDA43cOw6e9Quq5daua7ygcAwJE/pKJsRWhgSSemi7qVNGE5kyDIxIeN1533G/zfbvWX7Wcb9w==",
"license": "Apache-2.0",
"dependencies": {
"streamx": "^2.25.0",
"teex": "^1.0.1"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
"integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
"license": "Apache-2.0",
"dependencies": {
"bare-path": "^3.0.0"
}
},
"node_modules/basic-ftp": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
"integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/chromium-bidi": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
"integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
"license": "Apache-2.0",
"dependencies": {
"mitt": "^3.0.1",
"zod": "^3.24.1"
},
"peerDependencies": {
"devtools-protocol": "*"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/cosmiconfig": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz",
"integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==",
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
"js-yaml": "^4.1.0",
"parse-json": "^5.2.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/d-fischer"
},
"peerDependencies": {
"typescript": ">=4.9.5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/cosmiconfig/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"license": "MIT",
"dependencies": {
"ast-types": "^0.13.4",
"escodegen": "^2.1.0",
"esprima": "^4.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1581282",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
"license": "BSD-3-Clause"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"license": "BSD-2-Clause",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.0"
}
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"license": "MIT",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-uri": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
"license": "MIT",
"dependencies": {
"basic-ftp": "^5.0.2",
"data-uri-to-buffer": "^6.0.2",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"license": "MIT",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT"
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"license": "MIT"
},
"node_modules/katex": {
"version": "0.16.39",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.39.tgz",
"integrity": "sha512-FR2f6y85+81ZLO0GPhyQ+EJl/E5ILNWltJhpAeOTzRny952Z13x2867lTFDmvMZix//Ux3CuMQ2VkLXRbUwOFg==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"license": "MIT",
"dependencies": {
"commander": "^8.3.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-anchor": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
"integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
"license": "Unlicense",
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
}
},
"node_modules/markdown-it-footnote": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-4.0.0.tgz",
"integrity": "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ==",
"license": "MIT"
},
"node_modules/markdown-it-highlightjs": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-highlightjs/-/markdown-it-highlightjs-4.3.0.tgz",
"integrity": "sha512-dTEXpTV2J9WJiO8TA/FJ4DxidrYtTNcfBAdl+CLjQ3UFWj8s4dJyCPTcvoXNLmN5U5ZTCGu7TSkSvBvi4fUqkQ==",
"license": "Unlicense",
"dependencies": {
"highlight.js": "^11.9.0"
}
},
"node_modules/markdown-it-texmath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz",
"integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==",
"license": "MIT"
},
"node_modules/markdown-it-toc-done-right": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-toc-done-right/-/markdown-it-toc-done-right-4.2.0.tgz",
"integrity": "sha512-UB/IbzjWazwTlNAX0pvWNlJS8NKsOQ4syrXZQ/C72j+jirrsjVRT627lCaylrKJFBQWfRsPmIVQie8x38DEhAQ==",
"license": "MIT"
},
"node_modules/markdown-it/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/netmask": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"license": "MIT",
"dependencies": {
"@tootallnate/quickjs-emscripten": "^0.23.0",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"get-uri": "^6.0.1",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.6",
"pac-resolver": "^7.0.1",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pac-resolver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"license": "MIT",
"dependencies": {
"degenerator": "^5.0.0",
"netmask": "^2.0.2"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/proxy-agent": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"http-proxy-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"lru-cache": "^7.14.1",
"pac-proxy-agent": "^7.1.0",
"proxy-from-env": "^1.1.0",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/puppeteer": {
"version": "24.40.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.40.0.tgz",
"integrity": "sha512-IxQbDq93XHVVLWHrAkFP7F7iHvb9o0mgfsSIMlhHb+JM+JjM1V4v4MNSQfcRWJopx9dsNOr9adYv0U5fm9BJBQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.13.0",
"chromium-bidi": "14.0.0",
"cosmiconfig": "^9.0.0",
"devtools-protocol": "0.0.1581282",
"puppeteer-core": "24.40.0",
"typed-query-selector": "^2.12.1"
},
"bin": {
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/puppeteer-core": {
"version": "24.40.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.40.0.tgz",
"integrity": "sha512-MWL3XbUCfVgGR0gRsidzT6oKJT2QydPLhMITU6HoVWiiv4gkb6gJi3pcdAa8q4HwjBTbqISOWVP4aJiiyUJvag==",
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.13.0",
"chromium-bidi": "14.0.0",
"debug": "^4.4.3",
"devtools-protocol": "0.0.1581282",
"typed-query-selector": "^2.12.1",
"webdriver-bidi-protocol": "0.4.1",
"ws": "^8.19.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"license": "MIT",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.0.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/streamx": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
"integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tar-fs": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
"integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
},
"optionalDependencies": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0"
}
},
"node_modules/tar-stream": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
"integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"bare-fs": "^4.5.5",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"license": "MIT",
"dependencies": {
"streamx": "^2.12.5"
}
},
"node_modules/text-decoder": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typed-query-selector": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
"integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
"license": "MIT"
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT",
"optional": true
},
"node_modules/webdriver-bidi-protocol": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
"integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
"license": "Apache-2.0"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
{
"name": "md2pdf-export",
"version": "1.0.0",
"description": "Markdown to PDF/PNG/JPEG converter using Puppeteer (same methodology as Markdown Preview Enhanced)",
"private": true,
"main": "md2pdf.js",
"bin": {
"md2pdf": "./md2pdf.js"
},
"dependencies": {
"gray-matter": "^4.0.3",
"katex": "^0.16.0",
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-highlightjs": "^4.1.0",
"markdown-it-texmath": "^1.0.0",
"markdown-it-toc-done-right": "^4.2.0",
"puppeteer": ">=21.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
#!/usr/bin/env bash
# ============================================================================
# md2pdf-export: Environment Setup Script
# Installs Node.js, Puppeteer, and Chrome/Chromium dependencies
# Idempotent — safe to run multiple times
# ============================================================================
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
MIN_NODE_VERSION=18
NVM_VERSION="v0.40.1"
# ── Utility functions ────────────────────────────────────────────────────────
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_err() { echo -e "${RED}[ERROR]${NC} $*"; }
command_exists() { command -v "$1" &>/dev/null; }
version_gte() {
# Returns 0 if $1 >= $2 (semver major only)
local cur="$1" min="$2"
[ "$cur" -ge "$min" ] 2>/dev/null
}
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "macos"
elif [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian|pop|linuxmint|elementary) echo "debian" ;;
alpine) echo "alpine" ;;
centos|rhel|fedora|rocky|almalinux) echo "rhel" ;;
arch|manjaro) echo "arch" ;;
*) echo "linux-unknown" ;;
esac
else
echo "unknown"
fi
}
detect_container() {
# Detect if running inside Docker/container
if [ -f /.dockerenv ] || grep -qsE '(docker|containerd|kubepods)' /proc/1/cgroup 2>/dev/null; then
echo "true"
else
echo "false"
fi
}
# ── Step 1: Detect environment ───────────────────────────────────────────────
echo ""
echo "============================================================"
echo " md2pdf-export: Environment Setup"
echo "============================================================"
echo ""
OS=$(detect_os)
IN_CONTAINER=$(detect_container)
log_info "Detected OS: $OS"
log_info "In container: $IN_CONTAINER"
# ── Step 2: Install Node.js ──────────────────────────────────────────────────
install_node_nvm() {
log_info "Installing Node.js via nvm..."
# Install nvm if not present
if [ ! -d "$HOME/.nvm" ]; then
log_info "Installing nvm ${NVM_VERSION}..."
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash
fi
# Source nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
if ! command_exists nvm; then
log_err "nvm installation failed. Try installing Node.js manually."
return 1
fi
nvm install --lts
nvm use --lts
nvm alias default 'lts/*'
log_ok "Node.js $(node -v) installed via nvm"
}
install_node_package_manager() {
case "$OS" in
macos)
if command_exists brew; then
log_info "Installing Node.js via Homebrew..."
brew install node
else
install_node_nvm
fi
;;
debian)
log_info "Installing Node.js via NodeSource..."
if command_exists sudo; then
sudo apt-get update -qq
sudo apt-get install -y -qq curl
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y -qq nodejs
else
apt-get update -qq
apt-get install -y -qq curl
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y -qq nodejs
fi
;;
alpine)
apk add --no-cache nodejs npm
;;
rhel)
if command_exists dnf; then
sudo dnf install -y nodejs npm
else
sudo yum install -y nodejs npm
fi
;;
*)
install_node_nvm
;;
esac
}
check_node() {
if command_exists node; then
local node_major
node_major=$(node -v | sed 's/v//' | cut -d. -f1)
if version_gte "$node_major" "$MIN_NODE_VERSION"; then
log_ok "Node.js $(node -v) found (>= v${MIN_NODE_VERSION} required)"
return 0
else
log_warn "Node.js $(node -v) is too old (need >= v${MIN_NODE_VERSION})"
return 1
fi
else
log_warn "Node.js not found"
return 1
fi
}
if ! check_node; then
install_node_package_manager
# Re-source in case nvm was used
[ -s "$HOME/.nvm/nvm.sh" ] && . "$HOME/.nvm/nvm.sh"
if ! check_node; then
log_err "Failed to install Node.js >= v${MIN_NODE_VERSION}"
exit 1
fi
fi
# Ensure npm is available
if ! command_exists npm; then
log_err "npm not found even though Node.js is installed"
exit 1
fi
log_ok "npm $(npm -v) found"
# ── Step 3: Install Chrome/Chromium system dependencies ─────────────────────
install_chrome_deps() {
log_info "Installing Chromium system dependencies..."
case "$OS" in
debian)
local DEPS=(
ca-certificates fonts-liberation libasound2t64 libatk-bridge2.0-0t64
libatk1.0-0t64 libcups2t64 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0t64
libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1
libxfixes3 libxrandr2 xdg-utils wget fonts-noto-cjk
)
# Try with t64 suffix first (Ubuntu 24+), fallback to plain names
if command_exists sudo; then
sudo apt-get update -qq
sudo apt-get install -y -qq "${DEPS[@]}" 2>/dev/null || {
# Fallback for older Ubuntu/Debian without t64 suffix
local DEPS_FALLBACK=(
ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0
libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0
libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1
libxfixes3 libxrandr2 xdg-utils wget fonts-noto-cjk
)
sudo apt-get install -y -qq "${DEPS_FALLBACK[@]}"
}
else
apt-get update -qq
apt-get install -y -qq "${DEPS[@]}" 2>/dev/null || {
local DEPS_FALLBACK=(
ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0
libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0
libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1
libxfixes3 libxrandr2 xdg-utils wget fonts-noto-cjk
)
apt-get install -y -qq "${DEPS_FALLBACK[@]}"
}
fi
;;
alpine)
apk add --no-cache \
chromium nss freetype harfbuzz ca-certificates ttf-freefont \
font-noto-cjk
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
;;
rhel)
if command_exists dnf; then
sudo dnf install -y \
alsa-lib atk cups-libs dbus-libs libdrm mesa-libgbm gtk3 \
nss libXcomposite libXdamage libXfixes libXrandr xdg-utils \
google-noto-sans-cjk-ttc-fonts
fi
;;
macos)
# macOS: Puppeteer bundles Chromium, no system deps needed
# But install CJK fonts if missing
if command_exists brew; then
brew install --cask font-noto-sans-cjk 2>/dev/null || true
fi
;;
*)
log_warn "Unknown OS — skipping system dependency installation."
log_warn "If Chrome fails to launch, install Chromium deps manually."
;;
esac
log_ok "Chrome system dependencies installed"
}
install_chrome_deps
# ── Step 4: Install Puppeteer ────────────────────────────────────────────────
install_puppeteer() {
log_info "Installing puppeteer globally..."
# Set npm global prefix to user space to avoid sudo
local NPM_GLOBAL="$HOME/.npm-global"
mkdir -p "$NPM_GLOBAL"
npm config set prefix "$NPM_GLOBAL"
# Ensure it's in PATH
if [[ ":$PATH:" != *":$NPM_GLOBAL/bin:"* ]]; then
export PATH="$NPM_GLOBAL/bin:$PATH"
# Persist in shell rc
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
if [ -f "$rc" ]; then
grep -q 'npm-global/bin' "$rc" 2>/dev/null || \
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$rc"
fi
done
fi
# Install puppeteer
npm install -g puppeteer 2>&1 | tail -5
log_ok "puppeteer installed globally"
}
check_puppeteer() {
if node -e "require('puppeteer')" 2>/dev/null; then
local pup_ver
pup_ver=$(node -e "console.log(require('puppeteer/package.json').version)" 2>/dev/null)
log_ok "puppeteer v${pup_ver} found"
return 0
else
return 1
fi
}
if ! check_puppeteer; then
install_puppeteer
if ! check_puppeteer; then
log_err "Failed to install puppeteer"
exit 1
fi
fi
# ── Step 5: Install md2pdf.js local dependencies ────────────────────────────
install_skill_deps() {
log_info "Installing md2pdf.js npm dependencies..."
cd "$SKILL_DIR/scripts"
if [ ! -f package.json ]; then
log_warn "No package.json found in scripts/, creating one..."
cat > package.json <<'PJSON'
{
"name": "md2pdf-export",
"version": "1.0.0",
"private": true,
"dependencies": {
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-highlightjs": "^4.1.0",
"markdown-it-texmath": "^1.0.0",
"markdown-it-toc-done-right": "^4.2.0",
"katex": "^0.16.0",
"gray-matter": "^4.0.3",
"puppeteer": ">=21.0.0"
}
}
PJSON
fi
npm install --prefer-offline 2>&1 | tail -3
log_ok "md2pdf.js dependencies installed"
cd - > /dev/null
}
install_skill_deps
# ── Step 6: Verify everything works ─────────────────────────────────────────
log_info "Running smoke test..."
CHROME_LAUNCH_ARGS=""
if [ "$IN_CONTAINER" = "true" ]; then
CHROME_LAUNCH_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage"
fi
SMOKE_RESULT=$(node -e "
const puppeteer = require('puppeteer');
(async () => {
try {
const args = '${CHROME_LAUNCH_ARGS}'.split(' ').filter(Boolean);
const browser = await puppeteer.launch({
headless: 'new',
args: args
});
const page = await browser.newPage();
await page.setContent('<h1>Smoke Test OK</h1>');
const pdf = await page.pdf({ format: 'A4' });
console.log('PDF_SIZE=' + pdf.length);
await browser.close();
console.log('SMOKE_OK');
} catch (e) {
console.error('SMOKE_FAIL: ' + e.message);
process.exit(1);
}
})();
" 2>&1) || true
if echo "$SMOKE_RESULT" | grep -q "SMOKE_OK"; then
PDF_SIZE=$(echo "$SMOKE_RESULT" | grep "PDF_SIZE" | cut -d= -f2)
log_ok "Smoke test passed (PDF generated: ${PDF_SIZE} bytes)"
else
log_err "Smoke test failed:"
echo "$SMOKE_RESULT"
echo ""
log_warn "Common fixes:"
log_warn " - Docker: ensure --no-sandbox is enabled"
log_warn " - Missing libs: run 'npx puppeteer browsers install chrome'"
log_warn " - Set PUPPETEER_EXECUTABLE_PATH to an existing Chrome binary"
exit 1
fi
# ── Summary ──────────────────────────────────────────────────────────────────
echo ""
echo "============================================================"
echo " ✅ md2pdf-export: Environment Ready"
echo "============================================================"
echo ""
echo " Node.js: $(node -v)"
echo " npm: $(npm -v)"
echo " Puppeteer: $(node -e "console.log(require('puppeteer/package.json').version)" 2>/dev/null || echo 'unknown')"
echo " OS: $OS"
echo " Container: $IN_CONTAINER"
echo ""
echo " Usage:"
echo " node ${SKILL_DIR}/scripts/md2pdf.js input.md"
echo " node ${SKILL_DIR}/scripts/md2pdf.js input.md --type png"
echo " node ${SKILL_DIR}/scripts/md2pdf.js input.md -o output.pdf"
echo ""
echo "============================================================"