resumefy
Version:
A simple toolkit to bring your JSON Resume to life
141 lines (140 loc) • 4.46 kB
JavaScript
import { bright, underline } from 'ansicolor';
import { readFile } from 'fs/promises';
import path from 'path';
import { render as resumedRender } from 'resumed';
import { ResumeBrowser } from '../browser/index.js';
import { getFilename, loadTheme } from './utils.js';
import { log } from '../cli/log.js';
import { validateObject } from '../validate/validate.js';
import { startServer } from '../browser/server.js';
/**
* Renderer class to render resume in browser and save PDF and HTML files.
*/
export class Renderer {
#resumeFile;
#filename = 'resume';
#options;
#resume = {};
#resumeHtml = '';
#browser;
#cliOptions;
#themeModule;
constructor(resumeFile, { theme, outDir = '.' }, browser, cliOptions) {
this.#resumeFile = resumeFile;
this.#filename = getFilename(this.#resumeFile);
this.#options = { theme, outDir };
this.#browser = browser;
this.#cliOptions = cliOptions;
}
static async launch(resumeFile, { theme, outDir = '.' }, options, cliOptions) {
const browser = await ResumeBrowser.launch(options);
return new Renderer(resumeFile, { theme, outDir }, browser, cliOptions);
}
/**
* Parse resume JSON file
* @param log Optional log function
*/
async #parse(log = () => { }) {
log('📁 ', `Loading ${underline(this.#resumeFile)}`);
this.#resume = JSON.parse(await readFile(this.#resumeFile, 'utf-8'));
}
/**
* Validate resume JSON
* @param log Optional log function
*/
async #validate(log = () => { }) {
log('🔎 ', 'Validating resume');
await validateObject(this.#resume);
}
/**
* Load theme module
* @param log Optional log function
*/
async #loadTheme(log = () => { }) {
log('✨ ', 'Loading theme');
this.#themeModule = await loadTheme(this.#options.theme, this.#resume);
}
/**
* Generate HTML from resume JSON
* @param log Optional log function
*/
async #generateHtml(log = () => { }) {
log('📎 ', 'Rendering resume');
this.#resumeHtml = await resumedRender(this.#resume, this.#themeModule);
}
/**
* Render browser page
* @param log Optional log function
*/
async #renderPage(log = () => { }) {
log('🌐 ', 'Rendering browser page');
await this.#browser.render(this.#resumeHtml);
}
/**
* Write HTML and PDF files
* @param log Optional log function
*/
async #writeFiles(log = () => { }) {
log('💾 ', 'Writing files');
await this.#browser.writeFiles(this.#options.outDir, this.#filename);
}
/**
* Render resume in browser and save PDF and HTML files.
*/
async render() {
const steps = [
this.#parse.bind(this),
this.#validate.bind(this),
this.#loadTheme.bind(this),
this.#generateHtml.bind(this),
this.#renderPage.bind(this),
this.#writeFiles.bind(this),
];
await steps
.reduce(async (prev, step, i) => {
const logFn = this.#cliOptions ? log.step(i + 1, steps.length) : undefined;
return prev.then(() => step(logFn));
}, Promise.resolve())
.catch((err) => {
this.#browser.error(err);
throw err;
});
if (this.#cliOptions) {
log.success('Resume rendered successfully 🎉');
log.log('Files written:');
const files = ['html', 'pdf'];
files.forEach((ext) => {
log.log(`- ${bright(ext)}\t`, underline(path.resolve(path.join(this.#options.outDir, `${this.#filename}.${ext}`))));
});
}
if (!this.#cliOptions || !this.#cliOptions?.watch) {
await this.close();
}
}
/**
* Start file server for the output directory
*/
startFileServer(port = 8080) {
startServer(path.resolve(this.#options.outDir), port);
}
/**
* Add menu to browser
*/
async addMenu(serverUrl) {
if (this.#cliOptions?.watch) {
await this.#browser.addMenu(`${serverUrl}/${this.#filename}.pdf`);
}
}
/**
* Reload preview
*/
async reloadPreview() {
await this.#browser.reloadPreview();
}
/**
* Close browser
*/
async close() {
await this.#browser.close();
}
}