@thegetty/quire-cli 1.0.0-rc.35 → 1.0.0-rc.37
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 +37 -36
- package/src/commands/build.spec.js +113 -0
- package/src/commands/build.test.js +397 -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 +32 -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 +196 -94
- package/src/lib/11ty/cli.js +91 -37
- 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 +132 -0
- package/src/lib/logger/debug.spec.js +130 -0
- package/src/lib/logger/debug.test.js +128 -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/README.md
CHANGED
|
@@ -2,6 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides an abstraction to ease PDF generation across PrinceXML and Paged.js. The exported module dynamically loads a wrapper to align the libraries' JS APIs by exporting a single method that accepts a `lib` option and returns an async function that takes input, output, and option params.
|
|
4
4
|
|
|
5
|
+
### Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ lib/pdf/index.js │
|
|
10
|
+
│ • Façade: resolves engine, validates prerequisites │
|
|
11
|
+
│ • Checks engine availability (fail-fast before reporter) │
|
|
12
|
+
│ • Owns reporter lifecycle (start/succeed/fail) │
|
|
13
|
+
└─────────────────┬─────────────────────────┬─────────────────┘
|
|
14
|
+
│ │
|
|
15
|
+
┌─────────┴─────────┐ ┌─────────┴─────────┐
|
|
16
|
+
│ paged.js │ │ prince.js │
|
|
17
|
+
│ (Paged.js CLI) │ │ (Prince CLI) │
|
|
18
|
+
└─────────┬─────────┘ └─────────┬─────────┘
|
|
19
|
+
│ │
|
|
20
|
+
└───────────┬─────────────┘
|
|
21
|
+
│
|
|
22
|
+
┌─────────┴─────────┐
|
|
23
|
+
│ split.js │
|
|
24
|
+
│ (pdf-lib logic) │
|
|
25
|
+
└───────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Usage
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
import generatePdf from '#lib/pdf/index.js'
|
|
32
|
+
|
|
33
|
+
// Basic usage (uses project config for output path)
|
|
34
|
+
const pdfPath = await generatePdf({ lib: 'pagedjs' })
|
|
35
|
+
|
|
36
|
+
// With custom output path
|
|
37
|
+
const pdfPath = await generatePdf({
|
|
38
|
+
lib: 'prince',
|
|
39
|
+
output: 'downloads/my-book.pdf' // relative to project root
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// With absolute path
|
|
43
|
+
const pdfPath = await generatePdf({
|
|
44
|
+
lib: 'pagedjs',
|
|
45
|
+
output: '/tmp/publication.pdf'
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Options
|
|
50
|
+
|
|
51
|
+
| Option | Type | Default | Description |
|
|
52
|
+
|--------|------|---------|-------------|
|
|
53
|
+
| `lib` | string | `'pagedjs'` | PDF engine: `'pagedjs'` or `'prince'` |
|
|
54
|
+
| `output` | string | from config | Output path (overrides project config) |
|
|
55
|
+
| `debug` | boolean | `false` | Enable debug output |
|
|
56
|
+
|
|
57
|
+
### Output Path Resolution
|
|
58
|
+
|
|
59
|
+
1. If `options.output` is provided, it is used (relative paths resolved against project root)
|
|
60
|
+
2. Otherwise, uses `config.pdf.outputDir` + `config.pdf.filename` from project config
|
|
61
|
+
3. If no config, falls back to `{projectRoot}/{libName}.pdf`
|
|
62
|
+
|
|
63
|
+
### Page Mapping
|
|
64
|
+
|
|
5
65
|
The module also provides plugins for Prince and Paged.js to map quire webpages to PDF pages. In both cases this is achieved after PDF rendering by querying the HTML document that was printed for `.quire-page` elements and using the PDF generator's APIs to determine content page ids, page data like titles and contributors, and first / last pages. They then use a simple stripping algorithm with `pdf-lib` to split the pages for `--page-pdf` flagged runs.
|
|
6
66
|
|
|
7
67
|
### Paged.js Façade
|
|
@@ -17,3 +77,74 @@ The Prince abstraction wraps the command line execution of the Prince executable
|
|
|
17
77
|
See the [Prince Command-line Reference](https://www.princexml.com/doc/command-line/). Prince's [scripting documentation](https://www.princexml.com/doc/javascript/) has details on its runtime Javascript implementation, *which is only compatible up to ES5*.
|
|
18
78
|
|
|
19
79
|
The Prince plugin passes page map data as JSON to STDOUT.
|
|
80
|
+
|
|
81
|
+
### Test Strategy
|
|
82
|
+
|
|
83
|
+
The PDF module uses a layered testing approach:
|
|
84
|
+
|
|
85
|
+
| Layer | Test Type | Coverage |
|
|
86
|
+
|-------|-----------|----------|
|
|
87
|
+
| `index.js` | Unit tests | Engine resolution, path resolution, availability checks |
|
|
88
|
+
| `paged.js` | Integration tests | Error paths (Puppeteer failures, file write errors) |
|
|
89
|
+
| `prince.js` | Integration tests | Error paths (Prince failures, cancellation handling) |
|
|
90
|
+
| `split.js` | Unit tests | Pure PDF manipulation logic with mocked pdf-lib |
|
|
91
|
+
| Full flow | E2E tests | Happy paths with real tools |
|
|
92
|
+
|
|
93
|
+
#### Why This Strategy?
|
|
94
|
+
|
|
95
|
+
**Façade (`index.js`)** - Unit tests verify:
|
|
96
|
+
- Engine name normalization (`pagedjs`, `paged`, `paged.js` → same engine)
|
|
97
|
+
- Output path resolution priority (CLI > config > fallback)
|
|
98
|
+
- Engine availability checking (Prince binary in PATH)
|
|
99
|
+
- Error handling for missing prerequisites
|
|
100
|
+
|
|
101
|
+
**Engine implementations (`paged.js`, `prince.js`)** - Integration tests for error paths:
|
|
102
|
+
- These are orchestration code that wire external tools (Puppeteer, Prince CLI) with reporter updates
|
|
103
|
+
- Happy paths are well-covered by E2E tests with real tools
|
|
104
|
+
- Error paths (tool crashes, permission errors, cancellation) are hard to trigger in E2E
|
|
105
|
+
- Tests mock `execa`/`pagedjs-cli` to verify error wrapping and cleanup
|
|
106
|
+
|
|
107
|
+
**PDF splitting (`split.js`)** - Unit tests:
|
|
108
|
+
- Pure logic that manipulates PDFs using pdf-lib
|
|
109
|
+
- No external tool dependencies
|
|
110
|
+
- Tests mock pdf-lib's `PDFDocument` to verify page extraction, cover insertion, and error handling
|
|
111
|
+
|
|
112
|
+
**E2E tests** - Full integration:
|
|
113
|
+
- Test the complete `quire pdf` command with real publications
|
|
114
|
+
- Verify actual PDF output with both engines (when available)
|
|
115
|
+
- Cover the happy path that unit/integration tests intentionally skip
|
|
116
|
+
|
|
117
|
+
#### Running Tests
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# All PDF module tests
|
|
121
|
+
npm run test:unit -- src/lib/pdf/
|
|
122
|
+
|
|
123
|
+
# Specific test files
|
|
124
|
+
npm run test:unit -- src/lib/pdf/index.test.js # façade tests
|
|
125
|
+
npm run test:unit -- src/lib/pdf/paged.test.js # Paged.js error paths
|
|
126
|
+
npm run test:unit -- src/lib/pdf/prince.test.js # Prince error paths
|
|
127
|
+
npm run test:unit -- src/lib/pdf/split.test.js # PDF splitting logic
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Error Handling
|
|
131
|
+
|
|
132
|
+
All errors are wrapped in typed `PdfGenerationError` with:
|
|
133
|
+
- Tool name (`'Prince'`, `'Paged.js'`)
|
|
134
|
+
- Operation that failed (`'PDF rendering'`, `'page map extraction'`, etc.)
|
|
135
|
+
- Underlying error details
|
|
136
|
+
|
|
137
|
+
Example error flow:
|
|
138
|
+
```
|
|
139
|
+
Prince CLI fails → execa rejects with stderr
|
|
140
|
+
→ prince.js catches, wraps in PdfGenerationError
|
|
141
|
+
→ index.js catches, calls reporter.fail(), re-throws
|
|
142
|
+
→ Command handler displays formatted error to user
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Graceful Shutdown
|
|
146
|
+
|
|
147
|
+
Both engines support Ctrl+C cancellation:
|
|
148
|
+
- `prince.js` uses `execa`'s `cancelSignal` option
|
|
149
|
+
- `paged.js` registers cleanup with `processManager.onShutdown()`
|
|
150
|
+
- On cancellation, `reporter.warn('PDF generation cancelled')` notifies the user
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF Engine Registry
|
|
3
|
+
*
|
|
4
|
+
* Centralized metadata for PDF engines. Each engine module re-exports
|
|
5
|
+
* its metadata from here, ensuring single source of truth.
|
|
6
|
+
*
|
|
7
|
+
* The façade imports this registry to:
|
|
8
|
+
* 1. Resolve engine names to implementations
|
|
9
|
+
* 2. Check binary availability before loading heavy modules
|
|
10
|
+
* 3. Provide helpful error messages with install URLs
|
|
11
|
+
*/
|
|
12
|
+
import { docsUrl } from '#helpers/docs-url.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} EngineMetadata
|
|
16
|
+
* @property {string} name - Human-readable display name
|
|
17
|
+
* @property {string} module - Module filename (e.g., 'paged.js')
|
|
18
|
+
* @property {string|null} requiresBinary - CLI binary name, or null if built-in
|
|
19
|
+
* @property {Object} [toolInfo] - Error metadata (only for engines requiring binaries)
|
|
20
|
+
* @property {string} [toolInfo.displayName] - Display name for error messages
|
|
21
|
+
* @property {string} [toolInfo.installUrl] - URL to download the tool
|
|
22
|
+
* @property {string} [toolInfo.docsUrl] - Quire docs URL
|
|
23
|
+
* @property {string} [toolInfo.fallback] - Alternative suggestion
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @type {Record<string, EngineMetadata>} */
|
|
27
|
+
const ENGINES = {
|
|
28
|
+
pagedjs: {
|
|
29
|
+
name: 'Paged.js',
|
|
30
|
+
module: 'paged.js',
|
|
31
|
+
requiresBinary: null, // Built-in Node.js library
|
|
32
|
+
},
|
|
33
|
+
prince: {
|
|
34
|
+
name: 'Prince',
|
|
35
|
+
module: 'prince.js',
|
|
36
|
+
requiresBinary: 'prince',
|
|
37
|
+
toolInfo: {
|
|
38
|
+
displayName: 'PrinceXML',
|
|
39
|
+
installUrl: 'https://www.princexml.com/download/',
|
|
40
|
+
docsUrl: docsUrl('pdf-output'),
|
|
41
|
+
fallback: 'Or use the default PDF engine: quire pdf --engine pagedjs',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default ENGINES
|
package/src/lib/pdf/index.js
CHANGED
|
@@ -1,42 +1,145 @@
|
|
|
1
1
|
import { dynamicImport } from '#helpers/os-utils.js'
|
|
2
|
+
import which from '#helpers/which.js'
|
|
2
3
|
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import path from 'node:path'
|
|
5
|
+
import fs from 'fs-extra'
|
|
6
|
+
import paths, { loadProjectConfig } from '#lib/project/index.js'
|
|
7
|
+
import reporter from '#lib/reporter/index.js'
|
|
8
|
+
import { InvalidPdfLibraryError, MissingBuildOutputError } from '#src/errors/index.js'
|
|
9
|
+
import ENGINE_METADATA from './engines.js'
|
|
10
|
+
import createDebug from '#debug'
|
|
11
|
+
import { ENGINES } from './schema.js'
|
|
12
|
+
|
|
13
|
+
export { ENGINES }
|
|
4
14
|
|
|
5
15
|
const __filename = fileURLToPath(import.meta.url)
|
|
6
16
|
const __dirname = path.dirname(__filename)
|
|
7
17
|
|
|
18
|
+
const debug = createDebug('lib:pdf')
|
|
19
|
+
|
|
8
20
|
/**
|
|
9
|
-
*
|
|
21
|
+
* Resolve the PDF library implementation
|
|
22
|
+
*
|
|
23
|
+
* @param {string} name - Library name to resolve
|
|
24
|
+
* @returns {Object} Resolved engine with absolute module path
|
|
25
|
+
* @throws {InvalidPdfLibraryError} When library name is not recognized
|
|
10
26
|
*/
|
|
11
|
-
|
|
12
|
-
const lib = { name, options, path }
|
|
13
|
-
|
|
27
|
+
function resolveLibrary(name) {
|
|
14
28
|
const normalizedName = name.replace(/[-_.\s]/g, '').toLowerCase()
|
|
15
29
|
|
|
16
30
|
switch (normalizedName) {
|
|
17
31
|
case 'paged':
|
|
18
|
-
case 'pagedjs':
|
|
19
|
-
|
|
20
|
-
lib.options = options
|
|
21
|
-
lib.path = path.join(__dirname, 'paged.js')
|
|
22
|
-
break
|
|
23
|
-
}
|
|
32
|
+
case 'pagedjs':
|
|
33
|
+
return { ...ENGINE_METADATA.pagedjs, path: path.join(__dirname, ENGINE_METADATA.pagedjs.module) }
|
|
24
34
|
case 'prince':
|
|
25
|
-
case 'princexml':
|
|
26
|
-
|
|
27
|
-
lib.options = options
|
|
28
|
-
lib.path = path.join(__dirname, 'prince.js')
|
|
29
|
-
break
|
|
30
|
-
}
|
|
35
|
+
case 'princexml':
|
|
36
|
+
return { ...ENGINE_METADATA.prince, path: path.join(__dirname, ENGINE_METADATA.prince.module) }
|
|
31
37
|
default:
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
throw new InvalidPdfLibraryError(name)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if the required binary for an engine is available
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} engine - Engine definition from ENGINES
|
|
46
|
+
* @throws {ToolNotFoundError} When required binary is not in PATH
|
|
47
|
+
*/
|
|
48
|
+
function checkEngineAvailable(engine) {
|
|
49
|
+
if (!engine.requiresBinary) {
|
|
50
|
+
return // No binary required (e.g., pagedjs uses Node.js)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = which(engine.requiresBinary, engine.toolInfo)
|
|
54
|
+
debug('found %s at %s', engine.requiresBinary, result)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Construct the default output path for the PDF from project config
|
|
59
|
+
*
|
|
60
|
+
* @param {string} projectRoot - Project root directory
|
|
61
|
+
* @param {string} outputDir - Build output directory
|
|
62
|
+
* @param {Object} [pdfConfig] - PDF configuration from project config
|
|
63
|
+
* @param {string} libName - Library name for fallback filename
|
|
64
|
+
* @returns {string} Default output path
|
|
65
|
+
*/
|
|
66
|
+
function getDefaultOutputPath(projectRoot, outputDir, pdfConfig, libName) {
|
|
67
|
+
if (pdfConfig) {
|
|
68
|
+
return path.join(projectRoot, outputDir, pdfConfig.outputDir, `${pdfConfig.filename}.pdf`)
|
|
69
|
+
}
|
|
70
|
+
return path.join(projectRoot, `${libName}.pdf`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the final output path, allowing CLI override
|
|
75
|
+
*
|
|
76
|
+
* @param {string} [userOutput] - User-specified output path from --output option
|
|
77
|
+
* @param {string} defaultPath - Default path from project config
|
|
78
|
+
* @param {string} projectRoot - Project root for resolving relative paths
|
|
79
|
+
* @returns {string} Resolved absolute output path
|
|
80
|
+
*/
|
|
81
|
+
function resolveOutputPath(userOutput, defaultPath, projectRoot) {
|
|
82
|
+
if (!userOutput) {
|
|
83
|
+
return defaultPath
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Resolve relative paths against project root
|
|
87
|
+
if (path.isAbsolute(userOutput)) {
|
|
88
|
+
return userOutput
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return path.join(projectRoot, userOutput)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate a PDF from the built publication
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} options - Generation options
|
|
98
|
+
* @param {string} [options.lib='pagedjs'] - PDF library to use ('pagedjs' or 'prince')
|
|
99
|
+
* @param {string} [options.output] - Custom output path (overrides project config)
|
|
100
|
+
* @param {boolean} [options.debug] - Enable debug output
|
|
101
|
+
* @returns {Promise<string>} Absolute path to the generated PDF file
|
|
102
|
+
*/
|
|
103
|
+
export default async function generatePdf(options = {}) {
|
|
104
|
+
const libName = options.lib || 'pagedjs'
|
|
105
|
+
const lib = resolveLibrary(libName)
|
|
106
|
+
|
|
107
|
+
debug('resolved library: %s → %s', libName, lib.name)
|
|
108
|
+
|
|
109
|
+
// Check engine availability BEFORE starting reporter (fail fast with clean error)
|
|
110
|
+
checkEngineAvailable(lib)
|
|
111
|
+
|
|
112
|
+
const projectRoot = paths.getProjectRoot()
|
|
113
|
+
const buildOutputDir = paths.getOutputDir()
|
|
114
|
+
const config = await loadProjectConfig(projectRoot)
|
|
115
|
+
|
|
116
|
+
const publicationInput = path.join(projectRoot, buildOutputDir, 'pdf.html')
|
|
117
|
+
const coversInput = path.join(projectRoot, buildOutputDir, 'pdf-covers.html')
|
|
118
|
+
|
|
119
|
+
debug('input: %s', publicationInput)
|
|
120
|
+
debug('covers: %s', coversInput)
|
|
121
|
+
|
|
122
|
+
if (!fs.existsSync(publicationInput)) {
|
|
123
|
+
throw new MissingBuildOutputError('pdf.html', publicationInput)
|
|
34
124
|
}
|
|
35
125
|
|
|
126
|
+
const defaultPath = getDefaultOutputPath(projectRoot, buildOutputDir, config.pdf, libName)
|
|
127
|
+
const pdfPath = resolveOutputPath(options.output, defaultPath, projectRoot)
|
|
128
|
+
debug('output: %s (user: %s, default: %s)', pdfPath, options.output || 'none', defaultPath)
|
|
129
|
+
|
|
36
130
|
const { default: pdfLib } = await dynamicImport(lib.path)
|
|
37
131
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
132
|
+
reporter.start(`Generating PDF using ${lib.name}...`, { showElapsed: true })
|
|
133
|
+
reporter.detail(`Input: ${publicationInput}`)
|
|
134
|
+
reporter.detail(`Output: ${pdfPath}`)
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await pdfLib(publicationInput, coversInput, pdfPath, { ...options, pdfConfig: config.pdf })
|
|
138
|
+
reporter.succeed(`PDF saved to ${pdfPath}`)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
reporter.fail(`PDF generation failed`)
|
|
141
|
+
throw error
|
|
41
142
|
}
|
|
143
|
+
|
|
144
|
+
return pdfPath
|
|
42
145
|
}
|