diff --git a/README.md b/README.md index f6fd80e..c0eabb0 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Export resume to PDF. - `-o, --output`: Output filename - `-t, --theme`: Theme to use +- `--puppeteer-arg`: Puppeteer launch argument - `-h, --help`: Displays help message ### `init` diff --git a/src/cli.ts b/src/cli.ts index 355cf8d..9b19a56 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,6 +11,10 @@ type RenderOptions = { theme?: string } +type ExportOptions = RenderOptions & { + 'puppeteer-arg'?: string | string[] +} + enum OutputFormat { Html = 'html', Pdf = 'pdf', @@ -76,18 +80,22 @@ cli .command('export [filename]', 'Export resume to PDF') .option('-o, --output', 'Output filename') .option('-t, --theme', 'Theme to use') + .option('--puppeteer-arg', 'Puppeteer launch argument') .action( async ( filename: string = DEFAULT_FILENAME, { output = getOutputFilename(filename, OutputFormat.Pdf), theme, - }: RenderOptions, + ...opts + }: ExportOptions, ) => { const resume = await getResume(filename) const themeModule = await getThemeModule(resume, theme) const rendered = await render(resume, themeModule) - const exported = await pdf(rendered, resume, themeModule) + const exported = await pdf(rendered, resume, themeModule, { + args: [opts['puppeteer-arg'] ?? []].flat(), + }) await writeFile(output, exported) console.log( diff --git a/src/pdf.ts b/src/pdf.ts index f19b3e6..de2d99c 100644 --- a/src/pdf.ts +++ b/src/pdf.ts @@ -2,23 +2,27 @@ import { styleText } from 'node:util' import type { PuppeteerNode } from 'puppeteer' import type { Resume, Theme } from './types.js' +type PuppeteerOptions = { + moduleName?: string + args?: string[] +} + export const pdf = async ( html: string, resume: Resume, themeModule: Theme, - pptrModuleName = 'puppeteer', + { moduleName = 'puppeteer', args = [] }: PuppeteerOptions = {}, ) => { let puppeteer: PuppeteerNode try { - puppeteer = await import(pptrModuleName) + puppeteer = await import(moduleName) } catch { throw new Error( - `Could not import ${styleText('yellow', pptrModuleName)} package. Is it installed?`, + `Could not import ${styleText('yellow', moduleName)} package. Is it installed?`, ) } - - const browser = await puppeteer.launch() + const browser = await puppeteer.launch({ args }) const page = await browser.newPage() await page.setContent(html, { waitUntil: 'networkidle0' }) diff --git a/test/cli.test.ts b/test/cli.test.ts index 2f693be..4987156 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -219,18 +219,7 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme) - - expect(writeFile).toHaveBeenCalledTimes(1) - expect(writeFile).toHaveBeenCalledWith( - 'resume.pdf', - new TextEncoder().encode('pdf'), - ) - - expect(logSpy).toHaveBeenCalledTimes(1) - expect(logSpy.mock.calls.join('\n')).toMatchInlineSnapshot( - `"You can find your exported resume at resume.pdf. Nice work! 🚀"`, - ) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, { args: [] }) }) it('exports a resume with custom output', async () => { @@ -257,7 +246,7 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, { args: [] }) expect(writeFile).toHaveBeenCalledTimes(1) expect(writeFile).toHaveBeenCalledWith( @@ -270,6 +259,34 @@ describe('export', () => { `"You can find your exported resume at custom-output.pdf. Nice work! 🚀"`, ) }) + + it('exports a resume with custom Puppeteer args', async () => { + const resume = {} + + vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(resume)) + vi.mocked(render).mockResolvedValueOnce('rendered') + vi.mocked(pdf).mockResolvedValueOnce(new TextEncoder().encode('pdf')) + + await cli.parse([ + '', + '', + 'export', + '--theme', + 'jsonresume-theme-even', + '--puppeteer-arg=--no-sandbox', + ]) + + expect(readFile).toHaveBeenCalledTimes(1) + expect(readFile).toHaveBeenCalledWith('resume.json', 'utf-8') + + expect(render).toHaveBeenCalledTimes(1) + expect(render).toHaveBeenCalledWith(resume, theme) + + expect(pdf).toHaveBeenCalledTimes(1) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, { + args: ['--no-sandbox'], + }) + }) }) describe('validate', () => { diff --git a/test/pdf.test.ts b/test/pdf.test.ts index 8a9ec97..1fc7df2 100644 --- a/test/pdf.test.ts +++ b/test/pdf.test.ts @@ -1,3 +1,4 @@ +import * as puppeteer from 'puppeteer' import { expect, it, vi } from 'vitest' import { pdf } from '../src/pdf.js' @@ -18,6 +19,18 @@ it('exports a resume to PDF', async () => { } await expect(pdf('html', resume, theme)).resolves.toBe('pdf') + expect(puppeteer.launch).toHaveBeenCalledWith({ args: [] }) +}) + +it('exports a resume to PDF with custom Puppeteer args', async () => { + const resume = require('@jsonresume/schema/sample.resume.json') + const theme = { + render: vi.fn(({ basics: { name } }) => name), + } + const args = ['--no-sandbox'] + + await expect(pdf('html', resume, theme, { args })).resolves.toBe('pdf') + expect(puppeteer.launch).toHaveBeenCalledWith({ args }) }) it('asks if Puppeteer package is installed if importing fails', async () => { @@ -27,6 +40,6 @@ it('asks if Puppeteer package is installed if importing fails', async () => { } await expect(() => - pdf('html', resume, theme, 'non-puppeteer'), + pdf('html', resume, theme, { moduleName: 'non-puppeteer' }), ).rejects.toThrow('Could not import non-puppeteer package. Is it installed?') })