← back to Watches
utils/pdf-generator.js
200 lines
/**
* PDF Report Generator using Puppeteer
* Converts HTML analytics reports to professional PDFs
*/
import puppeteer from 'puppeteer';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class PDFGenerator {
constructor() {
this.browser = null;
this.outputDir = path.join(__dirname, '../analytics/reports/pdf');
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
}
/**
* Initialize browser
*/
async init() {
if (!this.browser) {
this.browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu'
]
});
}
return this.browser;
}
/**
* Generate PDF from HTML file
*/
async generateFromHTMLFile(htmlPath, outputName) {
await this.init();
const page = await this.browser.newPage();
try {
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);
await page.pdf({
path: pdfPath,
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm'
}
});
console.log(`✓ PDF generated: ${pdfPath}`);
return pdfPath;
} catch (error) {
console.error(`✗ PDF generation failed:`, error);
throw error;
} finally {
await page.close();
}
}
/**
* Generate PDF from HTML string
*/
async generateFromHTML(htmlContent, outputName) {
await this.init();
const page = await this.browser.newPage();
try {
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);
await page.pdf({
path: pdfPath,
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm'
}
});
console.log(`✓ PDF generated: ${pdfPath}`);
return pdfPath;
} catch (error) {
console.error(`✗ PDF generation failed:`, error);
throw error;
} finally {
await page.close();
}
}
/**
* Generate PDF from URL
*/
async generateFromURL(url, outputName) {
await this.init();
const page = await this.browser.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 });
const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);
await page.pdf({
path: pdfPath,
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm'
}
});
console.log(`✓ PDF generated from URL: ${pdfPath}`);
return pdfPath;
} catch (error) {
console.error(`✗ PDF generation from URL failed:`, error);
throw error;
} finally {
await page.close();
}
}
/**
* Generate analytics report PDF
*/
async generateAnalyticsReport(reportType = 'daily') {
const url = `http://localhost:7600/analytics/reports/${reportType}-report-${new Date().toISOString().split('T')[0]}.html`;
const outputName = `${reportType}-analytics-${new Date().toISOString().split('T')[0]}`;
return await this.generateFromURL(url, outputName);
}
/**
* Close browser
*/
async close() {
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
}
export default PDFGenerator;
// CLI usage
if (import.meta.url === `file://${process.argv[1]}`) {
const generator = new PDFGenerator();
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage: node pdf-generator.js <html-file-path> [output-name]');
console.log(' or: node pdf-generator.js --url <url> [output-name]');
console.log(' or: node pdf-generator.js --analytics [daily|weekly|monthly]');
process.exit(1);
}
(async () => {
try {
if (args[0] === '--url') {
await generator.generateFromURL(args[1], args[2] || 'output');
} else if (args[0] === '--analytics') {
await generator.generateAnalyticsReport(args[1] || 'daily');
} else {
await generator.generateFromHTMLFile(args[0], args[1] || 'output');
}
} catch (error) {
console.error('Error:', error);
process.exit(1);
} finally {
await generator.close();
}
})();
}