santifer/career-ops · warning
ENOENT
ENOENT
Error message
⚠️ Font file not found, keeping original reference: fonts/${name} What it means
generate-pdf.mjs inlines font files referenced by an HTML CV as base64 data URLs before rendering with Playwright. When a font file referenced as fonts/<name> does not exist on disk, the read throws ENOENT; the code deliberately downgrades it to a warning and keeps the original CSS reference, so the PDF renders with a fallback font (or a broken reference) instead of the intended typeface.
Solutions
- Place the missing font file at the referenced path: find the <name> from the warning and copy that exact filename into fonts/ next to generate-pdf.mjs
- Run the script from the repo root so the relative fonts/ path resolves
- Update the HTML/CSS template to reference an installed system font (e.g. 'Arial', sans-serif) instead of fonts/<name> if vendoring is not desired
Example fix
// before (fonts dir incomplete)
cv-template.html: @font-face { src: url('fonts/Inter-Bold.woff2'); } // file absent -> ENOENT
// after
cp ~/Downloads/Inter-Bold.woff2 fonts/Inter-Bold.woff2 Defensive patterns
Strategy: fallback
Validate before calling
import { accessSync, constants } from 'node:fs';
function fontFileExists(fontPath) {
try { accessSync(fontPath, constants.R_OK); return true; } catch { return false; }
}
if (!fontFileExists(`fonts/${name}`)) console.warn(`Font missing: fonts/${name}`); Try / catch
try {
const buf = await fs.readFile(fontPath);
// inline as data URL
} catch (err) {
if (err?.code === 'ENOENT') return keepOriginalReference();
throw err;
} Prevention
- Vendor all referenced font files into fonts/ and commit or install them
- Run PDF generation from the repo root
- Add a startup check that every @font-face URL in the template resolves to a file
- Keep font names in the template in sync with the fonts directory listing
When it happens
Trigger: FONT_REF replacement loop calls fs read on a fonts/<name> path that does not exist — cv.md/template references a font family (e.g. fonts/Inter-Regular.woff2) that was never placed in the fonts/ directory, the font file was renamed or deleted, or the script runs from a working directory where fonts/ is not present.
Common situations: Using a custom cv-template.html that names a specific font but only vendoring part of the family (missing weights); cloning the repo without the fonts directory (gitignored binaries); running generate-pdf.mjs from a different cwd than the repo root so relative paths resolve wrong.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Could not determine the rendered PDF page count from its…
- ⚠️ Font file not found, keeping original reference: fonts/
- Image failed to decode within 10s (unreadable or corrupt…
- archive render skipped — no Playwright browser in env
- Blocked request to restricted destination (DNS)
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/a209c415216c5853.
Report an issue: GitHub.
Appendix: source
Thrown at generate-pdf.mjs:1606
const fontPath = resolve(fontsDir, name);
const rel = relative(fontsDir, fontPath);
if (rel.startsWith('..') || isAbsolute(rel)) {
console.warn(`⚠️ Font reference escapes fonts/, keeping original reference: ${name}`);
continue;
}
if (_fontDataUrlCache.has(fontPath)) {
dataUrls.set(name, _fontDataUrlCache.get(fontPath));
continue;
}
try {
const buf = await readFile(fontPath);
const ext = name.slice(name.lastIndexOf('.') + 1).toLowerCase();
const dataUrl = `url('data:${MIME[ext] || 'application/octet-stream'};base64,${buf.toString('base64')}')`;
_fontDataUrlCache.set(fontPath, dataUrl);
dataUrls.set(name, dataUrl);
} catch (err) {
if (err?.code !== 'ENOENT') throw err;
console.warn(`⚠️ Font file not found, keeping original reference: fonts/${name}`);
}
}
return html.replace(FONT_REF, (match, _quote, name) => dataUrls.get(name) || match);
}
/**
* Render an HTML string to a PDF file via headless Chromium.
*
* Writes the HTML to a temporary file in the baseDir and loads it via
* page.goto() to give the page a file:// origin. This allows relative
* resources (images, fonts) to load — setContent() runs from about:blank
* and Chromium blocks file:// subresource loads from non-file origins.
*
* Local url('./fonts/...') references are inlined as data: URLs first so
* fonts also survive the ATS normalization pass (which may strip font refs).
*
* @param {string} html - Full HTML document to render.
* @param {string} outputPath - Absolute path to write the PDF to.View on GitHub (pinned to aac998c7ed)