@thegetty/quire-cli 1.0.0-rc.35 → 1.0.0-rc.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +9 -0
- package/bin/cli.js +19 -1
- package/package.json +21 -8
- package/patches/README.md +19 -0
- package/patches/install-npm-version+1.0.9.patch +12119 -0
- package/schemas/objects.schema.json +12 -38
- package/schemas/publication.schema.json +0 -22
- package/schemas/references.schema.json +0 -1
- package/src/Command.js +26 -6
- package/src/Command.spec.js +99 -0
- package/src/commands/README.md +213 -122
- package/src/commands/build.js +46 -37
- package/src/commands/build.spec.js +113 -0
- package/src/commands/build.test.js +402 -0
- package/src/commands/clean.js +25 -19
- package/src/commands/clean.spec.js +108 -0
- package/src/commands/clean.test.js +260 -0
- package/src/commands/config.js +251 -0
- package/src/commands/config.spec.js +107 -0
- package/src/commands/config.test.js +715 -0
- package/src/commands/create.js +42 -14
- package/src/commands/create.spec.js +112 -0
- package/src/commands/create.test.js +415 -0
- package/src/commands/epub.js +59 -30
- package/src/commands/epub.spec.js +114 -0
- package/src/commands/epub.test.js +503 -0
- package/src/commands/index.js +9 -2
- package/src/commands/info.js +18 -24
- package/src/commands/info.spec.js +64 -0
- package/src/commands/info.test.js +415 -0
- package/src/commands/pdf.js +58 -84
- package/src/commands/pdf.spec.js +114 -0
- package/src/commands/pdf.test.js +464 -0
- package/src/commands/preview.js +26 -31
- package/src/commands/preview.spec.js +97 -0
- package/src/commands/preview.test.js +250 -0
- package/src/commands/use.js +56 -0
- package/src/commands/use.spec.js +61 -0
- package/src/commands/use.test.js +280 -0
- package/src/commands/validate.js +31 -19
- package/src/commands/validate.spec.js +82 -0
- package/src/commands/validate.test.js +234 -0
- package/src/commands/workflows.js +70 -0
- package/src/errors/build/build-failed-error.js +19 -0
- package/src/errors/build/config-field-missing-error.js +20 -0
- package/src/errors/build/config-file-not-found-error.js +20 -0
- package/src/errors/build/index.js +11 -0
- package/src/errors/index.js +48 -0
- package/src/errors/install/dependency-install-error.js +19 -0
- package/src/errors/install/directory-not-empty-error.js +25 -0
- package/src/errors/install/index.js +12 -0
- package/src/errors/install/invalid-path-error.js +31 -0
- package/src/errors/install/invalid-starter-error.js +27 -0
- package/src/errors/install/version-not-found-error.js +35 -0
- package/src/errors/output/epub-generation-error.js +19 -0
- package/src/errors/output/index.js +14 -0
- package/src/errors/output/invalid-epub-library-error.js +20 -0
- package/src/errors/output/invalid-pdf-library-error.js +20 -0
- package/src/errors/output/missing-build-output-error.js +21 -0
- package/src/errors/output/pdf-generation-error.js +24 -0
- package/src/errors/output/tool-not-found-error.js +37 -0
- package/src/errors/project/index.js +10 -0
- package/src/errors/project/not-in-project-error.js +20 -0
- package/src/errors/project/project-create-error.js +21 -0
- package/src/errors/quire-error.js +27 -0
- package/src/errors/validation/validation-error.js +20 -11
- package/src/helpers/clean.js +1 -1
- package/src/helpers/docs-url.js +32 -0
- package/src/helpers/test-cwd.js +5 -6
- package/src/helpers/test-cwd.test.js +192 -0
- package/src/helpers/which.js +10 -4
- package/src/lib/11ty/README.md +135 -19
- package/src/lib/11ty/api.js +176 -93
- package/src/lib/11ty/cli.js +77 -35
- package/src/lib/11ty/index.js +64 -5
- package/src/lib/11ty/index.test.js +655 -0
- package/src/lib/README.md +275 -0
- package/src/lib/commander/index.js +100 -0
- package/src/lib/commander/index.test.js +86 -0
- package/src/lib/commander/options.js +195 -0
- package/src/lib/commander/options.test.js +109 -0
- package/src/lib/conf/README.md +84 -73
- package/src/lib/conf/config.js +5 -3
- package/src/lib/conf/config.test.js +281 -0
- package/src/lib/conf/defaults.js +44 -0
- package/src/lib/conf/format.js +60 -0
- package/src/lib/conf/format.test.js +106 -0
- package/src/lib/conf/helpers.js +91 -0
- package/src/lib/conf/helpers.test.js +136 -0
- package/src/lib/conf/index.js +22 -0
- package/src/lib/conf/schema.js +53 -8
- package/src/lib/epub/README.md +133 -2
- package/src/lib/epub/engines.js +46 -0
- package/src/lib/epub/epub.js +36 -11
- package/src/lib/epub/index.js +111 -21
- package/src/lib/epub/index.test.js +518 -0
- package/src/lib/epub/pandoc.js +33 -4
- package/src/lib/epub/pandoc.test.js +122 -0
- package/src/lib/epub/schema.js +21 -0
- package/src/lib/error/README.md +170 -0
- package/src/lib/error/handler.js +107 -0
- package/src/lib/git/README.md +151 -2
- package/src/lib/git/index.js +217 -13
- package/src/lib/git/index.spec.js +80 -0
- package/src/lib/git/index.test.js +453 -0
- package/src/lib/installer/index.js +309 -0
- package/src/lib/installer/index.spec.js +83 -0
- package/src/lib/installer/index.test.js +545 -0
- package/src/lib/logger/README.md +424 -0
- package/src/lib/logger/debug.js +90 -0
- package/src/lib/logger/debug.spec.js +130 -0
- package/src/lib/logger/index.js +228 -0
- package/src/lib/logger/index.spec.js +131 -0
- package/src/lib/logger/index.test.js +477 -0
- package/src/lib/npm/README.md +127 -0
- package/src/lib/npm/index.js +198 -0
- package/src/lib/npm/index.spec.js +60 -0
- package/src/lib/npm/index.test.js +355 -0
- package/src/lib/pdf/README.md +131 -0
- package/src/lib/pdf/engines.js +46 -0
- package/src/lib/pdf/index.js +124 -21
- package/src/lib/pdf/index.test.js +708 -0
- package/src/lib/pdf/paged.js +100 -52
- package/src/lib/pdf/paged.test.js +366 -0
- package/src/lib/pdf/prince.js +115 -37
- package/src/lib/pdf/prince.test.js +202 -0
- package/src/lib/pdf/schema.js +21 -0
- package/src/lib/pdf/split.js +61 -33
- package/src/lib/pdf/split.test.js +445 -0
- package/src/lib/process/manager.js +110 -0
- package/src/lib/process/manager.test.js +55 -0
- package/src/lib/project/build.js +143 -0
- package/src/lib/project/build.test.js +253 -0
- package/src/lib/project/config.js +48 -0
- package/src/lib/project/config.test.js +134 -0
- package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
- package/src/lib/project/detect.test.js +157 -0
- package/src/lib/project/index.js +40 -0
- package/src/lib/project/paths.js +224 -0
- package/src/lib/project/version.js +110 -0
- package/src/lib/project/version.test.js +350 -0
- package/src/lib/reporter/README.md +211 -2
- package/src/lib/reporter/index.js +512 -0
- package/src/lib/reporter/index.test.js +593 -0
- package/src/main.js +134 -47
- package/src/main.spec.js +61 -0
- package/src/main.test.js +347 -0
- package/src/validators/utils.js +2 -1
- package/src/commands/conf.js +0 -43
- package/src/commands/version.js +0 -43
- package/src/lib/11ty/paths.js +0 -103
- package/src/lib/i18n/README.md +0 -3
- package/src/lib/i18n/config.js +0 -53
- package/src/lib/i18n/index.js +0 -43
- package/src/lib/i18n/localeService.js +0 -58
- package/src/lib/quire/README.md +0 -19
- package/src/lib/quire/index.js +0 -350
package/src/lib/pdf/paged.js
CHANGED
|
@@ -4,25 +4,39 @@ import fs from 'fs-extra'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
|
|
7
|
+
import processManager from '#lib/process/manager.js'
|
|
8
|
+
import reporter from '#lib/reporter/index.js'
|
|
7
9
|
import { splitPdf } from './split.js'
|
|
10
|
+
import { PdfGenerationError } from '#src/errors/index.js'
|
|
11
|
+
import ENGINES from './engines.js'
|
|
12
|
+
import createDebug from '#debug'
|
|
8
13
|
|
|
9
14
|
const __filename = fileURLToPath(import.meta.url)
|
|
10
15
|
const __dirname = path.dirname(__filename)
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
const debug = createDebug('lib:pdf:paged')
|
|
18
|
+
|
|
19
|
+
/** Re-export engine metadata from central registry */
|
|
20
|
+
export const metadata = ENGINES.pagedjs
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
|
-
* A façade module for interacting with Paged.js and pagedjs-cli
|
|
23
|
+
* A façade module for interacting with Paged.js and pagedjs-cli
|
|
16
24
|
* @see https://gitlab.coko.foundation/pagedjs/
|
|
25
|
+
*
|
|
26
|
+
* @param {string} publicationInput - Path to the publication HTML file
|
|
27
|
+
* @param {string} coversInput - Path to the covers HTML file
|
|
28
|
+
* @param {string} pdfPath - Path where the PDF should be written
|
|
29
|
+
* @param {Object} [options={}] - Generation options
|
|
30
|
+
* @returns {Promise<string>} Path to the generated PDF file
|
|
17
31
|
*/
|
|
18
|
-
export default async (publicationInput, coversInput,
|
|
32
|
+
export default async (publicationInput, coversInput, pdfPath, options = {}) => {
|
|
19
33
|
/**
|
|
20
34
|
* Configure the Paged.js Printer options
|
|
21
35
|
* @see https://gitlab.coko.foundation/pagedjs/pagedjs-cli/-/blob/main/src/cli.js
|
|
22
36
|
*/
|
|
23
37
|
|
|
24
38
|
let additionalScripts = []
|
|
25
|
-
|
|
39
|
+
|
|
26
40
|
const { pdfConfig } = options
|
|
27
41
|
|
|
28
42
|
additionalScripts.push( path.join(__dirname, 'pagedPlugin.js') )
|
|
@@ -35,25 +49,35 @@ export default async (publicationInput, coversInput, output, options = {}) => {
|
|
|
35
49
|
additionalScripts,
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
const optionsOutput = JSON.stringify(printerOptions, null, 2)
|
|
40
|
-
console.debug(`[CLI:lib/pdf/pagedjs] Printer options\n${optionsOutput}`)
|
|
41
|
-
}
|
|
52
|
+
debug('printer options: %O', printerOptions)
|
|
42
53
|
|
|
43
54
|
let printer = new Printer(printerOptions)
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
// Register cleanup handler for graceful shutdown
|
|
57
|
+
processManager.onShutdown('pagedjs', () => {
|
|
58
|
+
debug('closing printer')
|
|
59
|
+
printer.close()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
printer.on('page', (page, pageElement, breakToken) => {
|
|
46
63
|
if (page.position === 0) {
|
|
47
|
-
|
|
48
|
-
}
|
|
64
|
+
debug('paged.js loaded')
|
|
65
|
+
}
|
|
49
66
|
})
|
|
50
67
|
|
|
51
68
|
printer.on('rendered', (msg) => {
|
|
52
|
-
|
|
69
|
+
debug('rendered: %s', msg)
|
|
53
70
|
})
|
|
54
71
|
|
|
55
72
|
printer.on('postprocessing', () => {
|
|
56
|
-
|
|
73
|
+
debug('post-processing')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Capture browser console messages for debugging
|
|
77
|
+
printer.on('console', (type, ...args) => {
|
|
78
|
+
if (type === 'warning' || type === 'error') {
|
|
79
|
+
debug('browser %s: %s', type, args.join(' '))
|
|
80
|
+
}
|
|
57
81
|
})
|
|
58
82
|
|
|
59
83
|
/**
|
|
@@ -61,29 +85,31 @@ export default async (publicationInput, coversInput, output, options = {}) => {
|
|
|
61
85
|
* @see
|
|
62
86
|
*
|
|
63
87
|
* @todo verify options before setting them
|
|
64
|
-
* @todo catch errors thrown by the printer
|
|
65
88
|
*/
|
|
66
89
|
const pdfOptions = {
|
|
67
90
|
outlineTags: options.outlineTags || ['h1'],
|
|
68
91
|
}
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
const optionsOutput = JSON.stringify(pdfOptions, null, 2)
|
|
72
|
-
console.debug(`[CLI:lib/pdf/pagedjs] PDF options\n${optionsOutput}`)
|
|
73
|
-
}
|
|
93
|
+
debug('pdf options: %O', pdfOptions)
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
debug('printing %s', publicationInput)
|
|
96
|
+
reporter.update('Rendering PDF...')
|
|
77
97
|
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
let file
|
|
99
|
+
try {
|
|
100
|
+
file = await printer.pdf(publicationInput, pdfOptions)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new PdfGenerationError('Paged.js', 'PDF rendering', error.message)
|
|
103
|
+
}
|
|
80
104
|
|
|
81
|
-
|
|
105
|
+
let pageMap
|
|
82
106
|
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
// Now it's printed, create the pageMap by running JS in the printer's context
|
|
108
|
+
let coversFile
|
|
85
109
|
|
|
86
|
-
|
|
110
|
+
debug('extracting page map')
|
|
111
|
+
reporter.update('Extracting page map...')
|
|
112
|
+
try {
|
|
87
113
|
const pages = await printer.browser.pages()
|
|
88
114
|
|
|
89
115
|
if (pages.length > 0) {
|
|
@@ -92,15 +118,24 @@ export default async (publicationInput, coversInput, output, options = {}) => {
|
|
|
92
118
|
return window.pageMap ?? {} // eslint-disable-line no-undef
|
|
93
119
|
})
|
|
94
120
|
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new PdfGenerationError('Paged.js', 'page map extraction', error.message)
|
|
123
|
+
}
|
|
95
124
|
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
if ( pdfConfig?.pagePDF?.coverPage===true && fs.existsSync(coversInput) ) {
|
|
126
|
+
debug('printing covers %s', coversInput)
|
|
127
|
+
reporter.update('Rendering cover pages...')
|
|
98
128
|
|
|
99
|
-
|
|
129
|
+
const coverPrinter = new Printer(printerOptions)
|
|
100
130
|
|
|
131
|
+
try {
|
|
101
132
|
coversFile = await coverPrinter.pdf(coversInput, pdfOptions)
|
|
102
|
-
|
|
133
|
+
} catch (error) {
|
|
134
|
+
coverPrinter.close()
|
|
135
|
+
throw new PdfGenerationError('Paged.js', 'cover PDF rendering', error.message)
|
|
136
|
+
}
|
|
103
137
|
|
|
138
|
+
try {
|
|
104
139
|
const coverPages = await coverPrinter.browser.pages()
|
|
105
140
|
|
|
106
141
|
if (coverPages.length > 0) {
|
|
@@ -111,40 +146,53 @@ export default async (publicationInput, coversInput, output, options = {}) => {
|
|
|
111
146
|
|
|
112
147
|
Object.values(coversMap).forEach( cov => {
|
|
113
148
|
if (cov.id in pageMap) {
|
|
114
|
-
pageMap[cov.id].coverPage = cov.startPage
|
|
149
|
+
pageMap[cov.id].coverPage = cov.startPage
|
|
115
150
|
}
|
|
116
151
|
})
|
|
117
|
-
|
|
118
152
|
}
|
|
119
|
-
|
|
153
|
+
} catch (error) {
|
|
120
154
|
coverPrinter.close()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Leave the printer open for debug logs
|
|
124
|
-
if (!options.debug) {
|
|
125
|
-
printer.close()
|
|
155
|
+
throw new PdfGenerationError('Paged.js', 'cover page map extraction', error.message)
|
|
126
156
|
}
|
|
127
157
|
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
coverPrinter.close()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Leave the printer open for debug logs
|
|
162
|
+
if (!options.debug) {
|
|
163
|
+
printer.close()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Unregister cleanup handler
|
|
167
|
+
processManager.onShutdownComplete('pagedjs')
|
|
130
168
|
|
|
131
|
-
|
|
132
|
-
|
|
169
|
+
if (file && pdfPath) {
|
|
170
|
+
debug('writing files')
|
|
171
|
+
reporter.update('Writing PDF files...')
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const { dir } = path.parse(pdfPath)
|
|
175
|
+
if (!fs.existsSync(dir)) {
|
|
133
176
|
fs.mkdirsSync(dir)
|
|
134
177
|
}
|
|
135
178
|
|
|
136
|
-
await fs.promises.writeFile(
|
|
137
|
-
|
|
179
|
+
await fs.promises.writeFile(pdfPath, file)
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw new PdfGenerationError('Paged.js', 'PDF file write', error.message)
|
|
182
|
+
}
|
|
138
183
|
|
|
139
|
-
|
|
184
|
+
try {
|
|
185
|
+
const files = await splitPdf(file, coversFile, pageMap, options.pdfConfig)
|
|
140
186
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.
|
|
144
|
-
|
|
187
|
+
await Promise.all(
|
|
188
|
+
Object.entries(files).map(([filePath, pagePdf]) =>
|
|
189
|
+
fs.promises.writeFile(filePath, pagePdf)
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new PdfGenerationError('Paged.js', 'PDF splitting', error.message)
|
|
145
194
|
}
|
|
146
|
-
|
|
147
|
-
} catch (ERR_FILE_NOT_FOUND) {
|
|
148
|
-
console.error(`[CLI:lib/pdf/pagedjs] file not found ${publicationInput}`)
|
|
149
195
|
}
|
|
196
|
+
|
|
197
|
+
return pdfPath
|
|
150
198
|
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import esmock from 'esmock'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import sinon from 'sinon'
|
|
4
|
+
import test from 'ava'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Paged.js PDF Engine Integration Tests
|
|
8
|
+
*
|
|
9
|
+
* Tests error handling paths that are difficult to trigger in E2E tests.
|
|
10
|
+
* The happy path is covered by E2E tests with real Paged.js/Puppeteer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a cross-platform absolute path for testing
|
|
15
|
+
* @param {...string} segments - Path segments to join
|
|
16
|
+
* @returns {string} Platform-appropriate absolute path
|
|
17
|
+
*/
|
|
18
|
+
function testPath(...segments) {
|
|
19
|
+
const root = process.platform === 'win32' ? 'C:\\' : '/'
|
|
20
|
+
return path.join(root, ...segments)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test.beforeEach((t) => {
|
|
24
|
+
t.context.sandbox = sinon.createSandbox()
|
|
25
|
+
|
|
26
|
+
// Mock reporter
|
|
27
|
+
t.context.mockReporter = {
|
|
28
|
+
update: t.context.sandbox.stub(),
|
|
29
|
+
warn: t.context.sandbox.stub()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Mock processManager
|
|
33
|
+
t.context.mockProcessManager = {
|
|
34
|
+
onShutdown: t.context.sandbox.stub(),
|
|
35
|
+
onShutdownComplete: t.context.sandbox.stub()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Mock fs-extra
|
|
39
|
+
t.context.mockFs = {
|
|
40
|
+
existsSync: t.context.sandbox.stub().returns(true),
|
|
41
|
+
mkdirsSync: t.context.sandbox.stub(),
|
|
42
|
+
promises: {
|
|
43
|
+
writeFile: t.context.sandbox.stub().resolves()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test.afterEach.always((t) => {
|
|
49
|
+
t.context.sandbox.restore()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a mock Printer instance
|
|
54
|
+
*/
|
|
55
|
+
function createMockPrinter(sandbox, options = {}) {
|
|
56
|
+
const mockPrinter = {
|
|
57
|
+
on: sandbox.stub(),
|
|
58
|
+
close: sandbox.stub(),
|
|
59
|
+
pdf: options.pdf || sandbox.stub().resolves(Buffer.from([1, 2, 3])),
|
|
60
|
+
browser: {
|
|
61
|
+
pages: options.pages || sandbox.stub().resolves([{
|
|
62
|
+
evaluate: sandbox.stub().resolves({})
|
|
63
|
+
}])
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return mockPrinter
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Error handling tests
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
test('throws PdfGenerationError when PDF rendering fails', async (t) => {
|
|
74
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
75
|
+
|
|
76
|
+
const mockPrinter = createMockPrinter(sandbox, {
|
|
77
|
+
pdf: sandbox.stub().rejects(new Error('Puppeteer crashed: out of memory'))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
81
|
+
|
|
82
|
+
const paged = await esmock('./paged.js', {
|
|
83
|
+
'pagedjs-cli': MockPrinter,
|
|
84
|
+
'fs-extra': mockFs,
|
|
85
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
86
|
+
'#lib/process/manager.js': { default: mockProcessManager }
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const error = await t.throwsAsync(() =>
|
|
90
|
+
paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), {})
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
t.is(error.code, 'PDF_FAILED')
|
|
94
|
+
t.true(error.message.includes('Paged.js'))
|
|
95
|
+
t.true(error.message.includes('PDF rendering'))
|
|
96
|
+
t.true(error.message.includes('out of memory'))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('throws PdfGenerationError when page map extraction fails', async (t) => {
|
|
100
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
101
|
+
|
|
102
|
+
const mockPrinter = createMockPrinter(sandbox, {
|
|
103
|
+
pages: sandbox.stub().rejects(new Error('Browser context destroyed'))
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
107
|
+
|
|
108
|
+
const paged = await esmock('./paged.js', {
|
|
109
|
+
'pagedjs-cli': MockPrinter,
|
|
110
|
+
'fs-extra': mockFs,
|
|
111
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
112
|
+
'#lib/process/manager.js': { default: mockProcessManager }
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const error = await t.throwsAsync(() =>
|
|
116
|
+
paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), {})
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
t.is(error.code, 'PDF_FAILED')
|
|
120
|
+
t.true(error.message.includes('Paged.js'))
|
|
121
|
+
t.true(error.message.includes('page map extraction'))
|
|
122
|
+
t.true(error.message.includes('Browser context destroyed'))
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('throws PdfGenerationError when cover PDF rendering fails', async (t) => {
|
|
126
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
127
|
+
|
|
128
|
+
// Main printer succeeds
|
|
129
|
+
const mainPrinter = createMockPrinter(sandbox)
|
|
130
|
+
|
|
131
|
+
// Cover printer fails
|
|
132
|
+
const coverPrinter = createMockPrinter(sandbox, {
|
|
133
|
+
pdf: sandbox.stub().rejects(new Error('Cover page timeout'))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
let printerCallCount = 0
|
|
137
|
+
const MockPrinter = sandbox.stub().callsFake(() => {
|
|
138
|
+
printerCallCount++
|
|
139
|
+
return printerCallCount === 1 ? mainPrinter : coverPrinter
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const paged = await esmock('./paged.js', {
|
|
143
|
+
'pagedjs-cli': MockPrinter,
|
|
144
|
+
'fs-extra': mockFs,
|
|
145
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
146
|
+
'#lib/process/manager.js': { default: mockProcessManager }
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Enable cover page generation
|
|
150
|
+
const options = {
|
|
151
|
+
pdfConfig: { pagePDF: { coverPage: true } }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const error = await t.throwsAsync(() =>
|
|
155
|
+
paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), options)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
t.is(error.code, 'PDF_FAILED')
|
|
159
|
+
t.true(error.message.includes('Paged.js'))
|
|
160
|
+
t.true(error.message.includes('cover PDF rendering'))
|
|
161
|
+
t.true(error.message.includes('Cover page timeout'))
|
|
162
|
+
// Verify cover printer was closed on error
|
|
163
|
+
t.true(coverPrinter.close.called)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('throws PdfGenerationError when cover page map extraction fails', async (t) => {
|
|
167
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
168
|
+
|
|
169
|
+
// Main printer succeeds
|
|
170
|
+
const mainPrinter = createMockPrinter(sandbox)
|
|
171
|
+
|
|
172
|
+
// Cover printer PDF succeeds but pages() fails
|
|
173
|
+
const coverPrinter = createMockPrinter(sandbox, {
|
|
174
|
+
pages: sandbox.stub().rejects(new Error('Cover browser crashed'))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
let printerCallCount = 0
|
|
178
|
+
const MockPrinter = sandbox.stub().callsFake(() => {
|
|
179
|
+
printerCallCount++
|
|
180
|
+
return printerCallCount === 1 ? mainPrinter : coverPrinter
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
const paged = await esmock('./paged.js', {
|
|
184
|
+
'pagedjs-cli': MockPrinter,
|
|
185
|
+
'fs-extra': mockFs,
|
|
186
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
187
|
+
'#lib/process/manager.js': { default: mockProcessManager }
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const options = {
|
|
191
|
+
pdfConfig: { pagePDF: { coverPage: true } }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const error = await t.throwsAsync(() =>
|
|
195
|
+
paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), options)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
t.is(error.code, 'PDF_FAILED')
|
|
199
|
+
t.true(error.message.includes('Paged.js'))
|
|
200
|
+
t.true(error.message.includes('cover page map extraction'))
|
|
201
|
+
t.true(error.message.includes('Cover browser crashed'))
|
|
202
|
+
t.true(coverPrinter.close.called)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('throws PdfGenerationError when PDF file write fails', async (t) => {
|
|
206
|
+
const { sandbox, mockReporter, mockProcessManager } = t.context
|
|
207
|
+
|
|
208
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
209
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
210
|
+
|
|
211
|
+
const mockFs = {
|
|
212
|
+
existsSync: sandbox.stub().returns(false),
|
|
213
|
+
mkdirsSync: sandbox.stub().throws(new Error('EACCES: permission denied')),
|
|
214
|
+
promises: { writeFile: sandbox.stub() }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const paged = await esmock('./paged.js', {
|
|
218
|
+
'pagedjs-cli': MockPrinter,
|
|
219
|
+
'fs-extra': mockFs,
|
|
220
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
221
|
+
'#lib/process/manager.js': { default: mockProcessManager }
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const error = await t.throwsAsync(() =>
|
|
225
|
+
paged.default(testPath('input.html'), testPath('covers.html'), testPath('restricted', 'output.pdf'), {})
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
t.is(error.code, 'PDF_FAILED')
|
|
229
|
+
t.true(error.message.includes('Paged.js'))
|
|
230
|
+
t.true(error.message.includes('PDF file write'))
|
|
231
|
+
t.true(error.message.includes('permission denied'))
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('closes printer on successful completion when not in debug mode', async (t) => {
|
|
235
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
236
|
+
|
|
237
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
238
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
239
|
+
|
|
240
|
+
const paged = await esmock('./paged.js', {
|
|
241
|
+
'pagedjs-cli': MockPrinter,
|
|
242
|
+
'fs-extra': mockFs,
|
|
243
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
244
|
+
'#lib/process/manager.js': { default: mockProcessManager },
|
|
245
|
+
'./split.js': { splitPdf: sandbox.stub().resolves({}) }
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
await paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), { debug: false })
|
|
249
|
+
|
|
250
|
+
t.true(mockPrinter.close.called)
|
|
251
|
+
t.true(mockProcessManager.onShutdownComplete.calledWith('pagedjs'))
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('keeps printer open when in debug mode', async (t) => {
|
|
255
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
256
|
+
|
|
257
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
258
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
259
|
+
|
|
260
|
+
const paged = await esmock('./paged.js', {
|
|
261
|
+
'pagedjs-cli': MockPrinter,
|
|
262
|
+
'fs-extra': mockFs,
|
|
263
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
264
|
+
'#lib/process/manager.js': { default: mockProcessManager },
|
|
265
|
+
'./split.js': { splitPdf: sandbox.stub().resolves({}) }
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
await paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), { debug: true })
|
|
269
|
+
|
|
270
|
+
t.false(mockPrinter.close.called)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('registers console event handler for browser warnings and errors', async (t) => {
|
|
274
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
275
|
+
|
|
276
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
277
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
278
|
+
|
|
279
|
+
const mockDebug = sandbox.stub()
|
|
280
|
+
const mockCreateDebug = sandbox.stub().returns(mockDebug)
|
|
281
|
+
|
|
282
|
+
const paged = await esmock('./paged.js', {
|
|
283
|
+
'pagedjs-cli': MockPrinter,
|
|
284
|
+
'fs-extra': mockFs,
|
|
285
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
286
|
+
'#lib/process/manager.js': { default: mockProcessManager },
|
|
287
|
+
'#debug': { default: mockCreateDebug },
|
|
288
|
+
'./split.js': { splitPdf: sandbox.stub().resolves({}) }
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
await paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), {})
|
|
292
|
+
|
|
293
|
+
// Verify console handler was registered
|
|
294
|
+
const consoleHandler = mockPrinter.on.getCalls().find(call => call.args[0] === 'console')
|
|
295
|
+
t.truthy(consoleHandler, 'console event handler should be registered')
|
|
296
|
+
|
|
297
|
+
// Simulate console warning
|
|
298
|
+
const handler = consoleHandler.args[1]
|
|
299
|
+
handler('warning', 'CSS property not supported')
|
|
300
|
+
|
|
301
|
+
t.true(mockDebug.calledWith('browser %s: %s', 'warning', 'CSS property not supported'))
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('logs browser errors via debug', async (t) => {
|
|
305
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
306
|
+
|
|
307
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
308
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
309
|
+
|
|
310
|
+
const mockDebug = sandbox.stub()
|
|
311
|
+
const mockCreateDebug = sandbox.stub().returns(mockDebug)
|
|
312
|
+
|
|
313
|
+
const paged = await esmock('./paged.js', {
|
|
314
|
+
'pagedjs-cli': MockPrinter,
|
|
315
|
+
'fs-extra': mockFs,
|
|
316
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
317
|
+
'#lib/process/manager.js': { default: mockProcessManager },
|
|
318
|
+
'#debug': { default: mockCreateDebug },
|
|
319
|
+
'./split.js': { splitPdf: sandbox.stub().resolves({}) }
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
await paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), {})
|
|
323
|
+
|
|
324
|
+
// Get console handler
|
|
325
|
+
const consoleHandler = mockPrinter.on.getCalls().find(call => call.args[0] === 'console')
|
|
326
|
+
const handler = consoleHandler.args[1]
|
|
327
|
+
|
|
328
|
+
// Simulate console error
|
|
329
|
+
handler('error', 'Script error:', 'undefined is not a function')
|
|
330
|
+
|
|
331
|
+
t.true(mockDebug.calledWith('browser %s: %s', 'error', 'Script error: undefined is not a function'))
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
test('ignores non-warning/error console messages', async (t) => {
|
|
335
|
+
const { sandbox, mockReporter, mockProcessManager, mockFs } = t.context
|
|
336
|
+
|
|
337
|
+
const mockPrinter = createMockPrinter(sandbox)
|
|
338
|
+
const MockPrinter = sandbox.stub().returns(mockPrinter)
|
|
339
|
+
|
|
340
|
+
const mockDebug = sandbox.stub()
|
|
341
|
+
const mockCreateDebug = sandbox.stub().returns(mockDebug)
|
|
342
|
+
|
|
343
|
+
const paged = await esmock('./paged.js', {
|
|
344
|
+
'pagedjs-cli': MockPrinter,
|
|
345
|
+
'fs-extra': mockFs,
|
|
346
|
+
'#lib/reporter/index.js': { default: mockReporter },
|
|
347
|
+
'#lib/process/manager.js': { default: mockProcessManager },
|
|
348
|
+
'#debug': { default: mockCreateDebug },
|
|
349
|
+
'./split.js': { splitPdf: sandbox.stub().resolves({}) }
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
await paged.default(testPath('input.html'), testPath('covers.html'), testPath('output.pdf'), {})
|
|
353
|
+
|
|
354
|
+
// Get console handler
|
|
355
|
+
const consoleHandler = mockPrinter.on.getCalls().find(call => call.args[0] === 'console')
|
|
356
|
+
const handler = consoleHandler.args[1]
|
|
357
|
+
|
|
358
|
+
// Reset debug stub to check only calls after handler invocation
|
|
359
|
+
mockDebug.resetHistory()
|
|
360
|
+
|
|
361
|
+
// Simulate console log (should be ignored)
|
|
362
|
+
handler('log', 'Debug info')
|
|
363
|
+
|
|
364
|
+
// Should not have logged this message (info/log messages are ignored)
|
|
365
|
+
t.false(mockDebug.calledWith('browser %s: %s', 'log', 'Debug info'))
|
|
366
|
+
})
|