resume/scripts/export.js
ilia c8a49018b1 Customize resume data, themes, export script, and PDF outputs
- Update resume/data.yml and English strings
- Adjust resume.vue, options, and theme components
- Change export flow in scripts/export.js and package.json
- Refresh PDF artifacts; add static/green.pdf; trim unused pdf/

Made-with: Cursor
2026-03-25 10:36:38 -04:00

125 lines
3.6 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 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
});
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();