@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.
Files changed (158) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +9 -0
  3. package/bin/cli.js +19 -1
  4. package/package.json +21 -8
  5. package/patches/README.md +19 -0
  6. package/patches/install-npm-version+1.0.9.patch +12119 -0
  7. package/schemas/objects.schema.json +12 -38
  8. package/schemas/publication.schema.json +0 -22
  9. package/schemas/references.schema.json +0 -1
  10. package/src/Command.js +26 -6
  11. package/src/Command.spec.js +99 -0
  12. package/src/commands/README.md +213 -122
  13. package/src/commands/build.js +46 -37
  14. package/src/commands/build.spec.js +113 -0
  15. package/src/commands/build.test.js +402 -0
  16. package/src/commands/clean.js +25 -19
  17. package/src/commands/clean.spec.js +108 -0
  18. package/src/commands/clean.test.js +260 -0
  19. package/src/commands/config.js +251 -0
  20. package/src/commands/config.spec.js +107 -0
  21. package/src/commands/config.test.js +715 -0
  22. package/src/commands/create.js +42 -14
  23. package/src/commands/create.spec.js +112 -0
  24. package/src/commands/create.test.js +415 -0
  25. package/src/commands/epub.js +59 -30
  26. package/src/commands/epub.spec.js +114 -0
  27. package/src/commands/epub.test.js +503 -0
  28. package/src/commands/index.js +9 -2
  29. package/src/commands/info.js +18 -24
  30. package/src/commands/info.spec.js +64 -0
  31. package/src/commands/info.test.js +415 -0
  32. package/src/commands/pdf.js +58 -84
  33. package/src/commands/pdf.spec.js +114 -0
  34. package/src/commands/pdf.test.js +464 -0
  35. package/src/commands/preview.js +26 -31
  36. package/src/commands/preview.spec.js +97 -0
  37. package/src/commands/preview.test.js +250 -0
  38. package/src/commands/use.js +56 -0
  39. package/src/commands/use.spec.js +61 -0
  40. package/src/commands/use.test.js +280 -0
  41. package/src/commands/validate.js +31 -19
  42. package/src/commands/validate.spec.js +82 -0
  43. package/src/commands/validate.test.js +234 -0
  44. package/src/commands/workflows.js +70 -0
  45. package/src/errors/build/build-failed-error.js +19 -0
  46. package/src/errors/build/config-field-missing-error.js +20 -0
  47. package/src/errors/build/config-file-not-found-error.js +20 -0
  48. package/src/errors/build/index.js +11 -0
  49. package/src/errors/index.js +48 -0
  50. package/src/errors/install/dependency-install-error.js +19 -0
  51. package/src/errors/install/directory-not-empty-error.js +25 -0
  52. package/src/errors/install/index.js +12 -0
  53. package/src/errors/install/invalid-path-error.js +31 -0
  54. package/src/errors/install/invalid-starter-error.js +27 -0
  55. package/src/errors/install/version-not-found-error.js +35 -0
  56. package/src/errors/output/epub-generation-error.js +19 -0
  57. package/src/errors/output/index.js +14 -0
  58. package/src/errors/output/invalid-epub-library-error.js +20 -0
  59. package/src/errors/output/invalid-pdf-library-error.js +20 -0
  60. package/src/errors/output/missing-build-output-error.js +21 -0
  61. package/src/errors/output/pdf-generation-error.js +24 -0
  62. package/src/errors/output/tool-not-found-error.js +37 -0
  63. package/src/errors/project/index.js +10 -0
  64. package/src/errors/project/not-in-project-error.js +20 -0
  65. package/src/errors/project/project-create-error.js +21 -0
  66. package/src/errors/quire-error.js +27 -0
  67. package/src/errors/validation/validation-error.js +20 -11
  68. package/src/helpers/clean.js +1 -1
  69. package/src/helpers/docs-url.js +32 -0
  70. package/src/helpers/test-cwd.js +5 -6
  71. package/src/helpers/test-cwd.test.js +192 -0
  72. package/src/helpers/which.js +10 -4
  73. package/src/lib/11ty/README.md +135 -19
  74. package/src/lib/11ty/api.js +176 -93
  75. package/src/lib/11ty/cli.js +77 -35
  76. package/src/lib/11ty/index.js +64 -5
  77. package/src/lib/11ty/index.test.js +655 -0
  78. package/src/lib/README.md +275 -0
  79. package/src/lib/commander/index.js +100 -0
  80. package/src/lib/commander/index.test.js +86 -0
  81. package/src/lib/commander/options.js +195 -0
  82. package/src/lib/commander/options.test.js +109 -0
  83. package/src/lib/conf/README.md +84 -73
  84. package/src/lib/conf/config.js +5 -3
  85. package/src/lib/conf/config.test.js +281 -0
  86. package/src/lib/conf/defaults.js +44 -0
  87. package/src/lib/conf/format.js +60 -0
  88. package/src/lib/conf/format.test.js +106 -0
  89. package/src/lib/conf/helpers.js +91 -0
  90. package/src/lib/conf/helpers.test.js +136 -0
  91. package/src/lib/conf/index.js +22 -0
  92. package/src/lib/conf/schema.js +53 -8
  93. package/src/lib/epub/README.md +133 -2
  94. package/src/lib/epub/engines.js +46 -0
  95. package/src/lib/epub/epub.js +36 -11
  96. package/src/lib/epub/index.js +111 -21
  97. package/src/lib/epub/index.test.js +518 -0
  98. package/src/lib/epub/pandoc.js +33 -4
  99. package/src/lib/epub/pandoc.test.js +122 -0
  100. package/src/lib/epub/schema.js +21 -0
  101. package/src/lib/error/README.md +170 -0
  102. package/src/lib/error/handler.js +107 -0
  103. package/src/lib/git/README.md +151 -2
  104. package/src/lib/git/index.js +217 -13
  105. package/src/lib/git/index.spec.js +80 -0
  106. package/src/lib/git/index.test.js +453 -0
  107. package/src/lib/installer/index.js +309 -0
  108. package/src/lib/installer/index.spec.js +83 -0
  109. package/src/lib/installer/index.test.js +545 -0
  110. package/src/lib/logger/README.md +424 -0
  111. package/src/lib/logger/debug.js +90 -0
  112. package/src/lib/logger/debug.spec.js +130 -0
  113. package/src/lib/logger/index.js +228 -0
  114. package/src/lib/logger/index.spec.js +131 -0
  115. package/src/lib/logger/index.test.js +477 -0
  116. package/src/lib/npm/README.md +127 -0
  117. package/src/lib/npm/index.js +198 -0
  118. package/src/lib/npm/index.spec.js +60 -0
  119. package/src/lib/npm/index.test.js +355 -0
  120. package/src/lib/pdf/README.md +131 -0
  121. package/src/lib/pdf/engines.js +46 -0
  122. package/src/lib/pdf/index.js +124 -21
  123. package/src/lib/pdf/index.test.js +708 -0
  124. package/src/lib/pdf/paged.js +100 -52
  125. package/src/lib/pdf/paged.test.js +366 -0
  126. package/src/lib/pdf/prince.js +115 -37
  127. package/src/lib/pdf/prince.test.js +202 -0
  128. package/src/lib/pdf/schema.js +21 -0
  129. package/src/lib/pdf/split.js +61 -33
  130. package/src/lib/pdf/split.test.js +445 -0
  131. package/src/lib/process/manager.js +110 -0
  132. package/src/lib/process/manager.test.js +55 -0
  133. package/src/lib/project/build.js +143 -0
  134. package/src/lib/project/build.test.js +253 -0
  135. package/src/lib/project/config.js +48 -0
  136. package/src/lib/project/config.test.js +134 -0
  137. package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
  138. package/src/lib/project/detect.test.js +157 -0
  139. package/src/lib/project/index.js +40 -0
  140. package/src/lib/project/paths.js +224 -0
  141. package/src/lib/project/version.js +110 -0
  142. package/src/lib/project/version.test.js +350 -0
  143. package/src/lib/reporter/README.md +211 -2
  144. package/src/lib/reporter/index.js +512 -0
  145. package/src/lib/reporter/index.test.js +593 -0
  146. package/src/main.js +134 -47
  147. package/src/main.spec.js +61 -0
  148. package/src/main.test.js +347 -0
  149. package/src/validators/utils.js +2 -1
  150. package/src/commands/conf.js +0 -43
  151. package/src/commands/version.js +0 -43
  152. package/src/lib/11ty/paths.js +0 -103
  153. package/src/lib/i18n/README.md +0 -3
  154. package/src/lib/i18n/config.js +0 -53
  155. package/src/lib/i18n/index.js +0 -43
  156. package/src/lib/i18n/localeService.js +0 -58
  157. package/src/lib/quire/README.md +0 -19
  158. package/src/lib/quire/index.js +0 -350
