@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display formatting functions for Quire configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides human-readable formatting with optional chalk styling.
|
|
5
|
+
* Separated from helpers.js to isolate the chalk dependency
|
|
6
|
+
* from the pure validation/coercion functions.
|
|
7
|
+
*
|
|
8
|
+
* @see ./helpers.js for schema-aware validation and coercion
|
|
9
|
+
*/
|
|
10
|
+
import chalk from 'chalk'
|
|
11
|
+
import schema from './schema.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the description for a configuration key from the schema
|
|
15
|
+
*
|
|
16
|
+
* @param {string} key - Configuration key
|
|
17
|
+
* @returns {string|undefined} Description string, or undefined if not found
|
|
18
|
+
*/
|
|
19
|
+
export function getKeyDescription(key) {
|
|
20
|
+
return schema[key]?.description
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format all settings as a human-readable string
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} store - Configuration store (key-value pairs)
|
|
27
|
+
* @param {Object} [options]
|
|
28
|
+
* @param {string} [options.configPath] - Path to the config file (shown in header)
|
|
29
|
+
* @param {boolean} [options.showInternal=false] - Include __internal__ keys
|
|
30
|
+
* @param {boolean} [options.useColor=false] - Apply chalk styling to output
|
|
31
|
+
* @returns {string} Formatted multiline settings display
|
|
32
|
+
*/
|
|
33
|
+
export function formatSettings(store, { configPath, showInternal = false, useColor = false } = {}) {
|
|
34
|
+
const style = useColor
|
|
35
|
+
? { bold: chalk.bold, cyan: chalk.cyan, dim: chalk.dim }
|
|
36
|
+
: { bold: (s) => s, cyan: (s) => s, dim: (s) => s }
|
|
37
|
+
|
|
38
|
+
const lines = []
|
|
39
|
+
|
|
40
|
+
if (configPath) {
|
|
41
|
+
lines.push(style.bold(`quire-cli configuration ${configPath}`))
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(style.bold('quire-cli configuration'))
|
|
44
|
+
}
|
|
45
|
+
lines.push('')
|
|
46
|
+
|
|
47
|
+
for (const [key, value] of Object.entries(store)) {
|
|
48
|
+
if (key.startsWith('__internal__') && !showInternal) continue
|
|
49
|
+
const description = getKeyDescription(key)
|
|
50
|
+
lines.push(` ${style.cyan(key)}: ${JSON.stringify(value)}`)
|
|
51
|
+
if (description) {
|
|
52
|
+
lines.push(` ${style.dim(description)}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
lines.push('')
|
|
57
|
+
lines.push(style.dim('Use "quire settings set <key> <value>" to change a setting'))
|
|
58
|
+
|
|
59
|
+
return lines.join('\n')
|
|
60
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import {
|
|
4
|
+
getKeyDescription,
|
|
5
|
+
formatSettings,
|
|
6
|
+
} from './format.js'
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// getKeyDescription
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
test('getKeyDescription returns description for known key', (t) => {
|
|
13
|
+
const desc = getKeyDescription('logLevel')
|
|
14
|
+
t.is(typeof desc, 'string')
|
|
15
|
+
t.true(desc.length > 0)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('getKeyDescription returns undefined for unknown key', (t) => {
|
|
19
|
+
t.is(getKeyDescription('unknownKey'), undefined)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// formatSettings (plain, useColor=false)
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
test('formatSettings includes header with config path', (t) => {
|
|
27
|
+
const output = formatSettings({}, { configPath: '/mock/path' })
|
|
28
|
+
t.true(output.includes('quire-cli configuration /mock/path'))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('formatSettings includes header without config path', (t) => {
|
|
32
|
+
const output = formatSettings({})
|
|
33
|
+
t.true(output.includes('quire-cli configuration'))
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('formatSettings includes key-value pairs', (t) => {
|
|
37
|
+
const store = { logLevel: 'info', logShowLevel: false }
|
|
38
|
+
const output = formatSettings(store)
|
|
39
|
+
t.true(output.includes('logLevel: "info"'))
|
|
40
|
+
t.true(output.includes('logShowLevel: false'))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('formatSettings includes schema descriptions', (t) => {
|
|
44
|
+
const store = { logLevel: 'info' }
|
|
45
|
+
const output = formatSettings(store)
|
|
46
|
+
const desc = getKeyDescription('logLevel')
|
|
47
|
+
t.true(output.includes(desc))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('formatSettings hides __internal__ keys by default', (t) => {
|
|
51
|
+
const store = { logLevel: 'info', __internal__secret: 'hidden' }
|
|
52
|
+
const output = formatSettings(store)
|
|
53
|
+
t.false(output.includes('__internal__secret'))
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('formatSettings shows __internal__ keys with showInternal', (t) => {
|
|
57
|
+
const store = { logLevel: 'info', __internal__secret: 'hidden' }
|
|
58
|
+
const output = formatSettings(store, { showInternal: true })
|
|
59
|
+
t.true(output.includes('__internal__secret'))
|
|
60
|
+
t.true(output.includes('"hidden"'))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('formatSettings includes help hint', (t) => {
|
|
64
|
+
const output = formatSettings({})
|
|
65
|
+
t.true(output.includes('Use "quire settings set <key> <value>" to change a setting'))
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// =============================================================================
|
|
69
|
+
// formatSettings (useColor=true)
|
|
70
|
+
// =============================================================================
|
|
71
|
+
|
|
72
|
+
test('formatSettings with useColor applies bold to header', (t) => {
|
|
73
|
+
const output = formatSettings({}, { useColor: true })
|
|
74
|
+
t.true(output.includes(chalk.bold('quire-cli configuration')))
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('formatSettings with useColor applies bold to header with path', (t) => {
|
|
78
|
+
const output = formatSettings({}, { useColor: true, configPath: '/mock/path' })
|
|
79
|
+
t.true(output.includes(chalk.bold('quire-cli configuration /mock/path')))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('formatSettings with useColor applies cyan to key names', (t) => {
|
|
83
|
+
const store = { logLevel: 'info' }
|
|
84
|
+
const output = formatSettings(store, { useColor: true })
|
|
85
|
+
t.true(output.includes(chalk.cyan('logLevel')))
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('formatSettings with useColor applies dim to descriptions', (t) => {
|
|
89
|
+
const store = { logLevel: 'info' }
|
|
90
|
+
const output = formatSettings(store, { useColor: true })
|
|
91
|
+
const desc = getKeyDescription('logLevel')
|
|
92
|
+
t.true(output.includes(chalk.dim(desc)))
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('formatSettings with useColor applies dim to help hint', (t) => {
|
|
96
|
+
const output = formatSettings({}, { useColor: true })
|
|
97
|
+
t.true(output.includes(chalk.dim('Use "quire settings set <key> <value>" to change a setting')))
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('formatSettings without useColor does not apply ANSI codes', (t) => {
|
|
101
|
+
const store = { logLevel: 'info' }
|
|
102
|
+
const output = formatSettings(store)
|
|
103
|
+
// Plain output should not contain ANSI escape sequences
|
|
104
|
+
// eslint-disable-next-line no-control-regex
|
|
105
|
+
t.false(/\u001b\[/.test(output))
|
|
106
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions for Quire configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides schema-aware validation and coercion
|
|
5
|
+
* without side effects or dependencies on the Conf instance.
|
|
6
|
+
*
|
|
7
|
+
* Nota bene: these functions import only schema and defaults (pure data),
|
|
8
|
+
* avoiding circular dependencies with the logger module.
|
|
9
|
+
*
|
|
10
|
+
* @see ./format.js for display formatting functions
|
|
11
|
+
*/
|
|
12
|
+
import schema from './schema.js'
|
|
13
|
+
import defaults from './defaults.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check whether a key is a valid configuration key
|
|
17
|
+
*
|
|
18
|
+
* @param {string} key - Configuration key to check
|
|
19
|
+
* @returns {boolean} True if the key exists in the schema
|
|
20
|
+
*/
|
|
21
|
+
export function isValidKey(key) {
|
|
22
|
+
return Object.hasOwn(schema, key)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get all valid configuration keys
|
|
27
|
+
*
|
|
28
|
+
* @returns {string[]} Sorted array of valid configuration keys
|
|
29
|
+
*/
|
|
30
|
+
export function getValidKeys() {
|
|
31
|
+
return Object.keys(schema).sort()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Coerce a CLI string value to the appropriate type based on schema
|
|
36
|
+
*
|
|
37
|
+
* @param {string} key - Configuration key
|
|
38
|
+
* @param {string} value - String value from CLI input
|
|
39
|
+
* @returns {*} Coerced value matching the schema type
|
|
40
|
+
*/
|
|
41
|
+
export function coerceValue(key, value) {
|
|
42
|
+
const keySchema = schema[key]
|
|
43
|
+
if (!keySchema) return value
|
|
44
|
+
|
|
45
|
+
switch (keySchema.type) {
|
|
46
|
+
case 'boolean':
|
|
47
|
+
if (value === 'true' || value === '1') return true
|
|
48
|
+
if (value === 'false' || value === '0') return false
|
|
49
|
+
return value
|
|
50
|
+
case 'number': {
|
|
51
|
+
const num = Number(value)
|
|
52
|
+
return isNaN(num) ? value : num
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format a validation error with helpful information from the schema
|
|
61
|
+
*
|
|
62
|
+
* @param {string} key - Configuration key
|
|
63
|
+
* @param {*} value - The invalid value
|
|
64
|
+
* @returns {string} Formatted multiline error message
|
|
65
|
+
*/
|
|
66
|
+
export function formatValidationError(key, value) {
|
|
67
|
+
const keySchema = schema[key]
|
|
68
|
+
const lines = [`Invalid value for '${key}': ${JSON.stringify(value)}`]
|
|
69
|
+
|
|
70
|
+
if (keySchema) {
|
|
71
|
+
if (keySchema.enum) {
|
|
72
|
+
lines.push(`Valid values: ${keySchema.enum.join(', ')}`)
|
|
73
|
+
}
|
|
74
|
+
if (keySchema.description) {
|
|
75
|
+
lines.push(`Description: ${keySchema.description}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return lines.join('\n')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the default value for a configuration key
|
|
84
|
+
*
|
|
85
|
+
* @param {string} key - Configuration key
|
|
86
|
+
* @returns {*} Default value, or undefined if key has no default
|
|
87
|
+
*/
|
|
88
|
+
export function getDefault(key) {
|
|
89
|
+
return defaults[key]
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
import {
|
|
3
|
+
isValidKey,
|
|
4
|
+
getValidKeys,
|
|
5
|
+
coerceValue,
|
|
6
|
+
formatValidationError,
|
|
7
|
+
getDefault,
|
|
8
|
+
} from './helpers.js'
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// isValidKey
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
test('isValidKey returns true for a known schema key', (t) => {
|
|
15
|
+
t.true(isValidKey('logLevel'))
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('isValidKey returns true for all schema keys', (t) => {
|
|
19
|
+
const keys = getValidKeys()
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
t.true(isValidKey(key), `${key} should be valid`)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('isValidKey returns false for an unknown key', (t) => {
|
|
26
|
+
t.false(isValidKey('nonExistentKey'))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('isValidKey returns false for __internal__ prefixed keys', (t) => {
|
|
30
|
+
t.false(isValidKey('__internal__secretKey'))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// getValidKeys
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
test('getValidKeys returns an array', (t) => {
|
|
38
|
+
const keys = getValidKeys()
|
|
39
|
+
t.true(Array.isArray(keys))
|
|
40
|
+
t.true(keys.length > 0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('getValidKeys returns sorted keys', (t) => {
|
|
44
|
+
const keys = getValidKeys()
|
|
45
|
+
const sorted = [...keys].sort()
|
|
46
|
+
t.deepEqual(keys, sorted)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('getValidKeys includes expected keys', (t) => {
|
|
50
|
+
const keys = getValidKeys()
|
|
51
|
+
t.true(keys.includes('logLevel'))
|
|
52
|
+
t.true(keys.includes('quireVersion'))
|
|
53
|
+
t.true(keys.includes('projectTemplate'))
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// coerceValue
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
test('coerceValue coerces "true" to boolean true', (t) => {
|
|
61
|
+
t.is(coerceValue('logShowLevel', 'true'), true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('coerceValue coerces "false" to boolean false', (t) => {
|
|
65
|
+
t.is(coerceValue('logShowLevel', 'false'), false)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('coerceValue coerces "1" to boolean true for boolean schema', (t) => {
|
|
69
|
+
t.is(coerceValue('logShowLevel', '1'), true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('coerceValue coerces "0" to boolean false for boolean schema', (t) => {
|
|
73
|
+
t.is(coerceValue('logShowLevel', '0'), false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('coerceValue passes through non-boolean string for boolean schema', (t) => {
|
|
77
|
+
t.is(coerceValue('logShowLevel', 'maybe'), 'maybe')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('coerceValue passes through string values for string schema', (t) => {
|
|
81
|
+
t.is(coerceValue('logLevel', 'debug'), 'debug')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('coerceValue passes through value for unknown key', (t) => {
|
|
85
|
+
t.is(coerceValue('unknownKey', 'someValue'), 'someValue')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// formatValidationError
|
|
90
|
+
// =============================================================================
|
|
91
|
+
|
|
92
|
+
test('formatValidationError includes invalid value', (t) => {
|
|
93
|
+
const error = formatValidationError('logLevel', 'invalid')
|
|
94
|
+
t.true(error.includes("Invalid value for 'logLevel'"))
|
|
95
|
+
t.true(error.includes('"invalid"'))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('formatValidationError includes valid enum values', (t) => {
|
|
99
|
+
const error = formatValidationError('logLevel', 'invalid')
|
|
100
|
+
t.true(error.includes('Valid values:'))
|
|
101
|
+
t.true(error.includes('info'))
|
|
102
|
+
t.true(error.includes('debug'))
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('formatValidationError includes description', (t) => {
|
|
106
|
+
const error = formatValidationError('logLevel', 'invalid')
|
|
107
|
+
t.true(error.includes('Description:'))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('formatValidationError handles key without enum', (t) => {
|
|
111
|
+
const error = formatValidationError('logPrefix', 123)
|
|
112
|
+
t.true(error.includes("Invalid value for 'logPrefix'"))
|
|
113
|
+
t.false(error.includes('Valid values:'))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('formatValidationError handles unknown key', (t) => {
|
|
117
|
+
const error = formatValidationError('unknownKey', 'value')
|
|
118
|
+
t.true(error.includes("Invalid value for 'unknownKey'"))
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// getDefault
|
|
123
|
+
// =============================================================================
|
|
124
|
+
|
|
125
|
+
test('getDefault returns default value for known key', (t) => {
|
|
126
|
+
t.is(getDefault('logLevel'), 'info')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('getDefault returns default for boolean key', (t) => {
|
|
130
|
+
t.is(getDefault('logShowLevel'), false)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('getDefault returns undefined for unknown key', (t) => {
|
|
134
|
+
t.is(getDefault('unknownKey'), undefined)
|
|
135
|
+
})
|
|
136
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quire configuration module
|
|
3
|
+
*
|
|
4
|
+
* Exports the singleton Conf instance as the default export
|
|
5
|
+
* and named helper functions for schema-aware operations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import config from '#lib/conf/index.js'
|
|
9
|
+
* import { isValidKey, coerceValue } from '#lib/conf/index.js'
|
|
10
|
+
*/
|
|
11
|
+
export { default as default } from './config.js'
|
|
12
|
+
export {
|
|
13
|
+
isValidKey,
|
|
14
|
+
getValidKeys,
|
|
15
|
+
coerceValue,
|
|
16
|
+
formatValidationError,
|
|
17
|
+
getDefault,
|
|
18
|
+
} from './helpers.js'
|
|
19
|
+
export {
|
|
20
|
+
getKeyDescription,
|
|
21
|
+
formatSettings,
|
|
22
|
+
} from './format.js'
|
package/src/lib/conf/schema.js
CHANGED
|
@@ -2,29 +2,74 @@
|
|
|
2
2
|
* Quire configuration schema
|
|
3
3
|
* @see https://github.com/sindresorhus/conf#schema
|
|
4
4
|
*
|
|
5
|
-
* Nota bene:
|
|
5
|
+
* Nota bene: default values here are overwritten by values in defaults.js
|
|
6
6
|
* @see https://github.com/sindresorhus/conf#defaults
|
|
7
7
|
*/
|
|
8
|
+
import { schema as epubEngineSchema } from '#lib/epub/schema.js'
|
|
9
|
+
import { schema as pdfEngineSchema } from '#lib/pdf/schema.js'
|
|
10
|
+
|
|
8
11
|
export default {
|
|
12
|
+
debug: {
|
|
13
|
+
type: 'boolean',
|
|
14
|
+
description: 'Enable debug output by default (equivalent to --debug flag)'
|
|
15
|
+
},
|
|
16
|
+
epubEngine: epubEngineSchema,
|
|
17
|
+
pdfEngine: pdfEngineSchema,
|
|
9
18
|
logLevel: {
|
|
10
|
-
type: 'string'
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['trace', 'debug', 'info', 'warn', 'error', 'silent'],
|
|
21
|
+
description: 'Minimum log level to display (trace, debug, info, warn, error, silent)'
|
|
22
|
+
},
|
|
23
|
+
logPrefix: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Prefix text shown before log messages'
|
|
26
|
+
},
|
|
27
|
+
logPrefixStyle: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
enum: ['bracket', 'emoji', 'plain', 'none'],
|
|
30
|
+
description: 'Style for displaying the log prefix (bracket: [quire], emoji: 📖, plain: quire:, none: no prefix)'
|
|
31
|
+
},
|
|
32
|
+
logShowLevel: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
description: 'Show log level label (INFO, WARN, etc.) in output'
|
|
35
|
+
},
|
|
36
|
+
logUseColor: {
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
description: 'Use colored output for log messages'
|
|
39
|
+
},
|
|
40
|
+
logColorMessages: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Color message text by log level (e.g., red for errors). Requires logUseColor'
|
|
11
43
|
},
|
|
12
44
|
projectTemplate: {
|
|
13
|
-
type: 'string'
|
|
45
|
+
type: 'string',
|
|
46
|
+
format: 'uri',
|
|
47
|
+
description: 'URL of the default project template for quire new'
|
|
14
48
|
},
|
|
15
49
|
quire11tyPath: {
|
|
16
|
-
type: 'string'
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Path to the quire-11ty package (use "." for local development)'
|
|
17
52
|
},
|
|
18
53
|
quireVersion: {
|
|
19
|
-
type: 'string'
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Version of quire-11ty to use ("latest" or specific version)'
|
|
20
56
|
},
|
|
21
57
|
updateChannel: {
|
|
22
|
-
type: 'string'
|
|
58
|
+
type: 'string',
|
|
59
|
+
enum: ['stable', 'rc', 'beta', 'alpha'],
|
|
60
|
+
description: 'Release channel for updates (stable, rc, beta, alpha)'
|
|
23
61
|
},
|
|
24
62
|
updateInterval: {
|
|
25
|
-
type: 'string'
|
|
63
|
+
type: 'string',
|
|
64
|
+
enum: ['DAILY', 'WEEKLY', 'MONTHLY', 'NEVER'],
|
|
65
|
+
description: 'How often to check for updates (DAILY, WEEKLY, MONTHLY, NEVER)'
|
|
66
|
+
},
|
|
67
|
+
verbose: {
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
description: 'Enable verbose output by default (equivalent to --verbose flag)'
|
|
26
70
|
},
|
|
27
71
|
versionFile: {
|
|
28
|
-
type: 'string'
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Filename used to identify Quire projects'
|
|
29
74
|
}
|
|
30
75
|
}
|
package/src/lib/epub/README.md
CHANGED
|
@@ -1,7 +1,138 @@
|
|
|
1
1
|
## CLI lib/EPUB Module
|
|
2
2
|
|
|
3
|
-
This module
|
|
3
|
+
This module provides an abstraction for EPUB e-book generation across Epub.js and Pandoc. The exported function accepts a `lib` option and dynamically loads the corresponding engine façade.
|
|
4
|
+
|
|
5
|
+
### Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌───────────────────────────────────────────────────────────┐
|
|
9
|
+
│ lib/epub/index.js (Façade) │
|
|
10
|
+
│ • Resolve engine (epubjs/pandoc) │
|
|
11
|
+
│ • Check engine availability (fail-fast) │
|
|
12
|
+
│ • Resolve output path (CLI > default) │
|
|
13
|
+
│ • Own reporter lifecycle (start/succeed/fail) │
|
|
14
|
+
│ • Dynamic import of engine implementation │
|
|
15
|
+
└───────────────────────────┬───────────────────────────────┘
|
|
16
|
+
│
|
|
17
|
+
┌─────────────┴─────────────────┐
|
|
18
|
+
│ lib/epub/engines.js │
|
|
19
|
+
│ • Central engine registry │
|
|
20
|
+
│ • Metadata (name, binary, │
|
|
21
|
+
│ toolInfo for errors) │
|
|
22
|
+
└─────────────┬─────────────────┘
|
|
23
|
+
│
|
|
24
|
+
┌────────────────┴────────────────────┐
|
|
25
|
+
│ │
|
|
26
|
+
┌──────────▼───────────┐ ┌───────────▼────────────┐
|
|
27
|
+
│ lib/epub/epub.js │ │ lib/epub/pandoc.js │
|
|
28
|
+
│ • Epub.js wrapper │ │ • Pandoc CLI wrapper │
|
|
29
|
+
│ • Load manifest │ │ • Filter XHTML files │
|
|
30
|
+
│ • Generate EPUB │ │ • Invoke pandoc │
|
|
31
|
+
│ • Write file │ │ • Write file │
|
|
32
|
+
└──────────────────────┘ └────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Usage
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import generateEpub from '#lib/epub/index.js'
|
|
39
|
+
|
|
40
|
+
// Generate EPUB with Epub.js (default)
|
|
41
|
+
const output = await generateEpub({
|
|
42
|
+
lib: 'epubjs',
|
|
43
|
+
output: 'my-book.epub',
|
|
44
|
+
reporter,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Generate EPUB with Pandoc
|
|
48
|
+
const output = await generateEpub({
|
|
49
|
+
lib: 'pandoc',
|
|
50
|
+
output: 'my-book.epub',
|
|
51
|
+
reporter,
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Options
|
|
56
|
+
|
|
57
|
+
| Option | Type | Default | Description |
|
|
58
|
+
|--------|------|---------|-------------|
|
|
59
|
+
| `lib` | string | `'epubjs'` | Engine to use: `'epubjs'` or `'pandoc'` |
|
|
60
|
+
| `output` | string | `'{lib}.epub'` | Output file path |
|
|
61
|
+
| `reporter` | object | required | Reporter instance for progress messages |
|
|
62
|
+
| `debug` | boolean | `false` | Enable debug output |
|
|
63
|
+
|
|
64
|
+
### Output Path Resolution
|
|
65
|
+
|
|
66
|
+
The output path is resolved in priority order:
|
|
67
|
+
1. CLI `--output` option value
|
|
68
|
+
2. Default: `{lib}.epub` (e.g., `epubjs.epub` or `pandoc.epub`)
|
|
69
|
+
|
|
70
|
+
Relative paths are resolved against the project root. The `.epub` extension is auto-added if missing.
|
|
4
71
|
|
|
5
72
|
### Epub.js Façade
|
|
6
73
|
|
|
7
|
-
|
|
74
|
+
Wraps the [`epubjs-cli`](https://github.com/futurepress/epubjs-cli) package for EPUB generation.
|
|
75
|
+
|
|
76
|
+
**How it works:**
|
|
77
|
+
1. Loads `manifest.json` from the build output directory
|
|
78
|
+
2. Uses `ManifestToEpub` to generate the EPUB content
|
|
79
|
+
3. Writes the generated file to the output path
|
|
80
|
+
|
|
81
|
+
See the [Epub.js documentation](https://github.com/futurepress/epub.js) for more details.
|
|
82
|
+
|
|
83
|
+
### Pandoc Façade
|
|
84
|
+
|
|
85
|
+
Wraps the [Pandoc](https://pandoc.org/) command-line tool for EPUB generation.
|
|
86
|
+
|
|
87
|
+
**How it works:**
|
|
88
|
+
1. Filters XHTML files from the build output
|
|
89
|
+
2. Invokes `pandoc` with appropriate options
|
|
90
|
+
3. Writes the generated EPUB to the output path
|
|
91
|
+
|
|
92
|
+
**Requirements:** Pandoc must be installed and available in PATH.
|
|
93
|
+
|
|
94
|
+
See the [Pandoc documentation](https://pandoc.org/MANUAL.html#creating-epubs-with-pandoc) for EPUB-specific options.
|
|
95
|
+
|
|
96
|
+
### Engine Availability Check
|
|
97
|
+
|
|
98
|
+
The façade performs a fail-fast check before starting the reporter:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Runs BEFORE reporter.start()
|
|
102
|
+
checkEngineAvailable(engine) // Throws ToolNotFoundError if binary missing
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- **Epub.js:** No binary check needed (pure JavaScript)
|
|
106
|
+
- **Pandoc:** Checks for `pandoc` in PATH
|
|
107
|
+
|
|
108
|
+
If the engine is unavailable, a `ToolNotFoundError` is thrown with:
|
|
109
|
+
- Install URL for the missing tool
|
|
110
|
+
- Fallback suggestion (use default engine)
|
|
111
|
+
- Link to documentation
|
|
112
|
+
|
|
113
|
+
### Error Handling
|
|
114
|
+
|
|
115
|
+
The module uses typed errors for consistent error handling:
|
|
116
|
+
|
|
117
|
+
| Error | When Thrown |
|
|
118
|
+
|-------|-------------|
|
|
119
|
+
| `InvalidEpubLibraryError` | Unknown engine name provided |
|
|
120
|
+
| `EpubGenerationError` | Generation fails (with tool/operation context) |
|
|
121
|
+
| `ToolNotFoundError` | Required binary not found in PATH |
|
|
122
|
+
|
|
123
|
+
### Test Strategy
|
|
124
|
+
|
|
125
|
+
| Component | Test Type | Coverage |
|
|
126
|
+
|-----------|-----------|----------|
|
|
127
|
+
| `commands/epub.js` | spec + integration | CLI interface, option handling |
|
|
128
|
+
| `lib/epub/index.js` | integration | Façade logic, path resolution, engine dispatch |
|
|
129
|
+
| `lib/epub/epub.js` | E2E | Happy path via full generation |
|
|
130
|
+
| `lib/epub/pandoc.js` | E2E | Happy path via full generation |
|
|
131
|
+
|
|
132
|
+
The engine implementations (`epub.js`, `pandoc.js`) are thin wrappers with limited logic, so E2E tests provide sufficient coverage. Error path tests would have lower ROI compared to the PDF module's `split.js`.
|
|
133
|
+
|
|
134
|
+
### Related Documentation
|
|
135
|
+
|
|
136
|
+
- [EPUB Command](../../commands/epub.js) - CLI command implementation
|
|
137
|
+
- [Reporter Pattern](../reporter/README.md) - Progress reporting ownership
|
|
138
|
+
- [Error Classes](../../errors/output/) - Typed error definitions
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPUB Engine Registry
|
|
3
|
+
*
|
|
4
|
+
* Centralized metadata for EPUB 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 QuireError from '#src/errors/quire-error.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} EngineMetadata
|
|
16
|
+
* @property {string} name - Human-readable display name
|
|
17
|
+
* @property {string} module - Module filename (e.g., 'epub.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
|
+
epubjs: {
|
|
29
|
+
name: 'Epub.js',
|
|
30
|
+
module: 'epub.js',
|
|
31
|
+
requiresBinary: null, // Built-in Node.js library
|
|
32
|
+
},
|
|
33
|
+
pandoc: {
|
|
34
|
+
name: 'Pandoc',
|
|
35
|
+
module: 'pandoc.js',
|
|
36
|
+
requiresBinary: 'pandoc',
|
|
37
|
+
toolInfo: {
|
|
38
|
+
displayName: 'Pandoc',
|
|
39
|
+
installUrl: 'https://pandoc.org/installing.html',
|
|
40
|
+
docsUrl: `${QuireError.DOCS_BASE}/epub-output/`,
|
|
41
|
+
fallback: 'Or use the default EPUB engine: quire epub --engine epubjs',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default ENGINES
|