From d53a8980109cb3069cb93da8ccaa929606d77575 Mon Sep 17 00:00:00 2001 From: Will Young Date: Sat, 10 May 2025 04:22:33 +0100 Subject: [PATCH 1/6] Add new argument --nosandbox for gitlab CI compatability --- src/cli.ts | 6 +++++- src/pdf.ts | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 1db89dc..ef0f0ad 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,6 +9,7 @@ import type { Resume, Theme } from './types.js' type RenderOptions = { output?: string theme?: string + nosandbox: boolean } enum OutputFormat { @@ -76,18 +77,21 @@ cli .command('export [filename]', 'Export resume to PDF') .option('-o, --output', 'Output filename') .option('-t, --theme', 'Theme to use') + .option('-n, --nosandbox', 'Launch puppeteer with --no-sandbox') .action( async ( filename: string = DEFAULT_FILENAME, { output = getOutputFilename(filename, OutputFormat.Pdf), theme, + nosandbox, }: RenderOptions, ) => { const resume = await getResume(filename) const themeModule = await getThemeModule(resume, theme) const rendered = await render(resume, themeModule) - const exported = await pdf(rendered, resume, themeModule) + console.log('nosandbox is ' + nosandbox) + const exported = await pdf(rendered, resume, themeModule, nosandbox) await writeFile(output, exported) console.log( diff --git a/src/pdf.ts b/src/pdf.ts index b784561..cb5788f 100644 --- a/src/pdf.ts +++ b/src/pdf.ts @@ -6,6 +6,7 @@ export const pdf = async ( html: string, resume: Resume, themeModule: Theme, + noSandbox: boolean, pptrModuleName = 'puppeteer', ) => { let puppeteer: PuppeteerNode @@ -17,8 +18,9 @@ export const pdf = async ( `Could not import ${yellow(pptrModuleName)} package. Is it installed?`, ) } - - const browser = await puppeteer.launch() + console.log('noSandbox is ' + noSandbox) + const puppeteerLaunchArgs = noSandbox ? { args: ['--no-sandbox'] } : {} + const browser = await puppeteer.launch(puppeteerLaunchArgs) const page = await browser.newPage() await page.setContent(html, { waitUntil: 'networkidle0' }) From 1a7e19a1c4748157b12b03f00d07ef563a5b1075 Mon Sep 17 00:00:00 2001 From: Will Young Date: Sat, 10 May 2025 05:02:41 +0100 Subject: [PATCH 2/6] Fix/add unit tests --- src/cli.ts | 1 - src/pdf.ts | 3 +-- test/cli.test.ts | 41 +++++++++++++++++++++++++++++++++++++++-- test/pdf.test.ts | 15 ++++++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index ef0f0ad..01d859c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -90,7 +90,6 @@ cli const resume = await getResume(filename) const themeModule = await getThemeModule(resume, theme) const rendered = await render(resume, themeModule) - console.log('nosandbox is ' + nosandbox) const exported = await pdf(rendered, resume, themeModule, nosandbox) await writeFile(output, exported) diff --git a/src/pdf.ts b/src/pdf.ts index cb5788f..324e426 100644 --- a/src/pdf.ts +++ b/src/pdf.ts @@ -6,7 +6,7 @@ export const pdf = async ( html: string, resume: Resume, themeModule: Theme, - noSandbox: boolean, + noSandbox?: boolean, pptrModuleName = 'puppeteer', ) => { let puppeteer: PuppeteerNode @@ -18,7 +18,6 @@ export const pdf = async ( `Could not import ${yellow(pptrModuleName)} package. Is it installed?`, ) } - console.log('noSandbox is ' + noSandbox) const puppeteerLaunchArgs = noSandbox ? { args: ['--no-sandbox'] } : {} const browser = await puppeteer.launch(puppeteerLaunchArgs) const page = await browser.newPage() diff --git a/test/cli.test.ts b/test/cli.test.ts index 2f693be..14bd9bd 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -219,7 +219,44 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, undefined) + + 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! 🚀"`, + ) + }) + + it('exports a resume with default filename and nosandbox passed as an argument', 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', + '-n', + ]) + + 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, true) expect(writeFile).toHaveBeenCalledTimes(1) expect(writeFile).toHaveBeenCalledWith( @@ -257,7 +294,7 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, undefined) expect(writeFile).toHaveBeenCalledTimes(1) expect(writeFile).toHaveBeenCalledWith( diff --git a/test/pdf.test.ts b/test/pdf.test.ts index 8a9ec97..13cab34 100644 --- a/test/pdf.test.ts +++ b/test/pdf.test.ts @@ -11,13 +11,22 @@ vi.mock('puppeteer', () => ({ }), })) -it('exports a resume to PDF', async () => { +it('exports a resume to PDF with no-sandbox', async () => { const resume = require('@jsonresume/schema/sample.resume.json') const theme = { render: vi.fn(({ basics: { name } }) => name), } - await expect(pdf('html', resume, theme)).resolves.toBe('pdf') + await expect(pdf('html', resume, theme, true)).resolves.toBe('pdf') +}) + +it('exports a resume to PDF without no-sandbox', async () => { + const resume = require('@jsonresume/schema/sample.resume.json') + const theme = { + render: vi.fn(({ basics: { name } }) => name), + } + + await expect(pdf('html', resume, theme, undefined)).resolves.toBe('pdf') }) it('asks if Puppeteer package is installed if importing fails', async () => { @@ -27,6 +36,6 @@ it('asks if Puppeteer package is installed if importing fails', async () => { } await expect(() => - pdf('html', resume, theme, 'non-puppeteer'), + pdf('html', resume, theme, true, 'non-puppeteer'), ).rejects.toThrow('Could not import non-puppeteer package. Is it installed?') }) From 7e0104ed00a44999cf32296fac1e0bc69a22eb2d Mon Sep 17 00:00:00 2001 From: Will Young Date: Sat, 10 May 2025 05:05:47 +0100 Subject: [PATCH 3/6] Update to fix vulnerability --- package-lock.json | 57 ++++++++++++++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 835ff8f..0cdb59c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "puppeteer": "24.6.0", "tslib": "2.8.1", "typescript": "5.8.3", - "vite": "6.2.5", + "vite": "^6.3.5", "vite-plugin-dts": "4.5.3", "vitest": "3.1.1" }, @@ -4882,13 +4882,12 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", "dev": true, - "license": "MIT", "dependencies": { - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2" }, "engines": { @@ -4899,11 +4898,10 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", "dev": true, - "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -4918,7 +4916,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "license": "MIT", "engines": { "node": ">=12" }, @@ -5032,15 +5029,17 @@ } }, "node_modules/vite": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", - "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" @@ -5153,6 +5152,32 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vitest": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", diff --git a/package.json b/package.json index 5ca760d..f5346dc 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "puppeteer": "24.6.0", "tslib": "2.8.1", "typescript": "5.8.3", - "vite": "6.2.5", + "vite": "^6.3.5", "vite-plugin-dts": "4.5.3", "vitest": "3.1.1" }, From 8fd8698ca19124375d830bb7e37ac6c68afd5678 Mon Sep 17 00:00:00 2001 From: Rafael Bardini Date: Thu, 18 Sep 2025 03:01:22 +0200 Subject: [PATCH 4/6] Revert "Update to fix vulnerability" This reverts commit 7e0104ed00a44999cf32296fac1e0bc69a22eb2d. --- package-lock.json | 57 +++++++++++++---------------------------------- package.json | 2 +- 2 files changed, 17 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0cdb59c..835ff8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "puppeteer": "24.6.0", "tslib": "2.8.1", "typescript": "5.8.3", - "vite": "^6.3.5", + "vite": "6.2.5", "vite-plugin-dts": "4.5.3", "vitest": "3.1.1" }, @@ -4882,12 +4882,13 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.4", + "fdir": "^6.4.3", "picomatch": "^4.0.2" }, "engines": { @@ -4898,10 +4899,11 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", "dev": true, + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -4916,6 +4918,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -5029,17 +5032,15 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" @@ -5152,32 +5153,6 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", diff --git a/package.json b/package.json index f5346dc..5ca760d 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "puppeteer": "24.6.0", "tslib": "2.8.1", "typescript": "5.8.3", - "vite": "^6.3.5", + "vite": "6.2.5", "vite-plugin-dts": "4.5.3", "vitest": "3.1.1" }, From f6ecb70b8860c08eb815f52e6984e19a0ed4d26b Mon Sep 17 00:00:00 2001 From: Rafael Bardini Date: Fri, 19 Sep 2025 03:15:35 +0200 Subject: [PATCH 5/6] Replace `--nosandbox` with `--puppeteer-arg` --- src/cli.ts | 15 ++++++++++----- src/pdf.ts | 15 +++++++++------ test/cli.test.ts | 44 ++++++++++++-------------------------------- test/pdf.test.ts | 14 +++++++++----- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 01d859c..6f013da 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,7 +9,10 @@ import type { Resume, Theme } from './types.js' type RenderOptions = { output?: string theme?: string - nosandbox: boolean +} + +type ExportOptions = RenderOptions & { + 'puppeteer-arg'?: string | string[] } enum OutputFormat { @@ -77,20 +80,22 @@ cli .command('export [filename]', 'Export resume to PDF') .option('-o, --output', 'Output filename') .option('-t, --theme', 'Theme to use') - .option('-n, --nosandbox', 'Launch puppeteer with --no-sandbox') + .option('--puppeteer-arg', 'Puppeteer launch argument') .action( async ( filename: string = DEFAULT_FILENAME, { output = getOutputFilename(filename, OutputFormat.Pdf), theme, - nosandbox, - }: 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, nosandbox) + 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 324e426..e7d03bd 100644 --- a/src/pdf.ts +++ b/src/pdf.ts @@ -2,24 +2,27 @@ import type { PuppeteerNode } from 'puppeteer' import { yellow } from 'yoctocolors' import type { Resume, Theme } from './types.js' +type PuppeteerOptions = { + moduleName?: string + args?: string[] +} + export const pdf = async ( html: string, resume: Resume, themeModule: Theme, - noSandbox?: boolean, - 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 ${yellow(pptrModuleName)} package. Is it installed?`, + `Could not import ${yellow(moduleName)} package. Is it installed?`, ) } - const puppeteerLaunchArgs = noSandbox ? { args: ['--no-sandbox'] } : {} - const browser = await puppeteer.launch(puppeteerLaunchArgs) + 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 14bd9bd..4987156 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -219,21 +219,10 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, undefined) - - 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 default filename and nosandbox passed as an argument', async () => { + it('exports a resume with custom output', async () => { const resume = {} vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(resume)) @@ -246,7 +235,8 @@ describe('export', () => { 'export', '--theme', 'jsonresume-theme-even', - '-n', + '--output', + 'custom-output.pdf', ]) expect(readFile).toHaveBeenCalledTimes(1) @@ -256,21 +246,21 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, true) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, { args: [] }) expect(writeFile).toHaveBeenCalledTimes(1) expect(writeFile).toHaveBeenCalledWith( - 'resume.pdf', + 'custom-output.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! 🚀"`, + `"You can find your exported resume at custom-output.pdf. Nice work! 🚀"`, ) }) - it('exports a resume with custom output', async () => { + it('exports a resume with custom Puppeteer args', async () => { const resume = {} vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(resume)) @@ -283,8 +273,7 @@ describe('export', () => { 'export', '--theme', 'jsonresume-theme-even', - '--output', - 'custom-output.pdf', + '--puppeteer-arg=--no-sandbox', ]) expect(readFile).toHaveBeenCalledTimes(1) @@ -294,18 +283,9 @@ describe('export', () => { expect(render).toHaveBeenCalledWith(resume, theme) expect(pdf).toHaveBeenCalledTimes(1) - expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, undefined) - - expect(writeFile).toHaveBeenCalledTimes(1) - expect(writeFile).toHaveBeenCalledWith( - 'custom-output.pdf', - new TextEncoder().encode('pdf'), - ) - - expect(logSpy).toHaveBeenCalledTimes(1) - expect(logSpy.mock.calls.join('\n')).toMatchInlineSnapshot( - `"You can find your exported resume at custom-output.pdf. Nice work! 🚀"`, - ) + expect(pdf).toHaveBeenCalledWith('rendered', resume, theme, { + args: ['--no-sandbox'], + }) }) }) diff --git a/test/pdf.test.ts b/test/pdf.test.ts index 13cab34..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' @@ -11,22 +12,25 @@ vi.mock('puppeteer', () => ({ }), })) -it('exports a resume to PDF with no-sandbox', async () => { +it('exports a resume to PDF', async () => { const resume = require('@jsonresume/schema/sample.resume.json') const theme = { render: vi.fn(({ basics: { name } }) => name), } - await expect(pdf('html', resume, theme, true)).resolves.toBe('pdf') + await expect(pdf('html', resume, theme)).resolves.toBe('pdf') + expect(puppeteer.launch).toHaveBeenCalledWith({ args: [] }) }) -it('exports a resume to PDF without no-sandbox', async () => { +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, undefined)).resolves.toBe('pdf') + 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 () => { @@ -36,6 +40,6 @@ it('asks if Puppeteer package is installed if importing fails', async () => { } await expect(() => - pdf('html', resume, theme, true, 'non-puppeteer'), + pdf('html', resume, theme, { moduleName: 'non-puppeteer' }), ).rejects.toThrow('Could not import non-puppeteer package. Is it installed?') }) From 2300645c441920e0c2ed874ee85b86a2638545d8 Mon Sep 17 00:00:00 2001 From: Rafael Bardini Date: Fri, 19 Sep 2025 23:09:17 +0200 Subject: [PATCH 6/6] Update README --- README.md | 1 + 1 file changed, 1 insertion(+) 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`