@thegetty/quire-cli 1.0.0-rc.37 → 1.0.0-rc.39

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 (104) hide show
  1. package/package.json +5 -2
  2. package/src/commands/build.js +13 -6
  3. package/src/commands/clean.js +10 -2
  4. package/src/commands/clean.spec.js +10 -0
  5. package/src/commands/clean.test.js +122 -0
  6. package/src/commands/config.js +3 -1
  7. package/src/commands/config.test.js +42 -1
  8. package/src/commands/doctor.js +251 -0
  9. package/src/commands/doctor.spec.js +115 -0
  10. package/src/commands/doctor.test.js +1409 -0
  11. package/src/commands/epub.js +9 -2
  12. package/src/commands/help.js +60 -0
  13. package/src/commands/help.test.js +132 -0
  14. package/src/commands/info.js +79 -71
  15. package/src/commands/info.spec.js +8 -0
  16. package/src/commands/info.test.js +173 -76
  17. package/src/commands/pdf.js +10 -3
  18. package/src/commands/validate.js +39 -7
  19. package/src/commands/validate.spec.js +8 -0
  20. package/src/commands/validate.test.js +144 -0
  21. package/src/errors/help/help-topic-not-found-error.js +22 -0
  22. package/src/errors/help/index.js +8 -0
  23. package/src/errors/index.js +6 -0
  24. package/src/errors/input/index.js +8 -0
  25. package/src/errors/input/invalid-input-error.js +21 -0
  26. package/src/helpers/pager.js +59 -0
  27. package/src/helpers/pager.test.js +85 -0
  28. package/src/lib/README.md +77 -22
  29. package/src/lib/conf/README.md +6 -0
  30. package/src/lib/conf/build-status.js +103 -0
  31. package/src/lib/conf/build-status.test.js +247 -0
  32. package/src/lib/conf/defaults.js +14 -0
  33. package/src/lib/conf/format.js +1 -1
  34. package/src/lib/conf/schema.js +46 -1
  35. package/src/lib/constants.js +28 -0
  36. package/src/lib/doctor/README.md +667 -0
  37. package/src/lib/doctor/checks/environment/cli-version.js +62 -0
  38. package/src/lib/doctor/checks/environment/cli-version.test.js +132 -0
  39. package/src/lib/doctor/checks/environment/git-available.js +77 -0
  40. package/src/lib/doctor/checks/environment/git-available.test.js +52 -0
  41. package/src/lib/doctor/checks/environment/index.js +13 -0
  42. package/src/lib/doctor/checks/environment/node-version.js +66 -0
  43. package/src/lib/doctor/checks/environment/node-version.test.js +17 -0
  44. package/src/lib/doctor/checks/environment/npm-available.js +66 -0
  45. package/src/lib/doctor/checks/environment/npm-available.test.js +52 -0
  46. package/src/lib/doctor/checks/environment/os-info.js +48 -0
  47. package/src/lib/doctor/checks/environment/os-info.test.js +81 -0
  48. package/src/lib/doctor/checks/environment/runtime-info.js +51 -0
  49. package/src/lib/doctor/checks/environment/runtime-info.test.js +132 -0
  50. package/src/lib/doctor/checks/outputs/epub-output.js +119 -0
  51. package/src/lib/doctor/checks/outputs/epub-output.test.js +277 -0
  52. package/src/lib/doctor/checks/outputs/index.js +10 -0
  53. package/src/lib/doctor/checks/outputs/pdf-output.js +144 -0
  54. package/src/lib/doctor/checks/outputs/pdf-output.test.js +377 -0
  55. package/src/lib/doctor/checks/outputs/stale-build.js +122 -0
  56. package/src/lib/doctor/checks/outputs/stale-build.test.js +282 -0
  57. package/src/lib/doctor/checks/project/data-files.js +56 -0
  58. package/src/lib/doctor/checks/project/data-files.test.js +125 -0
  59. package/src/lib/doctor/checks/project/dependencies.js +53 -0
  60. package/src/lib/doctor/checks/project/dependencies.test.js +71 -0
  61. package/src/lib/doctor/checks/project/index.js +11 -0
  62. package/src/lib/doctor/checks/project/quire-11ty.js +98 -0
  63. package/src/lib/doctor/checks/project/quire-11ty.test.js +170 -0
  64. package/src/lib/doctor/checks/project/quire-project.js +38 -0
  65. package/src/lib/doctor/checks/project/quire-project.test.js +47 -0
  66. package/src/lib/doctor/checks/tools/index.js +10 -0
  67. package/src/lib/doctor/checks/tools/pandoc-available.js +82 -0
  68. package/src/lib/doctor/checks/tools/pandoc-available.test.js +73 -0
  69. package/src/lib/doctor/checks/tools/prince-available.js +81 -0
  70. package/src/lib/doctor/checks/tools/prince-available.test.js +73 -0
  71. package/src/lib/doctor/constants.js +39 -0
  72. package/src/lib/doctor/formatDuration.js +108 -0
  73. package/src/lib/doctor/formatDuration.test.js +76 -0
  74. package/src/lib/doctor/formatters/human.js +257 -0
  75. package/src/lib/doctor/formatters/human.test.js +463 -0
  76. package/src/lib/doctor/formatters/index.js +8 -0
  77. package/src/lib/doctor/formatters/json.js +78 -0
  78. package/src/lib/doctor/formatters/json.test.js +174 -0
  79. package/src/lib/doctor/formatters/shared.js +129 -0
  80. package/src/lib/doctor/formatters/shared.test.js +194 -0
  81. package/src/lib/doctor/index.js +271 -0
  82. package/src/lib/doctor/index.test.js +797 -0
  83. package/src/lib/help/frontmatter.js +77 -0
  84. package/src/lib/help/frontmatter.test.js +139 -0
  85. package/src/lib/help/index.js +135 -0
  86. package/src/lib/help/index.test.js +160 -0
  87. package/src/lib/help/topics/configuration.md +77 -0
  88. package/src/lib/help/topics/debugging.md +69 -0
  89. package/src/lib/help/topics/epub.md +74 -0
  90. package/src/lib/help/topics/pdf.md +74 -0
  91. package/src/lib/help/topics/publishing.md +80 -0
  92. package/src/lib/help/topics/workflows.md +50 -0
  93. package/src/lib/platform.js +95 -0
  94. package/src/lib/project/build.js +44 -25
  95. package/src/lib/project/build.test.js +30 -8
  96. package/src/lib/project/detect.js +16 -2
  97. package/src/lib/project/index.js +18 -2
  98. package/src/lib/project/output-paths.js +87 -0
  99. package/src/lib/project/output-paths.test.js +66 -0
  100. package/src/lib/project/paths.js +48 -0
  101. package/src/main.js +38 -3
  102. package/src/packageConfig.js +17 -0
  103. package/src/validators/validate-data-files.js +154 -0
  104. package/src/validators/validate-data-files.test.js +217 -0
