A few months ago I decided to refresh my old CV made with Pages to something more personal. I decided that, since I'm a developer, it would be fun if my CV was generated from code. As many of my side projects go, I started with a simple idea, end I ended up with a riduculous overengineered solution that lead me to publishing the curriculum as an NPM package.
If you want to skip the story and see the result, run the following command:
npx @fibonacid/curriculum@latest
# pnpm dlx @fibonacid/curriculum@latest
If you just want to read the CV, you can see the web version here.
Writing
The CV is written inside the README.md file of the project.
It's a simple markdown file that contains the usual sections of a CV: personal info, education, work experience, skills, etc.
# Lorenzo Rivosecchi
I'm a Web Developer with 5 years of experience in the field. I'm currently working at [Assist Digital](https://assistdigital.com) as a Frontend Developer.
I have a background in Computer Music and my passions are music, technology and design.
[![Github](https://img.shields.io/badge/Github-black?logo=github)](https://github.com/fibonacid)
[![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?logo=linkedin)](https://www.linkedin.com/in/lorenzo-rivosecchi/)
[![Blog](https://img.shields.io/badge/Blog-black?logo=devdotto)](https://dev.to/fibonacid)
[![X (formerly Twitter)](https://img.shields.io/twitter/follow/fibonacid)](https://twitter.com/fibonacid)
Rendering
To render the file I used unified, with the following plugins:
import rehypeFormat from "rehype-format";
import rehypeStringify from "rehype-stringify";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import remarkGfm from "remark-gfm";
const processor = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypeDocument)
.use(rehypeFormat)
.use(rehypeStringify);
The README.md is read and processed, then written to a file inside the dist
folder.
import { readFile, writeFile } from "node:fs/promises";
// Read the file and process it
const input = await readFile("README.md");
const file = await processor.process(input);
// Write the file to disk
const output = String(file);
await writeFile("dist/index.html", output);
PDF Generation
Since most job applications require a PDF version of the CV, I decided to use Puppeteer to generate it.
import puppeteer from "puppeteer";
// Launch the headless browser and open a new page
const browser = await puppeteer.launch({
headless: "new",
});
const page = await browser.newPage();
// Navigate to the generated HTML file and generate the PDF
await page.goto(`file://${process.cwd()}/dist/index.html`);
await page.pdf({
path: "./dist/Curriculum.pdf",
format: "A4",
});
Now i can execute the build
script to get the PDF version:
{
"scripts": {
"build": "node build.js"
}
}
To make sure that the PDF is always up to date, I added a pre-commit hook that runs the build script before every commit.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn build
Styling
Since the README
looked pretty good on Github, I decided to make my CV look exactly like that. Thankfully I found a package called generate-github-markdown-css that made it easy to grab the original CSS from Github and use it in my project.
import githubMarkdownCss from 'generate-github-markdown-css';
import fs from "node:fs/promises";
async function main() {
const css = await githubMarkdownCss({
rootSelector = 'body',
});
await fs.writeFile("./assets/style.css", css);
}
To use the CSS in my HTML i just need to add it to the head of the document using rehype-document
:
const processor = unified()
// other plugins
.use(rehypeDocument, {
css: "./assets/style.css",
style,
})
Publishing
Since this CV will be distributed as a PDF, I need an automatic way to build and publish it somewhere for easy access. My first idea was to use Github Actions to build the pdf and upload it to a Google Drive folder, but the GDrive API is a bit of a pain to use, so I decided to publish it on NPM instead.
At this point I thought the work was almost done. My original idea was to let people download my CV using this commad:
npm install @fibonacid/curriculum
And include a postinstall script that would open the PDF in the browser:
{
"scripts": {
"postinstall": "open node_modules/@fibonacid/curriculum/dist/Curriculum.pdf"
}
}
This doesn't work for some reason, therefore I decided to create a simple CLI script that would ask the user where they want to store the file and if they want to open it immediately.
const answers = await inquirer.prompt([
{
type: "input",
name: "folder",
message: "Where should I save the file?",
},
{
type: "input",
name: "filename",
message: "How should I name the file?",
},
{
type: "confirm",
name: "open",
message: "Do you want to read it now?",
},
]);
To expose the CLI I just needed to add a bin
field to the package.json
file:
{
"bin": "./cli.js"
}
And that's it! Now I can publish my CV on NPM and let people download it with a simple command.
npx @fibonacid/curriculum
Need to install the following packages:
@fibonacid/curriculum
Ok to proceed? (y) y
? Where should I save the file? /Users/lorenzo/Downloads
? How should I name the file? Curriculum.pdf
? Do you want to open the file? (Y/n) y
Conclusion
This project was a lot of fun to build and I learned a lot of new things. I'm excited to build more stuff with Markdown and unified
in the future. If you want to check out the full code, you can find it here
Top comments (0)