commit 9a76e547646d4f0a0d5298daa573a28d209573f2 Author: Ubuntu Date: Wed Sep 3 23:52:13 2025 +0530 v1.0.0-rc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2807d53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,119 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage +.grunt + +# Bower dependency directory +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production + +# parcel-bundler cache +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Generated PDFs +*.pdf + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Docker +.dockerignore +Dockerfile +docker-compose.yml diff --git a/download-popup.html b/download-popup.html new file mode 100644 index 0000000..6e22644 --- /dev/null +++ b/download-popup.html @@ -0,0 +1,578 @@ + + + + + + PDF Download Popup Interface + + + +
+
+

๐Ÿš€ PDF Generator

+

Generate PDFs with download links - No size restrictions!

+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+

๐Ÿ”„ Generating PDF... Please wait

+
+ + + +
+ + + + + + + diff --git a/fixed-pdf-generation.js b/fixed-pdf-generation.js new file mode 100644 index 0000000..5cb6188 --- /dev/null +++ b/fixed-pdf-generation.js @@ -0,0 +1,478 @@ +#!/usr/bin/env node +/** + * Fixed PDF Generation with Proper Page Breaks and A4 Format + * + * Fixes: + * 1. No unexpected blank pages + * 2. Correct page size (A4) + * 3. Proper handling of page breaks using CSS + * 4. Page numbers in footer should match actual HTML pages + */ + +const express = require('express'); +const puppeteer = require('puppeteer'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const app = express(); +const PORT = process.env.PORT || 8001; // Different port to avoid conflicts + +// Configure temp folder +const TEMP_FOLDER = 'temp'; +if (!fs.existsSync(TEMP_FOLDER)) { + fs.mkdirSync(TEMP_FOLDER, { recursive: true }); +} + +// Middleware +app.use(cors()); +app.use(bodyParser.json({ limit: '100mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); + +// Serve static files from temp folder +app.use('/download', express.static(TEMP_FOLDER)); + +/** + * Generate A4 PDF with proper page breaks and no extra pages + */ +async function generateFixedA4PDF(htmlContent, outputPDF) { + let browser = null; + + try { + console.log("๐Ÿš€ Starting FIXED PDF generation..."); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu' + ] + }); + + const page = await browser.newPage(); + + // Set viewport to A4 dimensions (210mm x 297mm = 794px x 1123px at 96 DPI) + await page.setViewport({ width: 794, height: 1123 }); + page.setDefaultTimeout(0); // Infinite timeout + + // Create improved HTML with proper page break CSS + const fixedHTML = ` + + + + + + + + +
+
+ ${htmlContent.replace(/]*>|<\/body>/gi, '')} +
+ +
+ + + + + `; + + // Set content with fixed HTML + await page.setContent(fixedHTML, { + waitUntil: 'networkidle0', + timeout: 0 // Infinite timeout + }); + + // Wait for content to load completely + await page.waitForTimeout(3000); // Wait 3 seconds for content to settle + + // Wait for all resources to load + try { + await page.waitForNetworkIdle({ timeout: 0 }); + } catch (error) { + // Ignore timeout errors for networkidle + } + + // Inject additional CSS to fix any remaining page break issues + await page.addStyleTag({ + content: ` + /* Additional fixes for page breaks */ + * { + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + } + + /* Remove any elements that might cause extra pages */ + .hidden, .invisible, [style*="display: none"] { + display: none !important; + } + + /* Ensure proper page flow */ + body { + overflow: visible !important; + } + + /* Fix for any floating elements */ + .float-clear { + clear: both; + } + ` + }); + + console.log("๐Ÿ“„ Generating FIXED A4 PDF..."); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.5in', + right: '0.5in', + bottom: '0.8in', // Extra space for footer + left: '0.5in' + }, + scale: 1.0, + format: 'A4', + preferCSSPageSize: true, // Use CSS page size + displayHeaderFooter: true, // Enable header/footer for page numbers + headerTemplate: '
', // Empty header + footerTemplate: ` +
+ Page of +
+ `, + omitBackground: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… FIXED A4 PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: A4 (210mm x 297mm)`); + console.log(`๐Ÿ“ Margins: 0.5in all sides, 0.8in bottom for footer`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + console.log(`๐Ÿ“„ Page numbers: Enabled in footer`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} + +/** + * Main PDF generation endpoint with fixes + */ +app.post('/generate-fixed-pdf', async (req, res) => { + try { + // Get request data + let inputContent = null; + let outputName = null; + let returnDownloadLink = false; + + if (req.is('application/json')) { + try { + const data = req.body; + if (data) { + if (data.input) { + inputContent = data.input; + outputName = data.output || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.html) { + inputContent = data.html; + outputName = data.filename || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.content) { + inputContent = data.content; + outputName = data.name || null; + returnDownloadLink = data.return_download_link || false; + } + } + } catch (parseError) { + console.error('JSON parse error:', parseError); + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } else if (req.is('application/x-www-form-urlencoded')) { + inputContent = req.body.input || req.body.html || req.body.content; + outputName = req.body.output || req.body.filename || req.body.name; + returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; + } else { + try { + const rawData = JSON.parse(req.body); + inputContent = rawData.input || rawData.html || rawData.content; + outputName = rawData.output || rawData.filename || rawData.name; + returnDownloadLink = rawData.return_download_link || false; + } catch (error) { + return res.status(400).json({ error: 'Invalid request format' }); + } + } + + // Validate input + if (!inputContent) { + return res.status(400).json({ error: 'No HTML content provided' }); + } + + console.log(`๐Ÿ“ฅ Received FIXED PDF request - Download Link: ${returnDownloadLink}`); + + // Generate unique filename + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const randomId = crypto.randomBytes(4).toString('hex'); + const filename = outputName ? + `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_fixed_${randomId}.pdf` : + `property_brochure_fixed_${randomId}.pdf`; + + const outputPDF = path.join(TEMP_FOLDER, filename); + + // Generate PDF with fixes + await generateFixedA4PDF(inputContent, outputPDF); + + if (returnDownloadLink) { + // Return download link + const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + const result = { + success: true, + message: "FIXED PDF generated successfully - No extra pages, proper A4 size, correct page breaks, and accurate page numbers", + download_url: downloadUrl, + filename: filename, + file_size_mb: pdfSizeMB.toFixed(2), + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), + generated_at: new Date().toISOString(), + pdf_id: randomId, + status: "download_ready", + fixes_applied: [ + "No unexpected blank pages", + "Correct A4 page size", + "Proper CSS page breaks", + "Accurate page numbers in footer" + ] + }; + + console.log(`๐Ÿ”— FIXED PDF ready for download: ${downloadUrl}`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + res.json(result); + } else { + // Return base64 content + const pdfBuffer = fs.readFileSync(outputPDF); + const base64Content = pdfBuffer.toString('base64'); + + // Clean up the file + if (fs.existsSync(outputPDF)) { + fs.unlinkSync(outputPDF); + } + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(Buffer.from(base64Content, 'base64')); + } + + } catch (error) { + console.error(`โŒ FIXED PDF API Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Health check endpoint + */ +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '1.0.0-fixed', + features: [ + 'no_extra_blank_pages', + 'correct_a4_size', + 'proper_page_breaks', + 'accurate_page_numbers', + 'download_links', + 'base64_response' + ] + }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`๐Ÿš€ FIXED PDF Generator API running on port ${PORT}`); + console.log(`๐Ÿ“ Temp folder: ${TEMP_FOLDER}`); + console.log(`๐Ÿ”— Download endpoint: http://localhost:${PORT}/download/:filename`); + console.log(`๐Ÿ“Š Health check: http://localhost:${PORT}/health`); + console.log(`โœจ FIXES: No extra pages, A4 size, proper breaks, accurate page numbers`); +}); + +module.exports = app; diff --git a/improved-pdf-generation.js b/improved-pdf-generation.js new file mode 100644 index 0000000..2f7c157 --- /dev/null +++ b/improved-pdf-generation.js @@ -0,0 +1,144 @@ +async function generateA4PDF(htmlContent, outputPDF) { + let browser = null; + + try { + console.log("๐Ÿš€ Starting PDF generation..."); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu' + ] + }); + + const page = await browser.newPage(); + + // Set viewport to A4 dimensions (210mm x 297mm = 794px x 1123px at 96 DPI) + await page.setViewport({ width: 794, height: 1123 }); + page.setDefaultTimeout(0); // Infinite timeout + + // Inject CSS to fix white spaces and page breaks + const improvedHTML = ` + + + + + + + + ${htmlContent.replace(/]*>|<\/body>/gi, '')} + + + `; + + // Set content with improved HTML + await page.setContent(improvedHTML, { + waitUntil: 'networkidle0', + timeout: 0 // Infinite timeout + }); + + // Wait for content to load completely + await page.waitForTimeout(2000); // Wait 2 seconds for content to settle + + // Wait for all resources to load + try { + await page.waitForNetworkIdle({ timeout: 0 }); + } catch (error) { + // Ignore timeout errors for networkidle + } + + console.log("๐Ÿ“„ Generating A4 PDF..."); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.25in', + right: '0.25in', + bottom: '0.25in', + left: '0.25in' + }, + scale: 1.0, + format: 'A4', + preferCSSPageSize: false, + displayHeaderFooter: false, + omitBackground: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging (no size restriction for download links) + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… A4 PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: A4 (210mm x 297mm)`); + console.log(`๐Ÿ“ Margins: 0.25in all sides`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..22cc497 --- /dev/null +++ b/index.js @@ -0,0 +1,496 @@ +#!/usr/bin/env node +/** + * Property Brochure PDF Generator API - A4 Scale + * Uses consistent A4 format with proper scaling + * + * NEW FLOW: Supports both download links and direct file responses + * - return_download_link: true -> Generate PDF, save on server, return download link + * - return_download_link: false -> Generate PDF, return base64 content (old behavior) + */ + +const express = require('express'); +const puppeteer = require('puppeteer'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const app = express(); +const PORT = process.env.PORT || 8000; + +// Configure temp folder +const TEMP_FOLDER = 'temp'; +if (!fs.existsSync(TEMP_FOLDER)) { + fs.mkdirSync(TEMP_FOLDER, { recursive: true }); +} + +// Middleware +app.use(cors()); +app.use(bodyParser.json({ limit: '100mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); + +// Serve static files from temp folder +app.use('/download', express.static(TEMP_FOLDER)); + +/** + * Generate PDF with format options (A3 or A4) and minimal whitespace + */ +async function generatePDF(htmlContent, outputPDF, format = 'A4') { + let browser = null; + + try { + console.log("๐Ÿš€ Starting PDF generation..."); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu' + ] + }); + + const page = await browser.newPage(); + + // Set viewport based on format + const viewportDimensions = format === 'A3' + ? { width: 1123, height: 1587 } // A3: 297mm x 420mm + : { width: 794, height: 1123 }; // A4: 210mm x 297mm + await page.setViewport(viewportDimensions); + page.setDefaultTimeout(0); // Infinite timeout + + // Clean HTML content to remove excessive whitespace while preserving layout + const cleanedHTML = htmlContent + .replace(/\s+/g, ' ') + .replace(/>\s+<') + .trim() + .replace(/\n\s*\n/g, '\n') + .replace(/^\s+|\s+$/gm, '') + .replace(/\n{3,}/g, '\n\n') + .replace(/\s+<\//g, '\s+/g, '>') + .replace(/<(\w+)[^>]*>\s*<\/\1>/g, '') + .replace(//g, ''); + + // Inject minimal CSS to reduce whitespace while preserving layout + const improvedHTML = ` + + + + + + + + ${cleanedHTML.replace(/]*>|<\/body>/gi, '')} + + + `; + + // Set content with improved HTML + await page.setContent(improvedHTML, { + waitUntil: 'networkidle0', + timeout: 0 // Infinite timeout + }); + + // Wait for content to load completely + await page.waitForTimeout(0); + + // Wait for all resources to load + try { + await page.waitForNetworkIdle({ timeout: 0 }); + } catch (error) { + // Ignore timeout errors for networkidle + } + + // Additional CSS injection to reduce spacing + await page.addStyleTag({ + content: ` + /* Final spacing reduction */ + * { + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + } + + /* Reduce remaining spacing */ + body, html { + overflow: visible !important; + } + + /* Ensure proper page breaks */ + .page-break { + page-break-before: always !important; + } + ` + }); + + console.log(`๐Ÿ“„ Generating ${format} PDF with minimal whitespace...`); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.2in', // Minimal margins + right: '0.2in', + bottom: '0.2in', + left: '0.2in' + }, + scale: 1.0, + format: format, // Dynamic format (A3 or A4) + preferCSSPageSize: false, // Use our CSS settings + displayHeaderFooter: false, // No headers/footers to avoid spacing + omitBackground: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging (no size restriction for download links) + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… MINIMAL-WHITESPACE ${format} PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: ${format} (${format === 'A3' ? '297mm x 420mm' : '210mm x 297mm'})`); + console.log(`๐Ÿ“ Margins: 0.2in (minimal margins)`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + console.log(`๐Ÿšซ White spaces: REDUCED while preserving layout`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} + +/** + * Main PDF generation endpoint - supports both download link and base64 response + */ +app.post('/generate-pdf', async (req, res) => { + try { + // Get request data + let inputContent = null; + let outputName = null; + let returnDownloadLink = false; // Default to old behavior + let format = 'A4'; // Default format + + if (req.is('application/json')) { + try { + const data = req.body; + if (data) { + // Handle different request formats + if (data.input) { + // Direct HTML input + inputContent = data.input; + outputName = data.output || null; + returnDownloadLink = data.return_download_link || false; + format = data.format || 'A4'; + } else if (data.html) { + // Alternative HTML field + inputContent = data.html; + outputName = data.filename || null; + returnDownloadLink = data.return_download_link || false; + format = data.format || 'A4'; + } else if (data.content) { + // Content field + inputContent = data.content; + outputName = data.name || null; + returnDownloadLink = data.return_download_link || false; + format = data.format || 'A4'; + } + } + } catch (parseError) { + console.error('JSON parse error:', parseError); + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } else if (req.is('application/x-www-form-urlencoded')) { + // Handle form data + inputContent = req.body.input || req.body.html || req.body.content; + outputName = req.body.output || req.body.filename || req.body.name; + returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; + format = req.body.format || 'A4'; + } else { + // Try to get from raw body + try { + const rawData = JSON.parse(req.body); + inputContent = rawData.input || rawData.html || rawData.content; + outputName = rawData.output || rawData.filename || rawData.name; + returnDownloadLink = rawData.return_download_link || false; + format = rawData.format || 'A4'; + } catch (error) { + return res.status(400).json({ error: 'Invalid request format' }); + } + } + + // Validate input + if (!inputContent) { + return res.status(400).json({ error: 'No HTML content provided' }); + } + + // Validate format + if (!['A3', 'A4'].includes(format.toUpperCase())) { + return res.status(400).json({ error: 'Invalid format. Supported formats: A3, A4' }); + } + format = format.toUpperCase(); + + console.log(`๐Ÿ“ฅ Received request - Format: ${format}, Download Link: ${returnDownloadLink}`); + + // Generate unique filename + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const randomId = crypto.randomBytes(4).toString('hex'); + const filename = outputName ? + `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_${format}_${randomId}.pdf` : + `property_brochure_${format}_${randomId}.pdf`; + + const outputPDF = path.join(TEMP_FOLDER, filename); + + // Generate PDF + await generatePDF(inputContent, outputPDF, format); + + if (returnDownloadLink) { + // NEW FLOW: Return download link + const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + const result = { + success: true, + message: `MINIMAL-WHITESPACE ${format} PDF generated successfully`, + download_url: downloadUrl, + filename: filename, + format: format, + file_size_mb: pdfSizeMB.toFixed(2), + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days + generated_at: new Date().toISOString(), + pdf_id: randomId, + status: "download_ready", + features: ["reduced_whitespace", "preserved_layout", "minimal_margins"] + }; + + console.log(`๐Ÿ”— PDF ready for download: ${downloadUrl}`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + res.json(result); + } else { + // OLD FLOW: Return base64 content + const pdfBuffer = fs.readFileSync(outputPDF); + const base64Content = pdfBuffer.toString('base64'); + + // Clean up the file + if (fs.existsSync(outputPDF)) { + fs.unlinkSync(outputPDF); + } + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(Buffer.from(base64Content, 'base64')); + } + + } catch (error) { + console.error(`โŒ API Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Download endpoint for PDF files + */ +app.get('/download/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(TEMP_FOLDER, filename); + + // Security check - ensure filename is safe + if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) { + return res.status(400).json({ error: 'Invalid filename' }); + } + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: 'PDF file not found or expired' }); + } + + // Send file response + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`); + + const fileStream = fs.createReadStream(filePath); + fileStream.pipe(res); + + // Keep the generated PDF file (don't delete it) + fileStream.on('end', () => { + console.log(`๐Ÿ“ฅ PDF downloaded: ${filePath}`); + }); + + } catch (error) { + console.error(`โŒ Download Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Health check endpoint + */ +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '9.1.0-minimal-whitespace-a3-a4', + supported_formats: ['A3', 'A4'], + features: [ + 'reduced_white_spaces', + 'minimal_margins', + 'html_preprocessing', + 'preserved_layout', + 'no_extra_pages', + 'a3_a4_formats', + 'clean_pdf_output', + 'download_links', + 'base64_response', + 'infinite_timeouts', + 'format_validation' + ] + }); +}); + +/** + * Clean up temporary HTML files older than 1 hour (keep PDF files) + */ +function cleanupTempFiles() { + try { + const currentTime = Date.now(); + const files = fs.readdirSync(TEMP_FOLDER); + + files.forEach(filename => { + const filepath = path.join(TEMP_FOLDER, filename); + const stats = fs.statSync(filepath); + + // Only clean up HTML files, keep PDF files + if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour + try { + fs.unlinkSync(filepath); + console.log(`๐Ÿ—‘๏ธ Cleaned up old HTML file: ${filename}`); + } catch (error) { + console.error(`Error deleting file ${filename}:`, error); + } + } + }); + } catch (error) { + console.error('Error during cleanup:', error); + } +} + +// Clean up old files every hour +setInterval(cleanupTempFiles, 3600000); + +// Start server +app.listen(PORT, () => { + console.log(`๐Ÿš€ MINIMAL-WHITESPACE PDF Generator API running on port ${PORT}`); + console.log(`๐Ÿ“ Temp folder: ${TEMP_FOLDER}`); + console.log(`๐Ÿ”— Download endpoint: http://localhost:${PORT}/download/:filename`); + console.log(`๐Ÿ“Š Health check: http://localhost:${PORT}/health`); + console.log(`๐Ÿšซ WHITESPACE: REDUCED - Minimal spacing, preserved layout, clean output`); + console.log(`๐Ÿ“ FORMATS: A3 (297mm x 420mm) and A4 (210mm x 297mm)`); + console.log(`โœจ Features: Reduced white spaces, HTML preprocessing, Preserved layout, Format options`); +}); + +module.exports = app; diff --git a/index.js.backup b/index.js.backup new file mode 100644 index 0000000..fc369a4 --- /dev/null +++ b/index.js.backup @@ -0,0 +1,330 @@ +#!/usr/bin/env node +/** + * Property Brochure PDF Generator API - A4 Scale + * Uses consistent A4 format with proper scaling + * + * NEW FLOW: Supports both download links and direct file responses + * - return_download_link: true -> Generate PDF, save on server, return download link + * - return_download_link: false -> Generate PDF, return base64 content (old behavior) + */ + +const express = require('express'); +const puppeteer = require('puppeteer'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const app = express(); +const PORT = process.env.PORT || 8000; + +// Configure temp folder +const TEMP_FOLDER = 'temp'; +if (!fs.existsSync(TEMP_FOLDER)) { + fs.mkdirSync(TEMP_FOLDER, { recursive: true }); +} + +// Middleware +app.use(cors()); +app.use(bodyParser.json({ limit: '100mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); + +// Serve static files from temp folder +app.use('/download', express.static(TEMP_FOLDER)); + +/** + * Generate A4 PDF with proper scaling + */ +async function generateA4PDF(htmlContent, outputPDF) { + let browser = null; + + try { + console.log("๐Ÿš€ Starting PDF generation..."); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu' + ] + }); + + const page = await browser.newPage(); + + // Set viewport to A4 dimensions (210mm x 297mm = 794px x 1123px at 96 DPI) + await page.setViewport({ width: 794, height: 1123 }); + page.setDefaultTimeout(0); // Infinite timeout + + // Set content + await page.setContent(htmlContent, { + waitUntil: 'networkidle0', + timeout: 0 // Infinite timeout + }); + + // Wait for content to load completely + await page.waitForTimeout(0); + + // Wait for all resources to load + try { + await page.waitForNetworkIdle({ timeout: 0 }); + } catch (error) { + // Ignore timeout errors for networkidle + } + + console.log("๐Ÿ“„ Generating A4 PDF..."); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.5in', + right: '0.5in', + bottom: '0.5in', + left: '0.5in' + }, + scale: 1.0, + format: 'A4', // Always A4 + preferCSSPageSize: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging (no size restriction for download links) + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… A4 PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: A4 (210mm x 297mm)`); + console.log(`๐Ÿ“ Margins: 0.5in all sides`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} + +/** + * Main PDF generation endpoint - supports both download link and base64 response + */ +app.post('/generate-pdf', async (req, res) => { + try { + // Get request data + let inputContent = null; + let outputName = null; + let returnDownloadLink = false; // Default to old behavior + + if (req.is('application/json')) { + try { + const data = req.body; + if (data) { + // Handle different request formats + if (data.input) { + // Direct HTML input + inputContent = data.input; + outputName = data.output || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.html) { + // Alternative HTML field + inputContent = data.html; + outputName = data.filename || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.content) { + // Content field + inputContent = data.content; + outputName = data.name || null; + returnDownloadLink = data.return_download_link || false; + } + } + } catch (parseError) { + console.error('JSON parse error:', parseError); + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } else if (req.is('application/x-www-form-urlencoded')) { + // Handle form data + inputContent = req.body.input || req.body.html || req.body.content; + outputName = req.body.output || req.body.filename || req.body.name; + returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; + } else { + // Try to get from raw body + try { + const rawData = JSON.parse(req.body); + inputContent = rawData.input || rawData.html || rawData.content; + outputName = rawData.output || rawData.filename || rawData.name; + returnDownloadLink = rawData.return_download_link || false; + } catch (error) { + return res.status(400).json({ error: 'Invalid request format' }); + } + } + + // Validate input + if (!inputContent) { + return res.status(400).json({ error: 'No HTML content provided' }); + } + + console.log(`๐Ÿ“ฅ Received request - Download Link: ${returnDownloadLink}`); + + // Generate unique filename + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const randomId = crypto.randomBytes(4).toString('hex'); + const filename = outputName ? + `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_${randomId}.pdf` : + `property_brochure_${randomId}.pdf`; + + const outputPDF = path.join(TEMP_FOLDER, filename); + + // Generate PDF + await generateA4PDF(inputContent, outputPDF); + + if (returnDownloadLink) { + // NEW FLOW: Return download link + const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + const result = { + success: true, + message: "PDF generated successfully", + download_url: downloadUrl, + filename: filename, + file_size_mb: pdfSizeMB.toFixed(2), + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days + generated_at: new Date().toISOString(), + pdf_id: randomId, + status: "download_ready" + }; + + console.log(`๐Ÿ”— PDF ready for download: ${downloadUrl}`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + res.json(result); + } else { + // OLD FLOW: Return base64 content + const pdfBuffer = fs.readFileSync(outputPDF); + const base64Content = pdfBuffer.toString('base64'); + + // Clean up the file + if (fs.existsSync(outputPDF)) { + fs.unlinkSync(outputPDF); + } + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(Buffer.from(base64Content, 'base64')); + } + + } catch (error) { + console.error(`โŒ API Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Download endpoint for PDF files + */ +app.get('/download/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(TEMP_FOLDER, filename); + + // Security check - ensure filename is safe + if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) { + return res.status(400).json({ error: 'Invalid filename' }); + } + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: 'PDF file not found or expired' }); + } + + // Send file response + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`); + + const fileStream = fs.createReadStream(filePath); + fileStream.pipe(res); + + // Keep the generated PDF file (don't delete it) + fileStream.on('end', () => { + console.log(`๐Ÿ“ฅ PDF downloaded: ${filePath}`); + }); + + } catch (error) { + console.error(`โŒ Download Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Health check endpoint + */ +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '7.0.0', + features: ['download_links', 'base64_response', 'a4_format', 'infinite_timeouts'] + }); +}); + +/** + * Clean up temporary HTML files older than 1 hour (keep PDF files) + */ +function cleanupTempFiles() { + try { + const currentTime = Date.now(); + const files = fs.readdirSync(TEMP_FOLDER); + + files.forEach(filename => { + const filepath = path.join(TEMP_FOLDER, filename); + const stats = fs.statSync(filepath); + + // Only clean up HTML files, keep PDF files + if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour + try { + fs.unlinkSync(filepath); + console.log(`๐Ÿ—‘๏ธ Cleaned up old HTML file: ${filename}`); + } catch (error) { + console.error(`Error deleting file ${filename}:`, error); + } + } + }); + } catch (error) { + console.error('Error during cleanup:', error); + } +} + +// Clean up old files every hour +setInterval(cleanupTempFiles, 3600000); + +// Start server +app.listen(PORT, () => { + console.log(`๐Ÿš€ PDF Generator API running on port ${PORT}`); + console.log(`๐Ÿ“ Temp folder: ${TEMP_FOLDER}`); + console.log(`๐Ÿ”— Download endpoint: http://localhost:${PORT}/download/:filename`); + console.log(`๐Ÿ“Š Health check: http://localhost:${PORT}/health`); + console.log(`โœจ Features: Download links, Base64 response, A4 format, Infinite timeouts`); +}); + +module.exports = app; diff --git a/index.js.backup2 b/index.js.backup2 new file mode 100644 index 0000000..8841e25 --- /dev/null +++ b/index.js.backup2 @@ -0,0 +1,332 @@ +#!/usr/bin/env node +/** + * Property Brochure PDF Generator API - A4 Scale + * Uses consistent A4 format with proper scaling + * + * NEW FLOW: Supports both download links and direct file responses + * - return_download_link: true -> Generate PDF, save on server, return download link + * - return_download_link: false -> Generate PDF, return base64 content (old behavior) + */ + +const express = require('express'); +const puppeteer = require('puppeteer'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const app = express(); +const PORT = process.env.PORT || 8000; + +// Configure temp folder +const TEMP_FOLDER = 'temp'; +if (!fs.existsSync(TEMP_FOLDER)) { + fs.mkdirSync(TEMP_FOLDER, { recursive: true }); +} + +// Middleware +app.use(cors()); +app.use(bodyParser.json({ limit: '100mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); + +// Serve static files from temp folder +app.use('/download', express.static(TEMP_FOLDER)); + +/** + * Generate A4 PDF with proper scaling + */ +async function generateA4PDF(htmlContent, outputPDF) { + let browser = null; + + try { + console.log("๐Ÿš€ Starting PDF generation..."); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu' + ] + }); + + const page = await browser.newPage(); + + // Set viewport to A4 dimensions (210mm x 297mm = 794px x 1123px at 96 DPI) + await page.setViewport({ width: 794, height: 1123 }); + page.setDefaultTimeout(0); // Infinite timeout + + // Set content + await page.setContent(htmlContent, { + waitUntil: 'networkidle0', + timeout: 0 // Infinite timeout + }); + + // Wait for content to load completely + await page.waitForTimeout(0); + + // Wait for all resources to load + try { + await page.waitForNetworkIdle({ timeout: 0 }); + } catch (error) { + // Ignore timeout errors for networkidle + } + + console.log("๐Ÿ“„ Generating A4 PDF..."); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.5in', + right: '0.5in', + bottom: '0.5in', + left: '0.5in' + }, + scale: 1.0, + format: 'A4', // Always A4 + preferCSSPageSize: false, + displayHeaderFooter: false, + omitBackground: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging (no size restriction for download links) + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… A4 PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: A4 (210mm x 297mm)`); + console.log(`๐Ÿ“ Margins: 0.5in all sides`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} + +/** + * Main PDF generation endpoint - supports both download link and base64 response + */ +app.post('/generate-pdf', async (req, res) => { + try { + // Get request data + let inputContent = null; + let outputName = null; + let returnDownloadLink = false; // Default to old behavior + + if (req.is('application/json')) { + try { + const data = req.body; + if (data) { + // Handle different request formats + if (data.input) { + // Direct HTML input + inputContent = data.input; + outputName = data.output || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.html) { + // Alternative HTML field + inputContent = data.html; + outputName = data.filename || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.content) { + // Content field + inputContent = data.content; + outputName = data.name || null; + returnDownloadLink = data.return_download_link || false; + } + } + } catch (parseError) { + console.error('JSON parse error:', parseError); + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } else if (req.is('application/x-www-form-urlencoded')) { + // Handle form data + inputContent = req.body.input || req.body.html || req.body.content; + outputName = req.body.output || req.body.filename || req.body.name; + returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; + } else { + // Try to get from raw body + try { + const rawData = JSON.parse(req.body); + inputContent = rawData.input || rawData.html || rawData.content; + outputName = rawData.output || rawData.filename || rawData.name; + returnDownloadLink = rawData.return_download_link || false; + } catch (error) { + return res.status(400).json({ error: 'Invalid request format' }); + } + } + + // Validate input + if (!inputContent) { + return res.status(400).json({ error: 'No HTML content provided' }); + } + + console.log(`๐Ÿ“ฅ Received request - Download Link: ${returnDownloadLink}`); + + // Generate unique filename + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const randomId = crypto.randomBytes(4).toString('hex'); + const filename = outputName ? + `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_${randomId}.pdf` : + `property_brochure_${randomId}.pdf`; + + const outputPDF = path.join(TEMP_FOLDER, filename); + + // Generate PDF + await generateA4PDF(inputContent, outputPDF); + + if (returnDownloadLink) { + // NEW FLOW: Return download link + const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + const result = { + success: true, + message: "PDF generated successfully", + download_url: downloadUrl, + filename: filename, + file_size_mb: pdfSizeMB.toFixed(2), + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // 7 days + generated_at: new Date().toISOString(), + pdf_id: randomId, + status: "download_ready" + }; + + console.log(`๐Ÿ”— PDF ready for download: ${downloadUrl}`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + res.json(result); + } else { + // OLD FLOW: Return base64 content + const pdfBuffer = fs.readFileSync(outputPDF); + const base64Content = pdfBuffer.toString('base64'); + + // Clean up the file + if (fs.existsSync(outputPDF)) { + fs.unlinkSync(outputPDF); + } + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(Buffer.from(base64Content, 'base64')); + } + + } catch (error) { + console.error(`โŒ API Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Download endpoint for PDF files + */ +app.get('/download/:filename', (req, res) => { + try { + const filename = req.params.filename; + const filePath = path.join(TEMP_FOLDER, filename); + + // Security check - ensure filename is safe + if (!filename.match(/^[a-zA-Z0-9._-]+\.pdf$/)) { + return res.status(400).json({ error: 'Invalid filename' }); + } + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ error: 'PDF file not found or expired' }); + } + + // Send file response + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${path.basename(filePath)}"`); + + const fileStream = fs.createReadStream(filePath); + fileStream.pipe(res); + + // Keep the generated PDF file (don't delete it) + fileStream.on('end', () => { + console.log(`๐Ÿ“ฅ PDF downloaded: ${filePath}`); + }); + + } catch (error) { + console.error(`โŒ Download Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Health check endpoint + */ +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '7.0.0', + features: ['download_links', 'base64_response', 'a4_format', 'infinite_timeouts'] + }); +}); + +/** + * Clean up temporary HTML files older than 1 hour (keep PDF files) + */ +function cleanupTempFiles() { + try { + const currentTime = Date.now(); + const files = fs.readdirSync(TEMP_FOLDER); + + files.forEach(filename => { + const filepath = path.join(TEMP_FOLDER, filename); + const stats = fs.statSync(filepath); + + // Only clean up HTML files, keep PDF files + if (stats.isFile() && filename.endsWith('.html') && (currentTime - stats.mtime.getTime()) > 3600000) { // 1 hour + try { + fs.unlinkSync(filepath); + console.log(`๐Ÿ—‘๏ธ Cleaned up old HTML file: ${filename}`); + } catch (error) { + console.error(`Error deleting file ${filename}:`, error); + } + } + }); + } catch (error) { + console.error('Error during cleanup:', error); + } +} + +// Clean up old files every hour +setInterval(cleanupTempFiles, 3600000); + +// Start server +app.listen(PORT, () => { + console.log(`๐Ÿš€ PDF Generator API running on port ${PORT}`); + console.log(`๐Ÿ“ Temp folder: ${TEMP_FOLDER}`); + console.log(`๐Ÿ”— Download endpoint: http://localhost:${PORT}/download/:filename`); + console.log(`๐Ÿ“Š Health check: http://localhost:${PORT}/health`); + console.log(`โœจ Features: Download links, Base64 response, A4 format, Infinite timeouts`); +}); + +module.exports = app; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..eca864f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2642 @@ +{ + "name": "property-brochure-pdf-generator", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "property-brochure-pdf-generator", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^1.11.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "puppeteer": "^21.5.2" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", + "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "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/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.10.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/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chromium-bidi": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.8.tgz", + "integrity": "sha512-blqh+1cEQbHBKmok3rVJkBlBxt9beKBgOsxbFgs7UJcoVbbeZ+K7+6liAsjgpc8l1Xd55cQUy14fXZdGSb4zIw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0" + }, + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "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/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "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": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1232444", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1232444.tgz", + "integrity": "sha512-pM27vqEfxSxRkTMnF+XCmxSEb6duO5R+t8A9DEEJgy4Wz2RVanje2mmj99B6A3zv2r/qGfYlOvYznUhuokizmg==", + "license": "BSD-3-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "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/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/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.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/extract-zip/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/extract-zip/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/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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/get-uri/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/get-uri/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/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/http-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/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/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/https-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "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/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "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": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "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/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/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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/pac-proxy-agent/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/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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "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/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/proxy-agent/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/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/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "21.11.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-21.11.0.tgz", + "integrity": "sha512-9jTHuYe22TD3sNxy0nEIzC7ZrlRnDgeX3xPkbS7PnbdwYjl2o/z/YuCrRBwezdKpbTDTJ4VqIggzNyeRcKq3cg==", + "deprecated": "< 24.9.0 is no longer supported", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "1.9.1", + "cosmiconfig": "9.0.0", + "puppeteer-core": "21.11.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=16.13.2" + } + }, + "node_modules/puppeteer-core": { + "version": "21.11.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-21.11.0.tgz", + "integrity": "sha512-ArbnyA3U5SGHokEvkfWjW+O8hOxV1RSJxOgriX/3A4xZRqixt9ZFHD0yPgZQF05Qj0oAqi8H/7stDorjoHY90Q==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "1.9.1", + "chromium-bidi": "0.5.8", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1232444", + "ws": "8.16.0" + }, + "engines": { + "node": ">=16.13.2" + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "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/socks-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/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/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/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.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/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "license": "MIT", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "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/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "license": "MIT", + "optional": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.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.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..31afa3d --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "property-brochure-pdf-generator", + "version": "1.0.0", + "description": "Property Brochure PDF Generator API using Puppeteer", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "keywords": [ + "pdf", + "generation", + "puppeteer", + "express", + "api" + ], + "author": "", + "license": "MIT", + "dependencies": { + "axios": "^1.11.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "puppeteer": "^21.5.2" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/refined-pdf-generation.js b/refined-pdf-generation.js new file mode 100644 index 0000000..55588e6 --- /dev/null +++ b/refined-pdf-generation.js @@ -0,0 +1,546 @@ +#!/usr/bin/env node +/** + * Refined PDF Generation - NO WHITE SPACES + * + * Key Features: + * 1. Zero white spaces - comprehensive CSS reset + * 2. HTML content preprocessing to remove unwanted whitespace + * 3. Optimized page breaks with no extra spacing + * 4. Clean A4 format with minimal margins + * 5. No extra pages or blank spaces + */ + +const express = require('express'); +const puppeteer = require('puppeteer'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const app = express(); +const PORT = process.env.PORT || 8002; // Different port + +// Configure temp folder +const TEMP_FOLDER = 'temp'; +if (!fs.existsSync(TEMP_FOLDER)) { + fs.mkdirSync(TEMP_FOLDER, { recursive: true }); +} + +// Middleware +app.use(cors()); +app.use(bodyParser.json({ limit: '100mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '100mb' })); + +// Serve static files from temp folder +app.use('/download', express.static(TEMP_FOLDER)); + +/** + * Clean HTML content to remove unwanted whitespace + */ +function cleanHTMLContent(htmlContent) { + // Remove extra whitespace and normalize content + let cleaned = htmlContent + // Remove multiple spaces and normalize whitespace + .replace(/\s+/g, ' ') + // Remove whitespace around tags + .replace(/>\s+<') + // Remove leading/trailing whitespace + .trim() + // Remove empty lines + .replace(/\n\s*\n/g, '\n') + // Remove whitespace at start/end of lines + .replace(/^\s+|\s+$/gm, '') + // Remove multiple consecutive line breaks + .replace(/\n{3,}/g, '\n\n') + // Remove whitespace before closing tags + .replace(/\s+<\//g, '\s+/g, '>') + // Remove whitespace in empty tags + .replace(/<(\w+)[^>]*>\s*<\/\1>/g, '') + // Remove comments that might contain whitespace + .replace(//g, '') + // Remove script and style content whitespace + .replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, (match) => { + return match.replace(/\s+/g, ' ').trim(); + }); + + return cleaned; +} + +/** + * Generate PDF with ZERO white spaces + */ +async function generateNoWhitespacePDF(htmlContent, outputPDF) { + let browser = null; + + try { + console.log("๐Ÿš€ Starting NO-WHITESPACE PDF generation..."); + + // Clean the HTML content first + const cleanedHTML = cleanHTMLContent(htmlContent); + console.log("๐Ÿงน HTML content cleaned and whitespace removed"); + + // Launch browser + browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + '--no-first-run', + '--no-zygote', + '--disable-gpu', + '--disable-web-security', + '--disable-features=VizDisplayCompositor' + ] + }); + + const page = await browser.newPage(); + + // Set viewport to A4 dimensions + await page.setViewport({ width: 794, height: 1123 }); + page.setDefaultTimeout(0); + + // Create ZERO-WHITESPACE HTML template + const noWhitespaceHTML = ` + + + + + + + +
${cleanedHTML.replace(/]*>|<\/body>/gi, '')}
+ +`; + + // Set content with no-whitespace HTML + await page.setContent(noWhitespaceHTML, { + waitUntil: 'networkidle0', + timeout: 0 + }); + + // Wait for content to load + await page.waitForTimeout(2000); + + // Additional CSS injection to ensure no spacing + await page.addStyleTag({ + content: ` + /* Final spacing removal */ + * { + -webkit-print-color-adjust: exact !important; + color-adjust: exact !important; + margin: 0 !important; + padding: 0 !important; + } + + /* Remove any remaining spacing */ + body, html { + overflow: visible !important; + margin: 0 !important; + padding: 0 !important; + } + + /* Ensure no extra pages */ + .page-break { + page-break-before: always !important; + height: 0 !important; + margin: 0 !important; + padding: 0 !important; + } + ` + }); + + console.log("๐Ÿ“„ Generating NO-WHITESPACE A4 PDF..."); + + const pdfOptions = { + path: outputPDF, + printBackground: true, + margin: { + top: '0.1in', // Minimal margins + right: '0.1in', + bottom: '0.1in', + left: '0.1in' + }, + scale: 1.0, + format: 'A4', + preferCSSPageSize: false, + displayHeaderFooter: false, // No headers/footers to avoid spacing + omitBackground: false + }; + + // Generate PDF + await page.pdf(pdfOptions); + await browser.close(); + + // Get PDF file size for logging + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + console.log(`โœ… NO-WHITESPACE A4 PDF generated: ${outputPDF}`); + console.log(`๐Ÿ“ Format: A4 (210mm x 297mm)`); + console.log(`๐Ÿ“ Margins: 0.1in all sides (minimal)`); + console.log(`๐Ÿ” Scale: 1.0`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + console.log(`๐Ÿšซ White spaces: ELIMINATED`); + + } catch (error) { + if (error.name === 'TimeoutError') { + throw new Error("Timeout: Page took too long to load."); + } + console.error(`โŒ PDF generation error: ${error.message}`); + throw error; + } finally { + // Cleanup + if (browser) { + try { + await browser.close(); + } catch (error) { + console.error('Error closing browser:', error); + } + } + } +} + +/** + * Main PDF generation endpoint - NO WHITESPACES + */ +app.post('/generate-no-whitespace-pdf', async (req, res) => { + try { + // Get request data + let inputContent = null; + let outputName = null; + let returnDownloadLink = false; + + if (req.is('application/json')) { + try { + const data = req.body; + if (data) { + if (data.input) { + inputContent = data.input; + outputName = data.output || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.html) { + inputContent = data.html; + outputName = data.filename || null; + returnDownloadLink = data.return_download_link || false; + } else if (data.content) { + inputContent = data.content; + outputName = data.name || null; + returnDownloadLink = data.return_download_link || false; + } + } + } catch (parseError) { + console.error('JSON parse error:', parseError); + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } else if (req.is('application/x-www-form-urlencoded')) { + inputContent = req.body.input || req.body.html || req.body.content; + outputName = req.body.output || req.body.filename || req.body.name; + returnDownloadLink = req.body.return_download_link === 'true' || req.body.return_download_link === true; + } else { + try { + const rawData = JSON.parse(req.body); + inputContent = rawData.input || rawData.html || rawData.content; + outputName = rawData.output || rawData.filename || rawData.name; + returnDownloadLink = rawData.return_download_link || false; + } catch (error) { + return res.status(400).json({ error: 'Invalid request format' }); + } + } + + // Validate input + if (!inputContent) { + return res.status(400).json({ error: 'No HTML content provided' }); + } + + console.log(`๐Ÿ“ฅ Received NO-WHITESPACE PDF request - Download Link: ${returnDownloadLink}`); + + // Generate unique filename + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const randomId = crypto.randomBytes(4).toString('hex'); + const filename = outputName ? + `${outputName.replace(/[^a-zA-Z0-9-_]/g, '_')}_nowhitespace_${randomId}.pdf` : + `property_brochure_nowhitespace_${randomId}.pdf`; + + const outputPDF = path.join(TEMP_FOLDER, filename); + + // Generate PDF with no whitespaces + await generateNoWhitespacePDF(inputContent, outputPDF); + + if (returnDownloadLink) { + // Return download link + const downloadUrl = `https://salesforce.tech4biz.io/download/${filename}`; + const pdfStats = fs.statSync(outputPDF); + const pdfSizeMB = pdfStats.size / (1024 * 1024); + + const result = { + success: true, + message: "NO-WHITESPACE PDF generated successfully - All white spaces eliminated", + download_url: downloadUrl, + filename: filename, + file_size_mb: pdfSizeMB.toFixed(2), + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), + generated_at: new Date().toISOString(), + pdf_id: randomId, + status: "download_ready", + features: [ + "Zero white spaces", + "Minimal margins (0.1in)", + "HTML content preprocessing", + "Complete CSS reset", + "No extra pages", + "Clean A4 format" + ] + }; + + console.log(`๐Ÿ”— NO-WHITESPACE PDF ready for download: ${downloadUrl}`); + console.log(`๐Ÿ“Š File size: ${pdfSizeMB.toFixed(2)} MB`); + + res.json(result); + } else { + // Return base64 content + const pdfBuffer = fs.readFileSync(outputPDF); + const base64Content = pdfBuffer.toString('base64'); + + // Clean up the file + if (fs.existsSync(outputPDF)) { + fs.unlinkSync(outputPDF); + } + + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); + res.send(Buffer.from(base64Content, 'base64')); + } + + } catch (error) { + console.error(`โŒ NO-WHITESPACE PDF API Error: ${error.message}`); + res.status(500).json({ error: error.message }); + } +}); + +/** + * Health check endpoint + */ +app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + version: '1.0.0-no-whitespace', + features: [ + 'zero_white_spaces', + 'minimal_margins', + 'html_preprocessing', + 'complete_css_reset', + 'no_extra_pages', + 'clean_a4_format', + 'download_links', + 'base64_response' + ] + }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`๐Ÿš€ NO-WHITESPACE PDF Generator API running on port ${PORT}`); + console.log(`๐Ÿ“ Temp folder: ${TEMP_FOLDER}`); + console.log(`๐Ÿ”— Download endpoint: http://localhost:${PORT}/download/:filename`); + console.log(`๐Ÿ“Š Health check: http://localhost:${PORT}/health`); + console.log(`๐Ÿšซ WHITESPACE: ELIMINATED - Zero spacing, minimal margins, clean output`); +}); + +module.exports = app; + diff --git a/salesforce-popup-integration.js b/salesforce-popup-integration.js new file mode 100644 index 0000000..2779154 --- /dev/null +++ b/salesforce-popup-integration.js @@ -0,0 +1,164 @@ +/** + * Salesforce LWC Integration for PDF Download Popup + * Add this code to your propertyTemplateSelector.js file + */ + +// Add this method to your LWC component +showDownloadPopup(pdfResult) { + // Create modal HTML + const modalHTML = ` +
+ +
+ `; + + // Add modal to page + document.body.insertAdjacentHTML('beforeend', modalHTML); +} + +// Helper method to format dates +formatDate(dateString) { + if (!dateString) return 'Unknown'; + const date = new Date(dateString); + return date.toLocaleString(); +} + +// Update your existing PDF generation method to show popup +async generatePDFWithPopup() { + try { + // Show loading state + this.showLoading = true; + this.errorMessage = ''; + + // Call your existing PDF generation method + const result = await PDFGenerationProxy.generatePDFFromHTML(this.htmlContent, 'A4'); + + if (result.success) { + // Show the download popup + this.showDownloadPopup(result); + } else { + this.errorMessage = result.error || 'PDF generation failed'; + } + + } catch (error) { + this.errorMessage = error.message || 'An error occurred'; + } finally { + this.showLoading = false; + } +} + +// Add this to your LWC template to show the popup +// diff --git a/test-api.js b/test-api.js new file mode 100644 index 0000000..8377207 --- /dev/null +++ b/test-api.js @@ -0,0 +1,139 @@ +#!/usr/bin/env node +/** + * Test script for the Property Brochure PDF Generator API + * Run this after starting the server to test the API endpoints + */ + +const fs = require('fs'); +const path = require('path'); + +// Test HTML content +const testHTML = fs.readFileSync('test.html', 'utf8'); + +// Test the API endpoints +async function testAPI() { + console.log('๐Ÿงช Testing Property Brochure PDF Generator API...\n'); + + // Test 1: Root endpoint + console.log('1๏ธโƒฃ Testing GET / endpoint...'); + try { + const response = await fetch('http://localhost:8000/'); + const data = await response.json(); + console.log('โœ… Root endpoint working:', data.message); + console.log(' Version:', data.version); + console.log(' Status:', data.status); + } catch (error) { + console.log('โŒ Root endpoint failed:', error.message); + } + + // Test 2: Health endpoint + console.log('\n2๏ธโƒฃ Testing GET /health endpoint...'); + try { + const response = await fetch('http://localhost:8000/health'); + const data = await response.json(); + console.log('โœ… Health endpoint working:', data.status); + console.log(' Service:', data.service); + } catch (error) { + console.log('โŒ Health endpoint failed:', error.message); + } + + // Test 3: PDF generation + console.log('\n3๏ธโƒฃ Testing POST /generate-pdf endpoint...'); + try { + const response = await fetch('http://localhost:8000/generate-pdf', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + input: testHTML, + output: 'test_brochure.pdf' + }) + }); + + if (response.ok) { + const buffer = await response.arrayBuffer(); + const outputPath = path.join(__dirname, 'test_brochure.pdf'); + fs.writeFileSync(outputPath, Buffer.from(buffer)); + console.log('โœ… PDF generation successful!'); + console.log(' File saved as: test_brochure.pdf'); + console.log(' File size:', (buffer.byteLength / 1024).toFixed(2), 'KB'); + } else { + const error = await response.json(); + console.log('โŒ PDF generation failed:', error.error); + } + } catch (error) { + console.log('โŒ PDF generation failed:', error.message); + } + + // Test 4: Error handling - empty input + console.log('\n4๏ธโƒฃ Testing error handling (empty input)...'); + try { + const response = await fetch('http://localhost:8000/generate-pdf', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + input: '', + output: 'error_test.pdf' + }) + }); + + if (response.status === 400) { + const error = await response.json(); + console.log('โœ… Error handling working:', error.error); + } else { + console.log('โŒ Expected error 400, got:', response.status); + } + } catch (error) { + console.log('โŒ Error handling test failed:', error.message); + } + + // Test 5: Alternative format (Apex compatibility) + console.log('\n5๏ธโƒฃ Testing alternative format (Apex compatibility)...'); + try { + const response = await fetch('http://localhost:8000/generate-pdf', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + html_content: testHTML, + filename: 'apex_test.pdf' + }) + }); + + if (response.ok) { + const buffer = await response.arrayBuffer(); + const outputPath = path.join(__dirname, 'apex_test.pdf'); + fs.writeFileSync(outputPath, Buffer.from(buffer)); + console.log('โœ… Apex format working!'); + console.log(' File saved as: apex_test.pdf'); + } else { + const error = await response.json(); + console.log('โŒ Apex format failed:', error.error); + } + } catch (error) { + console.log('โŒ Apex format test failed:', error.message); + } + + console.log('\n๐ŸŽ‰ API testing completed!'); + console.log('\n๐Ÿ“ Generated files:'); + if (fs.existsSync('test_brochure.pdf')) { + console.log(' โ€ข test_brochure.pdf'); + } + if (fs.existsSync('apex_test.pdf')) { + console.log(' โ€ข apex_test.pdf'); + } +} + +// Check if fetch is available (Node.js 18+) +if (typeof fetch === 'undefined') { + console.log('โŒ This script requires Node.js 18+ or you need to install node-fetch'); + console.log(' Install with: npm install node-fetch'); + process.exit(1); +} + +// Run tests +testAPI().catch(console.error); diff --git a/test-fixed-pdf.js b/test-fixed-pdf.js new file mode 100644 index 0000000..d9db24d --- /dev/null +++ b/test-fixed-pdf.js @@ -0,0 +1,258 @@ +#!/usr/bin/env node +/** + * Test script for the fixed PDF generation + * Demonstrates the fixes for page breaks, A4 size, and page numbers + */ + +const axios = require('axios'); + +// Test HTML content with 3 pages +const testHTML = ` + + + + + Property Brochure Test + + + + +
+
+

Property Brochure - Page 1

+
+
+

Property Overview

+

This is the first page of our property brochure. It contains the main property information and overview.

+
Property Image 1
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+
+ +
+ + +
+
+

Property Brochure - Page 2

+
+
+

Property Features

+

This is the second page showcasing the property features and amenities.

+
Property Image 2
+
    +
  • Modern kitchen with stainless steel appliances
  • +
  • Hardwood floors throughout
  • +
  • Central air conditioning
  • +
  • Private balcony with city views
  • +
  • In-unit washer and dryer
  • +
+

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

+
+ +
+ + +
+
+

Property Brochure - Page 3

+
+
+

Contact Information

+

This is the final page with contact information and call-to-action.

+
Property Image 3
+

Contact Details

+

Phone: (555) 123-4567

+

Email: info@property.com

+

Address: 123 Property Street, City, State 12345

+

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ +
+ + +`; + +async function testFixedPDFGeneration() { + try { + console.log('๐Ÿงช Testing Fixed PDF Generation...'); + console.log('๐Ÿ“„ Test HTML has 3 pages'); + + // Test the fixed PDF generation endpoint + const response = await axios.post('http://localhost:8001/generate-fixed-pdf', { + input: testHTML, + output: 'test_property_brochure', + return_download_link: true + }, { + headers: { + 'Content-Type': 'application/json' + }, + timeout: 60000 // 60 second timeout + }); + + if (response.data.success) { + console.log('โœ… Fixed PDF Generation Test PASSED!'); + console.log('๐Ÿ“Š Results:'); + console.log(` - Success: ${response.data.success}`); + console.log(` - Message: ${response.data.message}`); + console.log(` - Download URL: ${response.data.download_url}`); + console.log(` - File Size: ${response.data.file_size_mb} MB`); + console.log(` - Fixes Applied: ${response.data.fixes_applied.join(', ')}`); + console.log(''); + console.log('๐ŸŽฏ Expected Results:'); + console.log(' - PDF should have exactly 3 pages (no extra blank pages)'); + console.log(' - A4 page size (210mm x 297mm)'); + console.log(' - Proper page breaks between content'); + console.log(' - Page numbers in footer: "Page X of 3"'); + console.log(''); + console.log('๐Ÿ”— Download the PDF to verify:'); + console.log(` ${response.data.download_url}`); + } else { + console.log('โŒ Fixed PDF Generation Test FAILED!'); + console.log('Response:', response.data); + } + + } catch (error) { + console.error('โŒ Test Error:', error.message); + if (error.response) { + console.error('Response data:', error.response.data); + } + } +} + +async function testOriginalPDFGeneration() { + try { + console.log('๐Ÿงช Testing Original PDF Generation (for comparison)...'); + + // Test the original PDF generation endpoint + const response = await axios.post('http://localhost:8000/generate-pdf', { + input: testHTML, + output: 'test_property_brochure_original', + return_download_link: true + }, { + headers: { + 'Content-Type': 'application/json' + }, + timeout: 60000 // 60 second timeout + }); + + if (response.data.success) { + console.log('๐Ÿ“Š Original PDF Results:'); + console.log(` - Download URL: ${response.data.download_url}`); + console.log(` - File Size: ${response.data.file_size_mb} MB`); + console.log(''); + console.log('โš ๏ธ Original PDF may have issues:'); + console.log(' - Extra blank pages'); + console.log(' - Incorrect page breaks'); + console.log(' - Missing or incorrect page numbers'); + } + + } catch (error) { + console.error('โŒ Original Test Error:', error.message); + } +} + +// Run tests +async function runTests() { + console.log('๐Ÿš€ Starting PDF Generation Tests...'); + console.log('====================================='); + + // Test original first + await testOriginalPDFGeneration(); + + console.log(''); + console.log('====================================='); + + // Test fixed version + await testFixedPDFGeneration(); + + console.log(''); + console.log('๐Ÿ Tests completed!'); + console.log('Compare the two PDFs to see the improvements.'); +} + +// Check if servers are running +async function checkServers() { + try { + // Check original server + await axios.get('http://localhost:8000/health', { timeout: 5000 }); + console.log('โœ… Original PDF server is running on port 8000'); + } catch (error) { + console.log('โŒ Original PDF server is not running on port 8000'); + console.log(' Start it with: node index.js'); + } + + try { + // Check fixed server + await axios.get('http://localhost:8001/health', { timeout: 5000 }); + console.log('โœ… Fixed PDF server is running on port 8001'); + } catch (error) { + console.log('โŒ Fixed PDF server is not running on port 8001'); + console.log(' Start it with: node fixed-pdf-generation.js'); + } +} + +// Main execution +async function main() { + console.log('๐Ÿ” Checking servers...'); + await checkServers(); + console.log(''); + + await runTests(); +} + +if (require.main === module) { + main().catch(console.error); +} + +module.exports = { testFixedPDFGeneration, testOriginalPDFGeneration }; diff --git a/test-no-whitespace-pdf.js b/test-no-whitespace-pdf.js new file mode 100644 index 0000000..ea6b774 --- /dev/null +++ b/test-no-whitespace-pdf.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node +/** + * Test script for NO-WHITESPACE PDF generation + * Tests the refined PDF generator to ensure no white spaces + */ + +const axios = require('axios'); + +// Test HTML content with potential whitespace issues +const testHTML = ` +
+

Property Brochure Test

+ +

This is a test paragraph with extra spaces.

+ +
    +
  • Item 1 with spaces
  • +
  • Item 2 with spaces
  • +
+ + + + + + +
Cell 1 Cell 2
+ +
+

Another paragraph

+
+
+`; + +async function testNoWhitespacePDF() { + try { + console.log("๐Ÿงช Testing NO-WHITESPACE PDF generation..."); + + const response = await axios.post('http://localhost:8002/generate-no-whitespace-pdf', { + input: testHTML, + output: 'test_no_whitespace', + return_download_link: true + }, { + headers: { + 'Content-Type': 'application/json' + }, + timeout: 60000 // 60 second timeout + }); + + if (response.data.success) { + console.log("โœ… NO-WHITESPACE PDF generated successfully!"); + console.log(`๐Ÿ“„ Filename: ${response.data.filename}`); + console.log(`๐Ÿ“Š File size: ${response.data.file_size_mb} MB`); + console.log(`๐Ÿ”— Download URL: ${response.data.download_url}`); + console.log(`๐Ÿšซ Features: ${response.data.features.join(', ')}`); + + // Test health endpoint + const healthResponse = await axios.get('http://localhost:8002/health'); + console.log("๐Ÿฅ Health check:", healthResponse.data.status); + console.log("๐Ÿ”ง Features:", healthResponse.data.features.join(', ')); + + } else { + console.error("โŒ PDF generation failed:", response.data); + } + + } catch (error) { + if (error.code === 'ECONNREFUSED') { + console.error("โŒ Connection refused. Make sure the server is running on port 8002"); + console.log("๐Ÿ’ก Start the server with: node refined-pdf-generation.js"); + } else { + console.error("โŒ Test error:", error.message); + if (error.response) { + console.error("๐Ÿ“„ Response data:", error.response.data); + } + } + } +} + +// Run the test +testNoWhitespacePDF(); + diff --git a/test-pdf-generation.js b/test-pdf-generation.js new file mode 100644 index 0000000..92fc3a0 --- /dev/null +++ b/test-pdf-generation.js @@ -0,0 +1,82 @@ +#!/usr/bin/env node +/** + * Test script for the improved PDF generation API + * Tests both A3 and A4 formats with zero whitespace + */ + +const fs = require('fs'); +const path = require('path'); +const axios = require('axios'); + +const API_BASE_URL = 'http://localhost:8000'; + +// Test HTML content from preview files +const testHTMLs = { + 'grand-oak': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-grand-oak.html', 'utf8'), + 'vertice': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-vertice.html', 'utf8'), + 'serenity-house': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-serenity-house.html', 'utf8'), + 'modern-home': fs.readFileSync('/home/ubuntu/salesforce/PDF_Generation_and_Automation/previews/preview-modern-home.html', 'utf8') +}; + +async function testPDFGeneration() { + console.log('๐Ÿงช Testing ZERO-WHITESPACE PDF Generation API'); + console.log('=' .repeat(60)); + + // Test health endpoint + try { + const healthResponse = await axios.get(`${API_BASE_URL}/health`); + console.log('โœ… Health Check:', healthResponse.data); + console.log('๐Ÿ“ Supported Formats:', healthResponse.data.supported_formats); + console.log(''); + } catch (error) { + console.error('โŒ Health check failed:', error.message); + return; + } + + // Test each HTML template with both A3 and A4 formats + for (const [templateName, htmlContent] of Object.entries(testHTMLs)) { + console.log(`\n๐Ÿ  Testing Template: ${templateName.toUpperCase()}`); + console.log('-'.repeat(40)); + + for (const format of ['A4', 'A3']) { + try { + console.log(`\n๐Ÿ“„ Generating ${format} PDF...`); + + const response = await axios.post(`${API_BASE_URL}/generate-pdf`, { + input: htmlContent, + output: `${templateName}-${format.toLowerCase()}`, + format: format, + return_download_link: true + }, { + headers: { + 'Content-Type': 'application/json' + }, + timeout: 60000 // 60 seconds timeout + }); + + if (response.data.success) { + console.log(`โœ… ${format} PDF Generated Successfully!`); + console.log(`๐Ÿ“ Filename: ${response.data.filename}`); + console.log(`๐Ÿ“Š File Size: ${response.data.file_size_mb} MB`); + console.log(`๐Ÿ”— Download URL: ${response.data.download_url}`); + console.log(`๐ŸŽฏ Features: ${response.data.features.join(', ')}`); + } else { + console.log(`โŒ ${format} PDF Generation Failed:`, response.data.message); + } + + } catch (error) { + console.error(`โŒ Error generating ${format} PDF for ${templateName}:`, error.response?.data?.error || error.message); + } + } + } + + console.log('\n๐ŸŽ‰ PDF Generation Testing Complete!'); + console.log('=' .repeat(60)); +} + +// Run the test +if (require.main === module) { + testPDFGeneration().catch(console.error); +} + +module.exports = { testPDFGeneration }; diff --git a/test.html b/test.html new file mode 100644 index 0000000..86a5a55 --- /dev/null +++ b/test.html @@ -0,0 +1,161 @@ + + + + + + Property Brochure Test + + + +
+ +

Luxury Property Brochure

+ +
+

Welcome to this stunning luxury property located in the heart of the city. This magnificent home offers the perfect blend of modern design and classic elegance, featuring premium finishes and thoughtful details throughout.

+
+ +
+
+

Price

+
$1,250,000
+
+
+

Location

+

Downtown Luxury District

+

Prime City Center

+
+
+ +
+
+

Bedrooms

+

4 Master Suites

+
+
+

Bathrooms

+

5 Full + 2 Half

+
+
+ +
+
+

Square Feet

+

4,500 sq ft

+
+
+

Lot Size

+

0.75 acres

+
+
+ +

Property Features

+
    +
  • ๐ŸŠโ€โ™‚๏ธ Infinity Pool with City Views
  • +
  • ๐Ÿณ Chef's Kitchen with Premium Appliances
  • +
  • ๐ŸŽญ Home Theater with Surround Sound
  • +
  • ๐Ÿ‹๏ธโ€โ™‚๏ธ Private Fitness Center
  • +
  • ๐Ÿš— 3-Car Garage with Electric Charging
  • +
  • ๐ŸŒฟ Landscaped Gardens with Water Features
  • +
  • ๐Ÿ”’ Smart Home Security System
  • +
  • โ˜€๏ธ Solar Panels for Energy Efficiency
  • +
+ +
+

This exceptional property offers an unparalleled living experience with its open-concept design, high ceilings, and floor-to-ceiling windows that flood the space with natural light. The gourmet kitchen features custom cabinetry, quartz countertops, and top-of-the-line appliances.

+ +

The master suite is a true sanctuary with a spa-like bathroom, walk-in closet, and private balcony. Additional amenities include a wine cellar, home office, and outdoor entertainment area perfect for hosting gatherings.

+
+ +
+

Contact Information

+

๐Ÿ“ž (555) 123-4567

+

๐Ÿ“ง info@luxuryproperties.com

+

๐ŸŒ www.luxuryproperties.com

+

๐Ÿ“ 123 Luxury Lane, Downtown, City, State 12345

+
+ +
+

This brochure was generated using the Property Brochure PDF Generator API

+

Generated on:

+
+
+ + + + diff --git a/usage-examples.md b/usage-examples.md new file mode 100644 index 0000000..3ed6940 --- /dev/null +++ b/usage-examples.md @@ -0,0 +1,184 @@ +# ZERO-WHITESPACE PDF Generator API - Usage Examples + +## Overview +This API generates exact PDFs with zero whitespace and supports both A3 and A4 formats. + +## Features +- โœ… **Zero Whitespace**: Complete elimination of unwanted spacing +- โœ… **Format Options**: A3 (297mm x 420mm) and A4 (210mm x 297mm) +- โœ… **Exact Output**: PDFs match HTML exactly without extra pages +- โœ… **Advanced HTML Processing**: Removes all unnecessary elements +- โœ… **Download Links**: Get direct download URLs for generated PDFs + +## API Endpoints + +### 1. Generate PDF +**POST** `/generate-pdf` + +#### Request Body (JSON) +```json +{ + "input": "Your HTML content here", + "output": "my-property-brochure", + "format": "A4", + "return_download_link": true +} +``` + +#### Parameters +- `input` (required): HTML content to convert to PDF +- `output` (optional): Custom filename (without extension) +- `format` (optional): "A3" or "A4" (default: "A4") +- `return_download_link` (optional): true/false (default: false) + +#### Response (Download Link) +```json +{ + "success": true, + "message": "ZERO-WHITESPACE A4 PDF generated successfully", + "download_url": "https://salesforce.tech4biz.io/download/property_brochure_A4_abc123.pdf", + "filename": "property_brochure_A4_abc123.pdf", + "format": "A4", + "file_size_mb": "2.45", + "expires_at": "2024-01-15T10:30:00.000Z", + "generated_at": "2024-01-08T10:30:00.000Z", + "pdf_id": "abc123", + "status": "download_ready", + "features": ["zero_whitespace", "exact_output", "minimal_margins"] +} +``` + +### 2. Health Check +**GET** `/health` + +#### Response +```json +{ + "status": "healthy", + "timestamp": "2024-01-08T10:30:00.000Z", + "version": "9.0.0-zero-whitespace-a3-a4", + "supported_formats": ["A3", "A4"], + "features": [ + "zero_white_spaces", + "zero_margins", + "advanced_html_preprocessing", + "complete_css_reset", + "no_extra_pages", + "a3_a4_formats", + "exact_pdf_output", + "download_links", + "base64_response", + "infinite_timeouts", + "format_validation" + ] +} +``` + +## Usage Examples + +### Example 1: Generate A4 PDF with Download Link +```bash +curl -X POST http://localhost:8000/generate-pdf \ + -H "Content-Type: application/json" \ + -d '{ + "input": "

My Property

Beautiful home for sale

", + "output": "property-brochure", + "format": "A4", + "return_download_link": true + }' +``` + +### Example 2: Generate A3 PDF with Download Link +```bash +curl -X POST http://localhost:8000/generate-pdf \ + -H "Content-Type: application/json" \ + -d '{ + "input": "

Large Property

Spacious home with garden

", + "output": "large-property", + "format": "A3", + "return_download_link": true + }' +``` + +### Example 3: JavaScript/Node.js Usage +```javascript +const axios = require('axios'); + +async function generatePDF(htmlContent, format = 'A4') { + try { + const response = await axios.post('http://localhost:8000/generate-pdf', { + input: htmlContent, + format: format, + return_download_link: true + }); + + if (response.data.success) { + console.log('PDF Generated:', response.data.download_url); + return response.data.download_url; + } + } catch (error) { + console.error('Error:', error.response?.data?.error || error.message); + } +} + +// Usage +const htmlContent = '

My Property

'; +generatePDF(htmlContent, 'A4'); +``` + +### Example 4: Python Usage +```python +import requests +import json + +def generate_pdf(html_content, format='A4'): + url = 'http://localhost:8000/generate-pdf' + data = { + 'input': html_content, + 'format': format, + 'return_download_link': True + } + + response = requests.post(url, json=data) + + if response.status_code == 200: + result = response.json() + if result['success']: + print(f"PDF Generated: {result['download_url']}") + return result['download_url'] + else: + print(f"Error: {response.json().get('error', 'Unknown error')}") + +# Usage +html_content = '

My Property

' +generate_pdf(html_content, 'A4') +``` + +## Key Improvements + +### 1. Zero Whitespace +- Complete CSS reset eliminates all default browser spacing +- Advanced HTML preprocessing removes unnecessary whitespace +- Zero margins ensure exact PDF output + +### 2. Format Support +- **A4**: 210mm x 297mm (standard document size) +- **A3**: 297mm x 420mm (larger format for detailed layouts) + +### 3. Exact Output +- PDFs match HTML exactly without extra pages +- No unwanted spacing or margins +- Perfect page count matching + +### 4. Advanced Processing +- Removes HTML comments and empty elements +- Eliminates whitespace between tags +- Optimizes content for PDF generation + +## Testing +Run the test script to verify functionality: +```bash +node test-pdf-generation.js +``` + +This will test all preview templates with both A3 and A4 formats.