resume/scripts/export.js
ilia fa336caf31 Update resume content, styling, and export configuration
Refine experience descriptions for clarity and brevity, adjust green
theme colors and spacing, add part-time employment type, add
company-section-label style, set print media type in export script,
and switch background to white.

Made-with: Cursor
2026-03-25 16:00:36 -04:00

127 lines
3.7 KiB
JavaScript
Executable File

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const http = require('http');
const config = require('../config');
const devPort = process.env.PORT || config.dev.port;
const {
interval
} = require('rxjs');
const {
filter,
first,
mergeMap
} = require('rxjs/operators');
const fetchResponse = () => {
return new Promise((res, rej) => {
try {
const req = http.request(`http://localhost:${devPort}/#/`, response => res(response.statusCode));
req.on('error', (err) => rej(err));
req.end();
} catch (err) {
rej(err);
}
});
};
const waitForServerReachable = () => {
return interval(1000).pipe(
mergeMap(async () => {
try {
const statusCode = await fetchResponse();
if (statusCode === 200) return true;
} catch (err) {}
return false;
}),
filter(ok => !!ok)
);
};
/*
const timedOut = timeout => {
return new Promise(res => {
setTimeout(res, timeout);
});
};
*/
const convert = async () => {
await waitForServerReachable().pipe(
first()
).toPromise();
console.log('Connected to server ...');
console.log('Exporting ...');
try {
const fullDirectoryPath = path.join(__dirname, '../pdf/');
let directories = getResumesFromDirectories();
const resumeFilterRaw = (process.env.EXPORT_RESUME || process.argv[2] || '').trim();
const resumeFilter = resumeFilterRaw.replace(/\.vue$/i, '').toLowerCase();
if (resumeFilter) {
directories = directories.filter(d => d.name.toLowerCase() === resumeFilter);
if (directories.length === 0) {
console.error(
`No resume template "${resumeFilterRaw}". Expected a name like "green" (see src/resumes/*.vue).`
);
process.exit(1);
}
console.log('Resume filter: ' + directories.map(d => d.name).join(', '));
}
for (const dir of directories) {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--font-render-hinting=none']
});
const page = await browser.newPage();
await page.goto(`http://localhost:${devPort}/#/resume/` + dir.name, {
waitUntil: 'load',
timeout: 120000
});
try {
await page.evaluate(() => document.fonts.ready);
} catch (_err) {
/* ignore if fonts API missing */
}
await page.emulateMediaType('print');
await new Promise((r) => setTimeout(r, 300));
if (
!fs.existsSync(fullDirectoryPath)
) {
fs.mkdirSync(fullDirectoryPath);
}
await page.pdf({
path: fullDirectoryPath + dir.name + '.pdf',
format: 'A4',
printBackground: true,
margin: { top: '0', bottom: '0', left: '0', right: '0' }
});
await browser.close();
}
} catch (err) {
throw new Error(err);
}
console.log('Finished exports.');
};
const getResumesFromDirectories = () => {
const directories = getDirectories();
return directories
.map(dir => {
const fileName = dir.replace('.vue', '');
return {
path: fileName,
name: fileName
};
});
};
const getDirectories = () => {
const srcpath = path.join(__dirname, '../src/resumes');
return fs.readdirSync(srcpath)
.filter(file => file !== 'resumes.js' && file !== 'template.vue' && file !== 'options.js');
};
convert();