Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
12 changes: 10 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ type RenderOptions = {
theme?: string
}

type ExportOptions = RenderOptions & {
'puppeteer-arg'?: string | string[]
}

enum OutputFormat {
Html = 'html',
Pdf = 'pdf',
Expand Down Expand Up @@ -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(
Expand Down
14 changes: 9 additions & 5 deletions src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down
43 changes: 30 additions & 13 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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(
Expand All @@ -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', () => {
Expand Down
15 changes: 14 additions & 1 deletion test/pdf.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as puppeteer from 'puppeteer'
import { expect, it, vi } from 'vitest'
import { pdf } from '../src/pdf.js'

Expand All @@ -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 () => {
Expand All @@ -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?')
})