@thegetty/quire-cli 1.0.0-pre-release.13 → 1.0.0-pre-release.15
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/package.json +4 -1
- package/src/commands/build.js +2 -3
- package/src/commands/clean.js +2 -10
- package/src/commands/epub.js +67 -0
- package/src/commands/index.js +3 -6
- package/src/commands/pdf.js +70 -0
- package/src/commands/preview.js +4 -5
- package/src/helpers/clean.js +3 -1
- package/src/helpers/os-utils.js +20 -0
- package/src/lib/11ty/api.js +13 -3
- package/src/lib/11ty/cli.js +2 -2
- package/src/lib/11ty/paths.js +45 -14
- package/src/lib/epub/README.md +7 -0
- package/src/lib/epub/epub.js +9 -0
- package/src/lib/epub/index.js +36 -0
- package/src/lib/epub/pandoc.js +42 -0
- package/src/lib/pdf/README.md +12 -0
- package/src/lib/pdf/index.js +38 -0
- package/src/lib/pdf/paged.js +42 -0
- package/src/lib/pdf/prince.js +17 -0
- package/src/main.js +20 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegetty/quire-cli",
|
|
3
3
|
"description": "Quire command-line interface",
|
|
4
|
-
"version": "1.0.0-pre-release.
|
|
4
|
+
"version": "1.0.0-pre-release.15",
|
|
5
5
|
"author": "Getty Digital",
|
|
6
6
|
"license": "SEE LICENSE IN https://github.com/thegetty/quire/blob/main/LICENSE",
|
|
7
7
|
"bugs": {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"conf": "^10.2.0",
|
|
52
52
|
"cross-env": "^7.0.3",
|
|
53
53
|
"del": "^7.0.0",
|
|
54
|
+
"epubjs": "^0.4.2",
|
|
54
55
|
"execa": "^6.1.0",
|
|
55
56
|
"fs-extra": "^11.0.0",
|
|
56
57
|
"hosted-git-info": "^6.1.1",
|
|
@@ -58,7 +59,9 @@
|
|
|
58
59
|
"inquirer": "^9.1.4",
|
|
59
60
|
"install-npm-version": "^1.0.8",
|
|
60
61
|
"loglevel": "^1.8.0",
|
|
62
|
+
"open": "^8.4.0",
|
|
61
63
|
"ora": "^6.1.2",
|
|
64
|
+
"pagedjs-cli": "^0.3.1",
|
|
62
65
|
"semver": "^7.3.8",
|
|
63
66
|
"simple-git": "^3.15.0",
|
|
64
67
|
"update-notifier": "^6.0.2"
|
package/src/commands/build.js
CHANGED
|
@@ -19,10 +19,9 @@ export default class BuildCommand extends Command {
|
|
|
19
19
|
version: '1.0.0',
|
|
20
20
|
args: [
|
|
21
21
|
// [
|
|
22
|
-
// '[formats]', 'output formats',
|
|
22
|
+
// '[formats...]', 'output formats',
|
|
23
23
|
// {
|
|
24
|
-
// choices: ['
|
|
25
|
-
// default: ['html', 'html only']
|
|
24
|
+
// choices: ['pdf', 'epub'],
|
|
26
25
|
// }
|
|
27
26
|
// ],
|
|
28
27
|
],
|
package/src/commands/clean.js
CHANGED
|
@@ -20,15 +20,7 @@ export default class CleanCommand extends Command {
|
|
|
20
20
|
description: 'Remove build outputs',
|
|
21
21
|
summary: 'remove build outputs',
|
|
22
22
|
version: '1.0.0',
|
|
23
|
-
args: [
|
|
24
|
-
// [
|
|
25
|
-
// '[formats]', 'output formats',
|
|
26
|
-
// {
|
|
27
|
-
// choices: ['html', 'pdf', 'epub'],
|
|
28
|
-
// default: ['html', 'html only']
|
|
29
|
-
// }
|
|
30
|
-
// ],
|
|
31
|
-
],
|
|
23
|
+
args: [],
|
|
32
24
|
options: [
|
|
33
25
|
[ '-d', '--dry-run', 'show paths to be cleaned without deleting files' ],
|
|
34
26
|
[ '-p', '--progress', 'display progress of removing files' ],
|
|
@@ -52,7 +44,7 @@ export default class CleanCommand extends Command {
|
|
|
52
44
|
? `the following files ${options.dryRun ? 'will be' : 'have been'} deleted:`
|
|
53
45
|
: 'no files to delete'
|
|
54
46
|
|
|
55
|
-
console.debug(`[CLI] ${message}
|
|
47
|
+
console.debug(`[CLI] ${message}\n${deletedPaths.join('\n')}`)
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
preAction(command) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Command from '#src/Command.js'
|
|
2
|
+
import { paths, projectRoot } from '#lib/11ty/index.js'
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import libEpub from '#lib/epub/index.js'
|
|
5
|
+
import open from 'open'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Quire CLI `build epub` Command
|
|
10
|
+
*
|
|
11
|
+
* Generate EPUB from Eleventy `build` output.
|
|
12
|
+
*
|
|
13
|
+
* @class EpubCommand
|
|
14
|
+
* @extends {Command}
|
|
15
|
+
*/
|
|
16
|
+
export default class EpubCommand extends Command {
|
|
17
|
+
static definition = {
|
|
18
|
+
name: 'epub',
|
|
19
|
+
description: 'Generate publication EPUB',
|
|
20
|
+
summary: 'run build epub',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
args: [],
|
|
23
|
+
options: [
|
|
24
|
+
[
|
|
25
|
+
'--lib <module>', 'use the specified epub library', 'epubjs',
|
|
26
|
+
{ choices: ['epubjs', 'pandoc'], default: 'epubjs' }
|
|
27
|
+
],
|
|
28
|
+
[ '--open', 'open EPUB in default application' ],
|
|
29
|
+
[ '--debug', 'run epub with debug output' ],
|
|
30
|
+
],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super(EpubCommand.definition)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async action(options, command) {
|
|
38
|
+
if (options.debug) {
|
|
39
|
+
console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const input = path.join(projectRoot, paths.epub)
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(input)) {
|
|
45
|
+
console.error(`Unable to find Epub input at ${input}\nPlease first run the 'quire build' command.`)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const output = path.join(projectRoot, `${options.lib}.epub`)
|
|
50
|
+
|
|
51
|
+
const epubLib = await libEpub(options.lib, { ...options.debug })
|
|
52
|
+
await epubLib(input, output, { ...options.debug })
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(output) && options.open) open(output)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* test if build site has already be run and output can be reused
|
|
59
|
+
* @todo
|
|
60
|
+
*/
|
|
61
|
+
preAction(command) {
|
|
62
|
+
const options = command.opts()
|
|
63
|
+
if (options.debug) {
|
|
64
|
+
console.debug('[CLI] Calling \'epub\' command pre-action with options', options)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/commands/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fileURLToPath
|
|
1
|
+
import { dynamicImport } from '#helpers/os-utils.js'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
3
|
import fs from 'fs-extra'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
@@ -15,10 +15,7 @@ const thisfile = path.basename(__filename)
|
|
|
15
15
|
const commands = await Promise.all(
|
|
16
16
|
fs.readdirSync(__dirname)
|
|
17
17
|
.filter((file) => file !== thisfile && file.match(/^.*\.js$/))
|
|
18
|
-
.map((file) =>
|
|
19
|
-
? import(pathToFileURL(path.resolve(__dirname, file)))
|
|
20
|
-
: import(path.resolve(__dirname, file))
|
|
21
|
-
)
|
|
18
|
+
.map(async (file) => await dynamicImport(path.resolve(__dirname, file)))
|
|
22
19
|
).then((modules) => {
|
|
23
20
|
return modules.map(({ default: CommandClass }) => new CommandClass())
|
|
24
21
|
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Command from '#src/Command.js'
|
|
2
|
+
import { paths, projectRoot } from '#lib/11ty/index.js'
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import libPdf from '#lib/pdf/index.js'
|
|
5
|
+
import open from 'open'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
// import testcwd from '#helpers/test-cwd.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Quire CLI `pdf` Command
|
|
11
|
+
*
|
|
12
|
+
* Generate PDF from Eleventy `build` output.
|
|
13
|
+
*
|
|
14
|
+
* @class PDFCommand
|
|
15
|
+
* @extends {Command}
|
|
16
|
+
*/
|
|
17
|
+
export default class PDFCommand extends Command {
|
|
18
|
+
static definition = {
|
|
19
|
+
name: 'pdf',
|
|
20
|
+
description: 'Generate publication PDF',
|
|
21
|
+
summary: 'run build pdf',
|
|
22
|
+
version: '1.0.0',
|
|
23
|
+
args: [
|
|
24
|
+
],
|
|
25
|
+
options: [
|
|
26
|
+
[
|
|
27
|
+
'--lib <module>', 'use the specified pdf module', 'pagedjs',
|
|
28
|
+
{ choices: ['pagedjs', 'prince'], default: 'pagedjs' }
|
|
29
|
+
],
|
|
30
|
+
[ '--open', 'open PDF in default application' ],
|
|
31
|
+
[ '--debug', 'run build with debug output to console' ],
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
super(PDFCommand.definition)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async action(options, command) {
|
|
40
|
+
if (options.debug) {
|
|
41
|
+
console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const input = path.join(projectRoot, paths.output, 'pdf.html')
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(input)) {
|
|
47
|
+
console.error(`Unable to find PDF input at ${input}\nPlease first run the 'quire build' command.`)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const output = path.join(projectRoot, `${options.lib}.pdf`)
|
|
52
|
+
|
|
53
|
+
const pdfLib = await libPdf(options.lib, { ...options.debug })
|
|
54
|
+
await pdfLib(input, output, { ...options.debug })
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(output) && options.open) open(output)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(error)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @todo test if build has already be run and output can be reused
|
|
66
|
+
*/
|
|
67
|
+
preAction(command) {
|
|
68
|
+
// testcwd(command)
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/commands/preview.js
CHANGED
|
@@ -18,10 +18,9 @@ export default class PreviewCommand extends Command {
|
|
|
18
18
|
version: '1.0.0',
|
|
19
19
|
args: [
|
|
20
20
|
// [
|
|
21
|
-
// '[formats]', 'output formats',
|
|
21
|
+
// '[formats...]', 'output formats',
|
|
22
22
|
// {
|
|
23
|
-
// choices: ['
|
|
24
|
-
// default: ['html', 'html only']
|
|
23
|
+
// choices: ['pdf', 'epub'],
|
|
25
24
|
// }
|
|
26
25
|
// ],
|
|
27
26
|
],
|
|
@@ -41,9 +40,9 @@ export default class PreviewCommand extends Command {
|
|
|
41
40
|
super(PreviewCommand.definition)
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
action(options
|
|
43
|
+
async action(args, options, command) {
|
|
45
44
|
if (options.debug) {
|
|
46
|
-
console.
|
|
45
|
+
console.debug('[CLI] Command \'%s\' called with arguments [%o] and options %o', this.name(), args.join(', '), options)
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
if (options['11ty'] === 'cli') {
|
package/src/helpers/clean.js
CHANGED
|
@@ -9,8 +9,10 @@ import path from 'node:path'
|
|
|
9
9
|
*/
|
|
10
10
|
export async function clean (projectRoot, paths, options = {}) {
|
|
11
11
|
const pathsToClean = [
|
|
12
|
+
path.join(projectRoot, paths.epub),
|
|
12
13
|
path.join(projectRoot, paths.output),
|
|
13
|
-
path.join(projectRoot,
|
|
14
|
+
path.join(projectRoot, '*.epub'),
|
|
15
|
+
path.join(projectRoot, '*.pdf'),
|
|
14
16
|
]
|
|
15
17
|
|
|
16
18
|
/**
|
package/src/helpers/os-utils.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Determine if the process is running on Windows operating system
|
|
3
5
|
*/
|
|
4
6
|
export const IS_WINDOWS =
|
|
5
7
|
process.platform === 'win32' || /^(cygwin|msys)$/.test(process.env.OSTYPE)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cross platform dynamic module import
|
|
11
|
+
* On the Windows platform ESM modules are loaded using the file:// scheme.
|
|
12
|
+
*
|
|
13
|
+
* Nota bene: a relative `path` argument is relative to this module!
|
|
14
|
+
*
|
|
15
|
+
*
|
|
16
|
+
* @param {String} path An aboslute path for the module to import
|
|
17
|
+
* @return {Object} Imported module
|
|
18
|
+
*/
|
|
19
|
+
export const dynamicImport = async (path) => {
|
|
20
|
+
try {
|
|
21
|
+
return IS_WINDOWS ? await import(pathToFileURL(path)) : await import(path)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`[CLI] unable to import ${path}`, error)
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/lib/11ty/api.js
CHANGED
|
@@ -32,7 +32,7 @@ const factory = async (options = {}) => {
|
|
|
32
32
|
*/
|
|
33
33
|
const copyOptions = {
|
|
34
34
|
debug: options.debug || false,
|
|
35
|
-
expand:
|
|
35
|
+
expand: false,
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -83,11 +83,19 @@ const factory = async (options = {}) => {
|
|
|
83
83
|
return addPassthroughCopy(entry, copyOptions)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Event callback when a build completes
|
|
89
|
+
* @see https://www.11ty.dev/docs/events/#eleventy.after
|
|
90
|
+
*/
|
|
91
|
+
eleventyConfig.on('eleventy.after', async () => {
|
|
92
|
+
console.debug('[11ty:API] build complete')
|
|
93
|
+
})
|
|
94
|
+
|
|
86
95
|
return eleventyConfig
|
|
87
96
|
},
|
|
88
97
|
configPath: options.config || config,
|
|
89
98
|
quietMode: options.quiet || false,
|
|
90
|
-
// source: 'script',
|
|
91
99
|
})
|
|
92
100
|
|
|
93
101
|
return eleventy
|
|
@@ -111,9 +119,11 @@ export default {
|
|
|
111
119
|
|
|
112
120
|
eleventy.setDryRun(options.dryrun)
|
|
113
121
|
|
|
114
|
-
await eleventy.
|
|
122
|
+
await eleventy.write()
|
|
115
123
|
},
|
|
116
124
|
serve: async (options = {}) => {
|
|
125
|
+
process.cwd(projectRoot)
|
|
126
|
+
|
|
117
127
|
console.info('[CLI:11ty] running development server')
|
|
118
128
|
|
|
119
129
|
process.env.ELEVENTY_ENV = 'development'
|
package/src/lib/11ty/cli.js
CHANGED
|
@@ -82,10 +82,10 @@ export default {
|
|
|
82
82
|
|
|
83
83
|
command.push('--serve')
|
|
84
84
|
|
|
85
|
-
env.ELEVENTY_ENV = 'development'
|
|
86
|
-
|
|
87
85
|
if (options.port) command.push(`--port=${options.port}`)
|
|
88
86
|
|
|
87
|
+
env.ELEVENTY_ENV = 'development'
|
|
88
|
+
|
|
89
89
|
await execa('node', command, {
|
|
90
90
|
all: true,
|
|
91
91
|
cwd: projectRoot,
|
package/src/lib/11ty/paths.js
CHANGED
|
@@ -23,19 +23,8 @@ const cliRoot = path.resolve(__dirname, path.join('..', '..'))
|
|
|
23
23
|
const eleventyConfig = '.eleventy.js'
|
|
24
24
|
const version = 'latest'
|
|
25
25
|
|
|
26
|
-
// Test project directory for an eleventy configuration file
|
|
27
|
-
const hasEleventyConfig = (dir) => {
|
|
28
|
-
try {
|
|
29
|
-
return fs.readdirSync(dir).includes(eleventyConfig)
|
|
30
|
-
} catch (error) {
|
|
31
|
-
throw new Error(`[CLI:11ty] Unable to read project directory for eleventy config ${error}`)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
26
|
export const projectRoot = process.cwd()
|
|
36
27
|
|
|
37
|
-
const inputDir = path.join(projectRoot, 'content')
|
|
38
|
-
|
|
39
28
|
/**
|
|
40
29
|
* Absolute path to the latest installed version of `quire-11ty`
|
|
41
30
|
* @todo use version read from the project `.quire-version` file
|
|
@@ -47,13 +36,55 @@ const libQuirePath = path.resolve(__dirname, path.join(cliRoot, 'lib', 'quire',
|
|
|
47
36
|
* Nota bene: to get a relative path to the `eleventyRoot`,
|
|
48
37
|
* for example when the version is specified is 'latest',
|
|
49
38
|
* it must be set to the real path to the symlink target.
|
|
39
|
+
*
|
|
40
|
+
* @todo refactor how and *when* the eleventyRoot is determined:
|
|
41
|
+
* - we only need eleventyRoot for cli commands that run 11ty
|
|
42
|
+
* - paths module is a concern of the `quire` module
|
|
43
|
+
* - global quire-cli installation
|
|
44
|
+
* - local quire-cli installation
|
|
45
|
+
*
|
|
46
|
+
* For an excellent developer experience, the quire-11ty code should be
|
|
47
|
+
* installed into an `11ty` or `quire-11ty` directory in the `projectRoot`
|
|
48
|
+
* this will allow us to more easily manage symlinks for local development
|
|
49
|
+
* using unpublished `quire-11ty` code.
|
|
50
|
+
* @example
|
|
51
|
+
* ```sh
|
|
52
|
+
* blargh/
|
|
53
|
+
* .git/
|
|
54
|
+
* .gitignore
|
|
55
|
+
* .node-version
|
|
56
|
+
* .quire-version
|
|
57
|
+
* 11ty@ --> /Users/mph/Code/Getty/quire/packages/11ty
|
|
58
|
+
* content/
|
|
59
|
+
* CHANGELOG.md
|
|
60
|
+
* LICENSE
|
|
61
|
+
* README.md
|
|
62
|
+
* package.json
|
|
63
|
+
* package-lock.json
|
|
64
|
+
* ```
|
|
65
|
+
* Installing to a directory will allow more cleanly `eject` and `uneject`
|
|
66
|
+
* using a symlink or single directory `rm -rf`.
|
|
67
|
+
* Should the `11ty` directory be a dot directory to hide the complexity from
|
|
68
|
+
* users or should it be visible to be more explicit when project is ejected?
|
|
50
69
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
const getEleventyRoot = () => {
|
|
71
|
+
// try {
|
|
72
|
+
// return fs.readdirSync(projectRoot).includes(eleventyConfig)
|
|
73
|
+
// ? projectRoot
|
|
74
|
+
// : fs.realpathSync(libQuirePath)
|
|
75
|
+
// } catch (error) {
|
|
76
|
+
// throw new Error(`[CLI:11ty] Unable to read project directory for eleventy config ${error}`)
|
|
77
|
+
// }
|
|
78
|
+
return projectRoot
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const eleventyRoot = getEleventyRoot()
|
|
82
|
+
|
|
83
|
+
const inputDir = path.join(projectRoot, 'content')
|
|
54
84
|
|
|
55
85
|
export default {
|
|
56
86
|
config: path.join(eleventyRoot, '.eleventy.js'),
|
|
87
|
+
epub: path.relative(eleventyRoot, path.join(projectRoot, '_epub')),
|
|
57
88
|
input: path.relative(eleventyRoot, inputDir),
|
|
58
89
|
output: path.relative(eleventyRoot, path.join(projectRoot, '_site')),
|
|
59
90
|
data: path.relative(inputDir, path.join(eleventyRoot, '_computed')),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## CLI lib/EPUB Module
|
|
2
|
+
|
|
3
|
+
This module implements a façade for EPUB e-book file format generation libries. The module exports a single method that accepts a `lib` option and delegates to a façade for the specified EPUB library.
|
|
4
|
+
|
|
5
|
+
### Epub.js Façade
|
|
6
|
+
|
|
7
|
+
See the [`Epub.js` documentation](https://github.com/futurepress/epub.js).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// import epub from 'epubjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A façade module for the EPUB.js library
|
|
5
|
+
* @see https://github.com/futurepress/epub.js
|
|
6
|
+
*/
|
|
7
|
+
export default async (input, output, options) => {
|
|
8
|
+
console.info('[CLI:lib/epubjs] integration with epub.js is not yet implemented.')
|
|
9
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { dynamicImport } from '#helpers/os-utils.js'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
+
const __dirname = path.dirname(__filename)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A façade delegation module for EPUB libraries
|
|
10
|
+
*/
|
|
11
|
+
export default async (lib = 'epubjs', options = {}) => {
|
|
12
|
+
let libName, libPath
|
|
13
|
+
|
|
14
|
+
switch (lib.toLowerCase()) {
|
|
15
|
+
case 'epubjs': {
|
|
16
|
+
libName = 'Epub.js'
|
|
17
|
+
libPath = path.join(__dirname, 'epub.js')
|
|
18
|
+
break
|
|
19
|
+
}
|
|
20
|
+
case 'pandoc': {
|
|
21
|
+
libName = 'Pandoc'
|
|
22
|
+
libPath = path.join(__dirname, 'pandoc.js')
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
default:
|
|
26
|
+
console.error(`[CLI:lib/pdf] Unrecognized EPUB library '${lib}'`)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { default: epubLib } = await dynamicImport(libPath)
|
|
31
|
+
|
|
32
|
+
return async (input, output, options = {}) => {
|
|
33
|
+
console.info(`[CLI:lib/epub] generating EPUB using ${libName}`)
|
|
34
|
+
return await epubLib(input, output, options)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import fs from 'fs-extra'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import paths, { projectRoot } from '#lib/11ty/paths.js'
|
|
5
|
+
import which from '#helpers/which.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Filter directory entries for XHTML files
|
|
9
|
+
*
|
|
10
|
+
* @param {fs.Dirent} dirent directory entry
|
|
11
|
+
* @return {fs.Dirent} XHTML entries
|
|
12
|
+
*/
|
|
13
|
+
const xhtmlFiles = (dirent) => {
|
|
14
|
+
const stats = fs.lstatSync(dirent)
|
|
15
|
+
const { ext } = path.parse(dirent)
|
|
16
|
+
return stats.isFile() && ext === '.xhtml'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A façade module for interacting with Pandoc CLI.
|
|
21
|
+
* @see https://pandoc.org/MANUAL.html#general-options
|
|
22
|
+
*/
|
|
23
|
+
export default async (input, output, options) => {
|
|
24
|
+
which('pandoc')
|
|
25
|
+
|
|
26
|
+
const inputs = fs.readdirSync(input)
|
|
27
|
+
.map((entry) => path.join(input, entry))
|
|
28
|
+
.filter(xhtmlFiles)
|
|
29
|
+
|
|
30
|
+
const cmdOptions = [
|
|
31
|
+
`--from=html-native_divs+native_spans`,
|
|
32
|
+
`--to=epub`,
|
|
33
|
+
`--output=${output}`,
|
|
34
|
+
// `--epub-metadata=${path.join(input, 'dc.xml')}`,
|
|
35
|
+
// `--epub-cover-image=${coverImage}`,
|
|
36
|
+
// `--template=${path.join(input, 'template.xhtml')}`,
|
|
37
|
+
`--css=${path.join(input, '_assets', 'epub.css')}`,
|
|
38
|
+
`--standalone`,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
await execa('pandoc', [...cmdOptions, ...inputs])
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
## CLI lib/PDF Module
|
|
2
|
+
|
|
3
|
+
This module implements a façade for PDF generation libries.
|
|
4
|
+
The module exports a single method that accepts a `lib` option and delegates to a façade for the specified PDF library.
|
|
5
|
+
|
|
6
|
+
### Paged.js Façade
|
|
7
|
+
|
|
8
|
+
See the [`Paged.js` documentation](https://gitlab.coko.foundation/pagedjs/).
|
|
9
|
+
|
|
10
|
+
### Prince XML Façade
|
|
11
|
+
|
|
12
|
+
See the [Prince Command-line Reference](https://www.princexml.com/doc/command-line/).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { dynamicImport } from '#helpers/os-utils.js'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
+
const __dirname = path.dirname(__filename)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A façade delegation module for PDF generation libraries
|
|
10
|
+
*/
|
|
11
|
+
export default async (lib = 'pagedjs', options = {}) => {
|
|
12
|
+
let libName, libPath
|
|
13
|
+
|
|
14
|
+
switch (lib.toLowerCase()) {
|
|
15
|
+
case 'paged':
|
|
16
|
+
case 'pagedjs': {
|
|
17
|
+
libName = 'Paged.js'
|
|
18
|
+
libPath = path.join(__dirname, 'paged.js')
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
case 'prince':
|
|
22
|
+
case 'princexml': {
|
|
23
|
+
libName = 'Prince'
|
|
24
|
+
libPath = path.join(__dirname, 'prince.js')
|
|
25
|
+
break
|
|
26
|
+
}
|
|
27
|
+
default:
|
|
28
|
+
console.error(`[CLI:lib/pdf] Unrecognized PDF library '${lib}'`)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { default: pdfLib } = await dynamicImport(libPath)
|
|
33
|
+
|
|
34
|
+
return async (input, output, options = {}) => {
|
|
35
|
+
console.info(`[CLI:lib/pdf] generating PDF using ${libName}`)
|
|
36
|
+
return await pdfLib(input, output, options)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Printer from 'pagedjs-cli'
|
|
2
|
+
import fs from 'fs-extra'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A façade module for interacting with Paged.js
|
|
6
|
+
* @see https://gitlab.coko.foundation/pagedjs/
|
|
7
|
+
*/
|
|
8
|
+
export default async (input, output, options = {}) => {
|
|
9
|
+
/**
|
|
10
|
+
* Configure the Paged.js Printer options
|
|
11
|
+
* @see https://gitlab.coko.foundation/pagedjs/pagedjs-cli/-/blob/main/src/cli.js
|
|
12
|
+
*/
|
|
13
|
+
const printerOptions = {
|
|
14
|
+
allowLocal: true,
|
|
15
|
+
debug: options.debug || false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const printer = new Printer(printerOptions)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configure the Paged.js PDF options
|
|
22
|
+
* @see
|
|
23
|
+
*
|
|
24
|
+
* @todo verify options before setting them
|
|
25
|
+
* @todo catch errors thrown by the printer
|
|
26
|
+
*/
|
|
27
|
+
const pdfOptions = {
|
|
28
|
+
outlineTags: options.outlineTags || ['h1'],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
console.info(`[CLI:lib/pdf/pagedjs] printing ${input}`)
|
|
33
|
+
const file = await printer.pdf(input, pdfOptions)
|
|
34
|
+
.catch((error) => console.error(error))
|
|
35
|
+
|
|
36
|
+
if (file && output) {
|
|
37
|
+
fs.writeFile(output, file, (error) => { if (error) throw error })
|
|
38
|
+
}
|
|
39
|
+
} catch (ERR_FILE_NOT_FOUND) {
|
|
40
|
+
console.error(`[CLI:lib/pdf/pagedjs] file not found ${input}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import which from '#helpers/which.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A façade module for interacting with the Prince CLI.
|
|
6
|
+
* @see https://www.princexml.com/doc/command-line/
|
|
7
|
+
*/
|
|
8
|
+
export default async (input, output, options = {}) => {
|
|
9
|
+
which('prince')
|
|
10
|
+
|
|
11
|
+
const cmdOptions = [
|
|
12
|
+
`--output=${output}`,
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const { stderror, stdout } = await execa('prince', [...cmdOptions, input])
|
|
16
|
+
return { stderror, stdout }
|
|
17
|
+
}
|
package/src/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
1
|
+
import { Argument, Command, Option } from 'commander'
|
|
2
2
|
import commands from './commands/index.js'
|
|
3
3
|
import packageConfig from '../package.json' assert { type: 'json' }
|
|
4
4
|
|
|
@@ -37,9 +37,15 @@ commands.forEach((command) => {
|
|
|
37
37
|
aliases.forEach((alias) => subCommand.alias(alias))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @see https://github.com/tj/commander.js#more-configuration-1
|
|
42
|
+
*/
|
|
40
43
|
if (Array.isArray(args)) {
|
|
41
|
-
args.forEach(([
|
|
42
|
-
|
|
44
|
+
args.forEach(([ name, description, configuration = {} ]) => {
|
|
45
|
+
const argument = new Argument(name, description)
|
|
46
|
+
if (configuration.choices) argument.choices(configuration.choices)
|
|
47
|
+
if (configuration.default) argument.default(configuration.default)
|
|
48
|
+
subCommand.addArgument(argument)
|
|
43
49
|
})
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -47,16 +53,16 @@ commands.forEach((command) => {
|
|
|
47
53
|
* @see https://github.com/tj/commander.js/#options
|
|
48
54
|
*/
|
|
49
55
|
if (Array.isArray(options)) {
|
|
50
|
-
options.forEach((
|
|
51
|
-
if (Array.isArray(
|
|
56
|
+
options.forEach((entry) => {
|
|
57
|
+
if (Array.isArray(entry)) {
|
|
52
58
|
// ensure we can join the first two attributes as the option name
|
|
53
59
|
// when only the short or the long flag is defined in the array
|
|
54
|
-
if (
|
|
55
|
-
if (
|
|
56
|
-
|
|
60
|
+
if (entry[0].startsWith('--')) entry.unshift('\u0020'.repeat(4))
|
|
61
|
+
if (entry[0].startsWith('-') && !entry[1].startsWith('--')) {
|
|
62
|
+
entry.splice(1, 0, '')
|
|
57
63
|
}
|
|
58
64
|
// assign attribute names to the array of option attributes
|
|
59
|
-
const [ short, long, description, defaultValue ] =
|
|
65
|
+
const [ short, long, description, defaultValue ] = entry
|
|
60
66
|
// join short and long flags as the option name attribute
|
|
61
67
|
const name = /^-\w/.test(short) && /^--\w/.test(long)
|
|
62
68
|
? [short, long].join(', ')
|
|
@@ -64,11 +70,13 @@ commands.forEach((command) => {
|
|
|
64
70
|
subCommand.option(name, description, defaultValue)
|
|
65
71
|
} else {
|
|
66
72
|
/**
|
|
67
|
-
* @todo allow
|
|
73
|
+
* @todo allow options to be defined by a configuration object
|
|
68
74
|
* @see https://github.com/tj/commander.js/#more-configuration
|
|
69
75
|
*/
|
|
70
|
-
// const option = new Option(name, description
|
|
71
|
-
//
|
|
76
|
+
// const option = new Option(name, description)
|
|
77
|
+
// for (const property of configuration) {
|
|
78
|
+
// option[property](configuration[property])
|
|
79
|
+
// }
|
|
72
80
|
// subCommand.addOption(option)
|
|
73
81
|
console.error('@TODO please use an array to define option attributes')
|
|
74
82
|
}
|