@@ -5,6 +5,7 @@ import eleventy from '#lib/11ty/index.js'
5
5
  import fs from 'fs-extra'
6
6
  import generateEpub, { ENGINES } from '#lib/epub/index.js'
7
7
  import open from 'open'
8
+ import { recordStatus } from '#lib/conf/build-status.js'
8
9
  import reporter from '#lib/reporter/index.js'
9
10
  import testcwd from '#helpers/test-cwd.js'
10
11
  import { MissingBuildOutputError } from '#src/errors/index.js'
@@ -84,9 +85,15 @@ Examples:
84
85
  // Pass engine as lib (matching lib/pdf interface)
85
86
  // Reporter lifecycle is owned by the façade (lib/epub/index.js)
86
87
  const epubOptions = { ...options, lib: options.engine }
87
- const output = await generateEpub(epubOptions)
88
88
 
89
- if (fs.existsSync(output) && options.open) open(output)
89
+ try {
90
+ const output = await generateEpub(epubOptions)
91
+ recordStatus(paths.getProjectRoot(), 'epub', 'ok')
92
+ if (fs.existsSync(output) && options.open) open(output)
93
+ } catch (error) {
94
+ recordStatus(paths.getProjectRoot(), 'epub', 'failed')
95
+ throw error
96
+ }
90
97
  }
