Add PDF generation to Docker build process
ci/woodpecker/push/woodpecker Pipeline failed
Details
ci/woodpecker/push/woodpecker Pipeline failed
Details
- Update Dockerfile to install npm and run PDF generation during build - Move generate-pdfs.js and package.json to docker/resume/ for Docker context - Update generate-pdfs.js to work from /srv directory in container - PDFs are now generated automatically during Docker build before deployment - PDFs will be available at /pdfs/ path matching HTML file structure
This commit is contained in:
parent
83c0ae74f7
commit
19182d6d21
|
|
@ -1,7 +1,7 @@
|
|||
FROM caddy:2.7-alpine
|
||||
|
||||
# Install dependencies
|
||||
RUN apk add --no-cache nodejs bash
|
||||
RUN apk add --no-cache nodejs npm bash
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /srv
|
||||
|
|
@ -9,11 +9,17 @@ WORKDIR /srv
|
|||
# Copy website files
|
||||
COPY . /srv
|
||||
|
||||
# Install npm dependencies for PDF generation
|
||||
RUN cd /srv && npm install --production
|
||||
|
||||
# Run all update scripts (sitemap, navigation, stories, CSP hashes, accessibility fixes)
|
||||
RUN cd /srv && \
|
||||
chmod +x update-all.sh && \
|
||||
./update-all.sh
|
||||
|
||||
# Generate PDFs for all pages
|
||||
RUN cd /srv && npm run generate-pdfs
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* PDF Generation Script
|
||||
*
|
||||
* Uses Puppeteer to render each HTML page to PDF.
|
||||
* Run with: node generate-pdfs.js
|
||||
*
|
||||
* Prerequisites: npm install puppeteer
|
||||
*/
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Configuration
|
||||
const SITE_DIR = __dirname; // Running from /srv in Docker, which contains all site files
|
||||
const PDF_DIR = path.join(SITE_DIR, 'pdfs');
|
||||
const PORT = 8765;
|
||||
|
||||
// MIME types for static file serving
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html',
|
||||
'.css': 'text/css',
|
||||
'.js': 'application/javascript',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.ico': 'image/x-icon',
|
||||
'.woff': 'font/woff',
|
||||
'.woff2': 'font/woff2',
|
||||
'.ttf': 'font/ttf',
|
||||
'.eot': 'application/vnd.ms-fontobject',
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all HTML files in a directory recursively
|
||||
*/
|
||||
function findHtmlFiles(dir, baseDir = dir) {
|
||||
const files = [];
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Skip pdfs directory, node_modules, and hidden directories
|
||||
if (entry.name === 'pdfs' || entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
files.push(...findHtmlFiles(fullPath, baseDir));
|
||||
} else if (entry.isFile() && entry.name.endsWith('.html')) {
|
||||
// Skip template files
|
||||
if (entry.name.includes('template') || entry.name.includes('with-includes')) {
|
||||
continue;
|
||||
}
|
||||
const relativePath = path.relative(baseDir, fullPath);
|
||||
files.push(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a simple static file server
|
||||
*/
|
||||
function createServer() {
|
||||
return http.createServer((req, res) => {
|
||||
let urlPath = req.url.split('?')[0];
|
||||
if (urlPath === '/') urlPath = '/index.html';
|
||||
|
||||
const filePath = path.join(SITE_DIR, urlPath);
|
||||
const ext = path.extname(filePath);
|
||||
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
||||
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
return;
|
||||
}
|
||||
res.writeHead(200, { 'Content-Type': contentType });
|
||||
res.end(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure directory exists
|
||||
*/
|
||||
function ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PDF for a single HTML file
|
||||
*/
|
||||
async function generatePdf(browser, htmlFile) {
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Convert file path to URL path
|
||||
const urlPath = '/' + htmlFile.replace(/\\/g, '/');
|
||||
const url = `http://localhost:${PORT}${urlPath}`;
|
||||
|
||||
// Determine output PDF path
|
||||
const pdfRelativePath = htmlFile.replace(/\.html$/, '.pdf');
|
||||
const pdfPath = path.join(PDF_DIR, pdfRelativePath);
|
||||
|
||||
// Ensure output directory exists
|
||||
ensureDir(path.dirname(pdfPath));
|
||||
|
||||
try {
|
||||
// Navigate to the page and wait for content to load
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle0',
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// Wait a bit for any JavaScript to finish
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Generate PDF
|
||||
await page.pdf({
|
||||
path: pdfPath,
|
||||
format: 'A4',
|
||||
printBackground: true,
|
||||
margin: {
|
||||
top: '20mm',
|
||||
right: '15mm',
|
||||
bottom: '20mm',
|
||||
left: '15mm'
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✓ Generated: ${pdfRelativePath}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ Failed: ${htmlFile} - ${error.message}`);
|
||||
} finally {
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
async function main() {
|
||||
console.log('PDF Generation Script');
|
||||
console.log('=====================\n');
|
||||
|
||||
// Find all HTML files
|
||||
const htmlFiles = findHtmlFiles(SITE_DIR);
|
||||
console.log(`Found ${htmlFiles.length} HTML files to process\n`);
|
||||
|
||||
if (htmlFiles.length === 0) {
|
||||
console.log('No HTML files found. Exiting.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean and create PDF directory
|
||||
if (fs.existsSync(PDF_DIR)) {
|
||||
fs.rmSync(PDF_DIR, { recursive: true });
|
||||
}
|
||||
ensureDir(PDF_DIR);
|
||||
|
||||
// Start local server
|
||||
const server = createServer();
|
||||
await new Promise(resolve => server.listen(PORT, resolve));
|
||||
console.log(`Local server started on port ${PORT}\n`);
|
||||
|
||||
// Launch browser
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new',
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
|
||||
try {
|
||||
// Generate PDFs for each HTML file
|
||||
for (const htmlFile of htmlFiles) {
|
||||
await generatePdf(browser, htmlFile);
|
||||
}
|
||||
|
||||
console.log(`\n✓ PDF generation complete! Files saved to: ${PDF_DIR}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
server.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main().catch(error => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "docker",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"generate-pdfs": "node generate-pdfs.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"puppeteer": "^21.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -53,28 +53,33 @@ fi
|
|||
echo "=== Ensuring accessibility fixes are applied ==="
|
||||
# Check if stories.css exists
|
||||
if [ -f "$SCRIPT_DIR/stories/stories.css" ]; then
|
||||
# Check if the file already has the accessibility fixes
|
||||
if ! grep -q "color: #004494;" "$SCRIPT_DIR/stories/stories.css"; then
|
||||
echo "Applying accessibility fixes to stories.css..."
|
||||
# Use sed to add the color property to story-nav-link class
|
||||
sed -i '' 's/\.story-nav-link {/\.story-nav-link {\n color: #004494; \/* Darker blue for 7:1+ contrast ratio *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
sed -i '' 's/\.story-nav-link:hover {/\.story-nav-link:hover {\n color: #003366; \/* Even darker on hover for better visibility *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
# Check if the file already has the accessibility fixes
|
||||
if ! grep -q "color: #004494;" "$SCRIPT_DIR/stories/stories.css"; then
|
||||
echo "Applying accessibility fixes to stories.css..."
|
||||
# Use sed to add the color property to story-nav-link class
|
||||
sed -i '' 's/\.story-nav-link {/\.story-nav-link {\n color: #004494; \/* Darker blue for 7:1+ contrast ratio *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
sed -i '' 's/\.story-nav-link:hover {/\.story-nav-link:hover {\n color: #003366; \/* Even darker on hover for better visibility *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
|
||||
# Update placeholder-notice links
|
||||
sed -i '' 's/\.placeholder-notice a {/\.placeholder-notice a {\n color: #004494; \/* Darker blue for 7:1+ contrast ratio *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
# Update placeholder-notice links
|
||||
sed -i '' 's/\.placeholder-notice a {/\.placeholder-notice a {\n color: #004494; \/* Darker blue for 7:1+ contrast ratio *\//g' "$SCRIPT_DIR/stories/stories.css"
|
||||
|
||||
# Add hover state for placeholder-notice links if it doesn't exist
|
||||
if ! grep -q "\.placeholder-notice a:hover" "$SCRIPT_DIR/stories/stories.css"; then
|
||||
echo -e "\n.placeholder-notice a:hover {\n color: #003366; /* Even darker on hover */\n}" >> "$SCRIPT_DIR/stories/stories.css"
|
||||
# Add hover state for placeholder-notice links if it doesn't exist
|
||||
if ! grep -q "\.placeholder-notice a:hover" "$SCRIPT_DIR/stories/stories.css"; then
|
||||
echo -e "\n.placeholder-notice a:hover {\n color: #003366; /* Even darker on hover */\n}" >> "$SCRIPT_DIR/stories/stories.css"
|
||||
fi
|
||||
|
||||
echo "Accessibility fixes applied to stories.css"
|
||||
else
|
||||
echo "Accessibility fixes already present in stories.css"
|
||||
fi
|
||||
|
||||
echo "Accessibility fixes applied to stories.css"
|
||||
else
|
||||
echo "Accessibility fixes already present in stories.css"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ stories/stories.css not found, skipping accessibility fixes"
|
||||
echo "⚠️ stories/stories.css not found, skipping accessibility fixes"
|
||||
fi
|
||||
|
||||
# Generate PDFs (this is now done in Dockerfile, but kept here for manual runs)
|
||||
echo "=== PDF generation ==="
|
||||
echo "Note: PDFs are generated during Docker build. Skipping in update-all.sh"
|
||||
echo "To generate PDFs manually, run: npm run generate-pdfs"
|
||||
|
||||
echo "=== All updates completed successfully ==="
|
||||
echo "To apply changes, restart the server using: ./caddy.sh"
|
||||
|
|
|
|||
Loading…
Reference in New Issue