@@ -1,12 +1,16 @@
1
1
  import Command from '#src/Command.js'
2
- import { paths, projectRoot } from '#lib/11ty/index.js'
2
+ import { withOutputModes } from '#lib/commander/index.js'
3
+ import paths, { hasEpubOutput } from '#lib/project/index.js'
4
+ import eleventy from '#lib/11ty/index.js'
3
5
  import fs from 'fs-extra'
4
- import libEpub from '#lib/epub/index.js'
6
+ import generateEpub, { ENGINES } from '#lib/epub/index.js'
5
7
  import open from 'open'
6
- import path from 'node:path'
8
+ import reporter from '#lib/reporter/index.js'
9
+ import testcwd from '#helpers/test-cwd.js'
10
+ import { MissingBuildOutputError } from '#src/errors/index.js'
7
11
 
8
12
  /**
9
- * Quire CLI `build epub` Command
13
+ * Quire CLI `epub` Command
10
14
  *
11
15
  * Generate EPUB from Eleventy `build` output.
12
16
  *
@@ -14,53 +18,78 @@ import path from 'node:path'
14
18
  * @extends {Command}
15
19
  */
16
20
  export default class EpubCommand extends Command {
17
- static definition = {
21
+ static definition = withOutputModes({
18
22
  name: 'epub',
19
23
  description: 'Generate publication EPUB',
20
- summary: 'run build epub',
24
+ summary: 'generate EPUB e-book',
25
+ docsLink: 'quire-commands/#output-files',
26
+ helpText: `
27
+ Examples:
28
+ quire epub Generate EPUB using default engine
29
+ quire epub --engine pandoc Generate EPUB using Pandoc
30
+ quire epub --open Generate and open EPUB
31
+ quire epub --build Build site first, then generate EPUB
32
+ quire epub --output my-book.epub Generate EPUB with custom output path
33
+ `,
21
34
  version: '1.0.0',
22
- args: [],
23
35
  options: [
36
+ [ '--build', 'run build first if output is missing' ],
37
+ [ '--open', 'open EPUB in default application' ],
38
+ [ '-o, --output <path>', 'output file path (default: {engine}.epub)' ],
24
39
  [
25
- '--lib <module>', 'use the specified epub library', 'epubjs',
26
- { choices: ['epubjs', 'pandoc'], default: 'epubjs' }
40
+ '--engine <name>', 'EPUB engine to use (default: from config or epubjs)',
41
+ { choices: ENGINES }
42
+ ],
43
+ [
44
+ '--lib <name>', 'deprecated alias for --engine option',
45
+ { hidden: true, choices: ENGINES, conflicts: 'engine' }
27
46
  ],
28
- [ '--open', 'open EPUB in default application' ],
29
- [ '--debug', 'run epub with debug output' ],
30
47
  ],
31
- }
48
+ })
32
49
 
33
50
  constructor() {
34
51
  super(EpubCommand.definition)
35
52
  }
36
53
 
37
54
  async action(options, command) {
38
- if (options.debug) {
39
- console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
40
- }
55
+ this.debug('called with options %O', options)
41
56
 
42
- const input = path.join(projectRoot, paths.epub)
57
+ // Configure reporter for this command
58
+ reporter.configure({ quiet: options.quiet, verbose: options.verbose })
43
59
 
44
- if (!fs.existsSync(input)) {
45
- console.error(`Unable to find Epub input at ${input}\nPlease first run the 'quire build' command.`)
46
- return
60
+ // Resolve engine: CLI --engine > deprecated --lib > config epubEngine > default
61
+ if (!options.engine) {
62
+ if (options.lib) {
63
+ // Support deprecated --lib option
64
+ options.engine = options.lib
65
+ } else {
66
+ // Use config setting or fallback to default
67
+ options.engine = this.config.get('epubEngine') || 'epubjs'
68
+ }
47
69
  }
48
70
 
49
- const output = path.join(projectRoot, `${options.lib}.epub`)
71
+ // Run build first if --build flag is set and output is missing
72
+ if (options.build && !hasEpubOutput()) {
73
+ this.debug('running build before epub generation')
74
+ reporter.start('Building site...', { showElapsed: true })
75
+ await eleventy.build({ debug: options.debug })
76
+ }
50
77
 
51
- const epubLib = await libEpub(options.lib, { debug: options.debug })
52
- await epubLib(input, output)
78
+ // Check for build output (will throw if missing)
79
+ // TODO: Add interactive prompt when build output missing and --build not used
80
+ if (!hasEpubOutput()) {
81
+ throw new MissingBuildOutputError('EPUB', paths.getEpubDir())
82
+ }
83
+
84
+ // Pass engine as lib (matching lib/pdf interface)
85
+ // Reporter lifecycle is owned by the façade (lib/epub/index.js)
86
+ const epubOptions = { ...options, lib: options.engine }
87
+ const output = await generateEpub(epubOptions)
53
88
 
54
89
  if (fs.existsSync(output) && options.open) open(output)
55
90
  }
56
91
 
57
- /**
58
- * @todo test if build site has already be run and output can be reused
59
- */
60
- preAction(command) {
61
- const options = command.opts()
62
- if (options.debug) {
63
- console.debug('[CLI] Calling \'epub\' command pre-action with options', options)
64
- }
92
+ preAction(thisCommand, actionCommand) {
93
+ testcwd(thisCommand)
65
94
  }
66
95
  }
@@ -0,0 +1,114 @@
1
+ import { Command, Option } from 'commander'
2
+ import program from '#src/main.js'
3
+ import test from 'ava'
4
+
5
+ /**
6
+ * Command Contract/Interface Tests
7
+ *
8
+ * Verifies the command's public API and Commander.js integration.
9
+ * @see docs/testing-commands.md
10
+ */
11
+
12
+ test.before((t) => {
13
+ // Get the registered command (from program.commands) once and share across all tests
14
+ t.context.command = program.commands.find((cmd) => cmd.name() === 'epub')
15
+ })
16
+
17
+ test('command is registered in CLI program', (t) => {
18
+ const { command } = t.context
19
+
20
+ t.truthy(command, 'command "epub" should be registered in program')
21
+ t.true(command instanceof Command, 'registered command should be Commander.js Command instance')
22
+ })
23
+
24
+ test('registered command has correct metadata', (t) => {
25
+ const { command } = t.context
26
+
27
+ t.is(command.name(), 'epub')
28
+ t.truthy(command.description())
29
+ t.is(typeof command._actionHandler, 'function', 'command should have action handler')
30
+ })
31
+
32
+ test('registered command has no arguments', (t) => {
33
+ const { command } = t.context
34
+ const registeredArguments = command.registeredArguments
35
+
36
+ t.is(registeredArguments.length, 0, 'epub command should have no arguments')
37
+ })
38
+
39
+ test('registered command has correct options', (t) => {
40
+ const { command } = t.context
41
+
42
+ // Get all options
43
+ const engineOption = command.options.find((opt) => opt.long === '--engine')
44
+ const libOption = command.options.find((opt) => opt.long === '--lib')
45
+ const buildOption = command.options.find((opt) => opt.long === '--build')
46
+ const openOption = command.options.find((opt) => opt.long === '--open')
47
+ const quietOption = command.options.find((opt) => opt.long === '--quiet')
48
+ const verboseOption = command.options.find((opt) => opt.long === '--verbose')
49
+ const debugOption = command.options.find((opt) => opt.long === '--debug')
50
+
51
+ // Verify all options exist
52
+ t.truthy(engineOption, '--engine option should exist')
53
+ t.truthy(libOption, '--lib option should exist (deprecated alias)')
54
+ t.truthy(buildOption, '--build option should exist')
55
+ t.truthy(openOption, '--open option should exist')
56
+ t.truthy(quietOption, '--quiet option should exist')
57
+ t.truthy(verboseOption, '--verbose option should exist')
58
+ t.truthy(debugOption, '--debug option should exist')
59
+
60
+ // Verify they are Option instances
61
+ t.true(engineOption instanceof Option, '--engine should be Option instance')
62
+ t.true(libOption instanceof Option, '--lib should be Option instance')
63
+ t.true(buildOption instanceof Option, '--build should be Option instance')
64
+ t.true(openOption instanceof Option, '--open should be Option instance')
65
+ t.true(quietOption instanceof Option, '--quiet should be Option instance')
66
+ t.true(verboseOption instanceof Option, '--verbose should be Option instance')
67
+ t.true(debugOption instanceof Option, '--debug should be Option instance')
68
+
69
+ // Verify --engine option properties
70
+ t.is(engineOption.long, '--engine')
71
+ t.truthy(engineOption.description)
72
+ t.true(engineOption.required, '--engine should require a value')
73
+
74
+ // Verify --lib is hidden (deprecated alias)
75
+ t.is(libOption.long, '--lib')
76
+ t.true(libOption.hidden, '--lib should be hidden (deprecated)')
77
+
78
+ t.is(buildOption.long, '--build')
79
+ t.truthy(buildOption.description)
80
+ t.false(buildOption.required, '--build should not require a value')
81
+
82
+ t.is(openOption.long, '--open')
83
+ t.truthy(openOption.description)
84
+ t.false(openOption.required, '--open should not require a value')
85
+
86
+ t.is(quietOption.long, '--quiet')
87
+ t.is(quietOption.short, '-q', '--quiet should have -q short flag')
88
+ t.truthy(quietOption.description)
89
+ t.false(quietOption.required, '--quiet should not require a value')
90
+
91
+ t.is(verboseOption.long, '--verbose')
92
+ t.is(verboseOption.short, '-v', '--verbose should have -v short flag')
93
+ t.truthy(verboseOption.description)
94
+ t.false(verboseOption.required, '--verbose should not require a value')
95
+
96
+ t.is(debugOption.long, '--debug')
97
+ t.truthy(debugOption.description)
98
+ t.false(debugOption.required, '--debug should not require a value')
99
+ })
100
+
101
+ test('command options are accessible via public API', (t) => {
102
+ const { command } = t.context
103
+
104
+ // Test that options can be accessed the way Commander.js does
105
+ const optionNames = command.options.map((opt) => opt.long)
106
+
107
+ t.true(optionNames.includes('--engine'))
108
+ t.true(optionNames.includes('--lib')) // deprecated alias still exists
109
+ t.true(optionNames.includes('--build'))
110
+ t.true(optionNames.includes('--open'))
111
+ t.true(optionNames.includes('--quiet'))
112
+ t.true(optionNames.includes('--verbose'))
113
+ t.true(optionNames.includes('--debug'))
114
+ })