cvexp
v2.1.0
Published
Render a resume JSON file to a polished A4 PDF (and matching HTML). React SSR + Tailwind v4 + headless Chrome. Single template, zero theming.
Maintainers
Readme
cvexp
JSON → A4 PDF resume. One template, no theming knobs.
Pipeline: React SSR → Tailwind v4 (compiled at build time) → Uint8Array via a caller-supplied Puppeteer-compatible browser. No browser is bundled — works on Node.js and Cloudflare Workers alike.
Install
npm i cvexpUsage
generate(input, browser) validates input, renders HTML via React SSR, then uses the provided browser to produce a PDF. Returns { html: string; pdf: Uint8Array }. Validation throws an Error whose cause is a ZodError — pretty-print with z.prettifyError(cause).
Node.js
import { launch } from "chrome-launcher";
import puppeteer from "puppeteer-core";
import { generate } from "cvexp";
const chrome = await launch({ chromeFlags: ["--headless=new"] });
const { webSocketDebuggerUrl } = await fetch(`http://127.0.0.1:${chrome.port}/json/version`).then((r) => r.json());
const browser = await puppeteer.connect({ browserWSEndpoint: webSocketDebuggerUrl });
const { html, pdf } = await generate(resumeData, browser);
await browser.disconnect();
chrome.kill();Cloudflare Workers
import puppeteer from "@cloudflare/puppeteer";
import { generate } from "cvexp";
const browser = await puppeteer.launch(env.BROWSER);
const { html, pdf } = await generate(resumeData, browser);
await browser.close();
return new Response(pdf, { headers: { "Content-Type": "application/pdf" } });HTML only (no browser required)
import { renderResumeToHTML } from "cvexp";
const html = renderResumeToHTML(resumeData);A complete sample lives in examples/resume.json.
Schema
Add a $schema reference to your resume JSON for IDE autocomplete:
{
"$schema": "./node_modules/cvexp/dist/cvexp.schema.json",
"basics": { "name": "Jane Doe" },
"summary": "...",
"experience": { "items": [] }
}Top-level keys: basics, summary, experience, education, skills, languages. Every field has a default — {} is valid and renders a blank page. Full field-level descriptions in the published cvexp.schema.json.
Styling
One template. Recolour by editing a single token in src/styles/tailwind.css:
@theme {
--color-accent: #3b82f6;
}No theme-switcher, no template gallery — by design.
Development
pnpm install
pnpm generate # tests/sp.json → output/ (requires local Chrome)
pnpm verify # format:check + lint + typecheck + knip + build + publint + attw + test + smokeReleases
Automated via semantic-release. Push a Conventional Commit to main and a release ships itself — feat: bumps minor, fix:/refactor:/perf:/build: bump patch, BREAKING CHANGE: bumps major. chore:/test:/style:/ci: produce no release. Never bump the version manually.
License
MIT — see LICENSE.