91
98
 
92
99
  preAction(thisCommand, actionCommand) {
@@ -0,0 +1,60 @@
1
+ import Command from '#src/Command.js'
2
+ import { outputWithPaging } from '#helpers/pager.js'
3
+ import { getTopicContent, getTopicList } from '#lib/help/index.js'
4
+
5
+ /**
6
+ * Quire CLI `help` Command
7
+ *
8
+ * Display help for a specific topic or list available topics.
9
+ * Complements Commander's built-in help with extended documentation.
10
+ *
11
+ * @example
12
+ * quire help # List available topics
13
+ * quire help workflows # Show workflows topic
14
+ * quire help --list # List available topics
15
+ *
16
+ * @class HelpCommand
17
+ * @extends {Command}
18
+ */
19
+ export default class HelpCommand extends Command {
20
+ static definition = {
21
+ name: 'help',
22
+ aliases: ['h'],
23
+ description: 'Display help for a topic or list available topics',
24
+ summary: 'show help for a topic',
25
+ docsLink: 'quire-commands/',
26
+ helpText: `
27
+ Examples:
28
+ quire help List available help topics
29
+ quire help workflows Show common workflow examples
30
+ quire help pdf Show PDF generation guide
31
+ quire help --list List all available topics
32
+ `,
33
+ version: '1.0.0',
34
+ args: [
35
+ ['[topic]', 'help topic to display']
36
+ ],
37
+ options: [
38
+ ['--list', 'list all available topics'],
39
+ ],
40
+ }
41
+
42
+ constructor() {
43
+ super(HelpCommand.definition)
44
+ }
45
+
46
+ async action(topic, options, command) {
47
+ this.debug('called with topic=%s options=%O', topic, options)
48
+
49
+ // List topics if no topic specified or --list flag
50
+ if (!topic || options.list) {
51
+ const list = await getTopicList()
52
+ process.stdout.write(list + '\n')
53
+ return
54
+ }
55
+
56
+ // Display requested topic with paging (throws HelpTopicNotFoundError if missing)
57
+ const content = await getTopicContent(topic)
58
+ await outputWithPaging(content)
59
+ }
60
+ }
@@ -0,0 +1,132 @@
1
+ import esmock from 'esmock'
2
+ import sinon from 'sinon'
3
+ import test from 'ava'
4
+
5
+ /**
6
+ * Help Command Integration Tests
7
+ *
8
+ * Tests the help command behavior with mocked dependencies.
9
+ */
10
+
11
+ test.serial('help command outputs topic list when called without arguments', async (t) => {
12
+ const sandbox = sinon.createSandbox()
13
+ const stdoutWrite = sandbox.stub(process.stdout, 'write')
14
+
15
+ try {
16
+ const mockList = 'Available help topics:\n debugging Troubleshooting help'
17
+
18
+ const HelpCommand = await esmock('./help.js', {
19
+ '#lib/help/index.js': {
20
+ getTopicList: sandbox.stub().resolves(mockList),
21
+ getTopicContent: sandbox.stub()
22
+ },
23
+ '#helpers/pager.js': {
24
+ outputWithPaging: sandbox.stub()
25
+ }
26
+ })
27
+
28
+ const command = new HelpCommand.default()
29
+ await command.action(undefined, {}, {})
30
+
31
+ t.true(stdoutWrite.calledOnce)
32
+ t.true(stdoutWrite.calledWith(mockList + '\n'))
33
+ } finally {
34
+ sandbox.restore()
35
+ }
36
+ })
37
+
38
+ test.serial('help command outputs topic list when --list flag is provided', async (t) => {
39
+ const sandbox = sinon.createSandbox()
40
+ const stdoutWrite = sandbox.stub(process.stdout, 'write')
41
+
42
+ try {
43
+ const mockList = 'Available help topics:\n pdf PDF generation'
44
+
45
+ const HelpCommand = await esmock('./help.js', {
46
+ '#lib/help/index.js': {
47
+ getTopicList: sandbox.stub().resolves(mockList),
48
+ getTopicContent: sandbox.stub()
49
+ },
50
+ '#helpers/pager.js': {
51
+ outputWithPaging: sandbox.stub()
52
+ }
53
+ })
54
+
55
+ const command = new HelpCommand.default()
56
+ await command.action('ignored', { list: true }, {})
57
+
58
+ t.true(stdoutWrite.calledOnce)
59
+ t.true(stdoutWrite.calledWith(mockList + '\n'))
60
+ } finally {
61
+ sandbox.restore()
62
+ }
63
+ })
64
+
65
+ test('help command outputs topic content with paging when topic is specified', async (t) => {
66
+ const sandbox = sinon.createSandbox()
67
+
68
+ try {
69
+ const mockRendered = 'Rendered workflows content'
70
+ const mockOutputWithPaging = sandbox.stub().resolves()
71
+
72
+ const HelpCommand = await esmock('./help.js', {
73
+ '#lib/help/index.js': {
74
+ getTopicList: sandbox.stub(),
75
+ getTopicContent: sandbox.stub().resolves(mockRendered)
76
+ },
77
+ '#helpers/pager.js': {
78
+ outputWithPaging: mockOutputWithPaging
79
+ }
80
+ })
81
+
82
+ const command = new HelpCommand.default()
83
+ await command.action('workflows', {}, {})
84
+
85
+ t.true(mockOutputWithPaging.calledOnce)
86
+ t.true(mockOutputWithPaging.calledWith(mockRendered))
87
+ } finally {
88
+ sandbox.restore()
89
+ }
90
+ })
91
+
92
+ test('help command propagates HelpTopicNotFoundError from getTopicContent', async (t) => {
93
+ const sandbox = sinon.createSandbox()
94
+
95
+ try {
96
+ const { default: HelpTopicNotFoundError } = await import('#src/errors/help/help-topic-not-found-error.js')
97
+
98
+ const HelpCommand = await esmock('./help.js', {
99
+ '#lib/help/index.js': {
100
+ getTopicList: sandbox.stub(),
101
+ getTopicContent: sandbox.stub().rejects(new HelpTopicNotFoundError('nonexistent'))
102
+ },
103
+ '#helpers/pager.js': {
104
+ outputWithPaging: sandbox.stub()
105
+ }
106
+ })
107
+
108
+ const command = new HelpCommand.default()
109
+
110
+ const error = await t.throwsAsync(
111
+ () => command.action('nonexistent', {}, {}),
112
+ { name: 'HelpTopicNotFoundError' }
113
+ )
114
+
115
+ t.is(error.code, 'HELP_TOPIC_NOT_FOUND')
116
+ t.is(error.exitCode, 2)
117
+ t.is(error.topic, 'nonexistent')
118
+ } finally {
119
+ sandbox.restore()
120
+ }
121
+ })
122
+
123
+ test('help command has correct definition', async (t) => {
124
+ const HelpCommand = await import('./help.js')
125
+ const command = new HelpCommand.default()
126
+
127
+ t.is(command.name, 'help')
128
+ t.deepEqual(command.aliases, ['h'])
129
+ t.truthy(command.description)
130
+ t.truthy(command.args)
131
+ t.truthy(command.options)
132
+ })
@@ -1,34 +1,52 @@
1
1
  import Command from '#src/Command.js'
2
- import npm from '#lib/npm/index.js'
3
- import { execa } from 'execa'
2
+ import { withOutputModes } from '#lib/commander/index.js'
3
+ import { binPath } from '#src/packageConfig.js'
4
4
  import fs from 'node:fs'
5
- import os from 'node:os'
6
5
  import path from 'node:path'
7
6
  import testcwd from '#helpers/test-cwd.js'
8
7
 
9
8
  /**
10
9
  * Quire CLI `info` Command
11
10
  *
12
- * Runs the Eleventy `info` command to list the quire-cli, quire-11ty, node, and npm versions.
11
+ * Display version information for the current Quire project.
12
+ * Shows the versions of quire-cli and quire-11ty that the project was
13
+ * created with, plus the starter template version.
14
+ *
15
+ * With --debug, also shows the full filesystem path to the quire-cli
16
+ * executable.
17
+ *
18
+ * For system environment information (OS, Node.js, npm, Git),
19
+ * use the `quire doctor` command instead.
13
20
  *
14
21
  * @class InfoCommand
15
22
  * @extends {Command}
16
23
  */
17
24
  export default class InfoCommand extends Command {
18
- static definition = {
25
+ static definition = withOutputModes({
19
26
  name: 'info',
20
- description: 'List Quire cli, quire-11ty, and node versions',
21
- summary: 'show version information',
27
+ description: 'Display version information for the current project',
28
+ summary: 'show project version information',
22
29
  docsLink: 'quire-commands/#get-help',
23
30
  helpText: `
31
+ Shows the versions used when this project was created:
32
+ • quire-cli version that created the project
33
+ • quire-11ty version installed in the project
34
+ • starter template version (if available)
35
+
36
+ For system environment checks (OS, Node.js, npm, Git),
37
+ use 'quire doctor' instead.
38
+
24
39
  Example:
25
- quire info --debug Include node, npm, and OS versions
40
+ quire info Show project versions
41
+ quire info --debug Include installation paths
42
+ quire info --json Output version information as JSON
43
+ quire doctor Check environment and project health
26
44
  `,
27
45
  version: '1.0.0',
28
46
  options: [
29
- ['--debug', 'include os versions in output']
47
+ ['--json', 'output version information as JSON'],
30
48
  ],
31
- }
49
+ })
32
50
 
33
51
  constructor() {
34
52
  super(InfoCommand.definition)
@@ -43,79 +61,69 @@ Example:
43
61
 
44
62
  try {
45
63
  const versionFileData = fs.readFileSync(versionFileName, { encoding: 'utf8' })
46
-
47
- versionInfo = JSON.parse(versionFileData)
64
+ versionInfo = JSON.parse(versionFileData)
48
65
  } catch (error) {
49
66
  this.logger.warn(
50
67
  `This project was generated with the quire-cli prior to version 1.0.0.rc-8. Updating the version file to the new format, though this project's version file will not contain specific starter version information.`
51
68
  )
52
-
53
69
  fs.writeFileSync(versionFileName, JSON.stringify(versionInfo))
54
70
  }
55
71
 
56
72
  const { name: projectDirectory } = path.parse(process.cwd())
57
73
 
58
- const versions = [
59
- {
60
- title: `[${projectDirectory}]`,
61
- items: [
62
- {
63
- name: 'quire-cli',
64
- get: () => versionInfo.cli,
65
- },
66
- {
67
- name: 'quire-11ty',
68
- get: () => {
69
- const { version } = JSON.parse(fs.readFileSync('./package.json'))
70
- return version
71
- },
72
- },
73
- {
74
- name: 'starter',
75
- get: () => versionInfo.starter,
76
- },
77
- ],
78
- },
79
- {
80
- title: '[System]',
81
- items: [
82
- {
83
- name: 'quire-cli',
84
- get: async () => {
85
- const { stdout } = await execa('quire', ['--version'])
86
- return stdout
87
- },
88
- },
89
- {
90
- debug: true,
91
- name: 'node',
92
- get: () => process.version,
93
- },
94
- {
95
- debug: true,
96
- name: 'npm',
97
- get: async () => await npm.version(),
98
- },
99
- {
100
- debug: true,
101
- name: 'os',
102
- get: () => `${os.type()} ${os.release()}`,
103
- },
104
- ],
105
- },
74
+ // Read quire-11ty version from project's package.json
75
+ let quire11tyVersion = 'unknown'
76
+ try {
77
+ const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
78
+ quire11tyVersion = packageJson.version
79
+ } catch {
80
+ this.debug('Could not read package.json')
81
+ }
82
+
83
+ if (options.json) {
84
+ const result = {
85
+ project: {
86
+ directory: projectDirectory,
87
+ cli: versionInfo.cli,
88
+ quire11ty: quire11tyVersion,
89
+ starter: versionInfo.starter || null,
90
+ },
91
+ }
92
+ console.log(JSON.stringify(result, null, 2))
93
+ return
94
+ }
95
+
96
+ const lines = [
97
+ `Project: ${projectDirectory}`,
98
+ '',
99
+ ` quire-cli ${versionInfo.cli || 'unknown'}`,
100
+ ` quire-11ty ${quire11tyVersion}`,
106
101
  ]
107
102
 
108
- /**
109
- * Filter the command output based on `debug` settings
110
- */
111
- for (const { items, title } of versions) {
112
- const versionList = await Promise.all(
113
- items
114
- .filter(({ debug }) => !debug || (options.debug && debug))
115
- .map(async ({ name, get }) => `${name} ${await get()}`)
116
- )
117
- this.logger.info(`${title}\n ${versionList.join('\n ')}`)
103
+ if (versionInfo.starter) {
104
+ lines.push(` starter ${versionInfo.starter}`)
105
+ }
106
+
107
+ if (options.debug) {
108
+ lines.push('')
109
+ lines.push(this.resolveCliPath())
118
110
  }
111
+
112
+ lines.push('')
113
+ lines.push('Tip: Run \'quire doctor\' for system environment checks')
114
+
115
+ this.logger.info(lines.join('\n'))
116
+ }
117
+
118
+ /**
119
+ * Resolve the full filesystem path to the quire CLI executable
120
+ * @returns {string} Formatted path line
121
+ */
122
+ resolveCliPath() {
123
+ const resolved = binPath()
124
+ return resolved
125
+ ? ` quire-cli ${resolved}`
126
+ : ' quire-cli not found in PATH'
119
127
  }
120
128
 
121
129
  preAction(thisCommand, actionCommand) {
@@ -40,15 +40,22 @@ test('registered command has correct options', (t) => {
40
40
  const { command } = t.context
41
41
 
42
42
  // Get all options
43
+ const jsonOption = command.options.find((opt) => opt.long === '--json')
43
44
  const debugOption = command.options.find((opt) => opt.long === '--debug')
44
45
 
45
46
  // Verify all options exist
47
+ t.truthy(jsonOption, '--json option should exist')
46
48
  t.truthy(debugOption, '--debug option should exist')
47
49
 
48
50
  // Verify they are Option instances
51
+ t.true(jsonOption instanceof Option, '--json should be Option instance')
49
52
  t.true(debugOption instanceof Option, '--debug should be Option instance')
50
53
 
51
54
  // Verify option properties
55
+ t.is(jsonOption.long, '--json')
56
+ t.truthy(jsonOption.description)
57
+ t.false(jsonOption.required, '--json should not require a value')
58
+
52
59
  t.is(debugOption.long, '--debug')
53
60
  t.truthy(debugOption.description)
54
61
  t.false(debugOption.required, '--debug should not require a value')
@@ -60,5 +67,6 @@ test('command options are accessible via public API', (t) => {
60
67
  // Test that options can be accessed the way Commander.js does
61
68
  const optionNames = command.options.map((opt) => opt.long)
62
69
 
70
+ t.true(optionNames.includes('--json'))
63
71
  t.true(optionNames.includes('--debug'))
64
72
  })