@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,201 +1,292 @@
1
- ## Quire CLI Commands
1
+ # Quire CLI Commands
2
2
 
3
- ### `build`
3
+ This directory contains the Quire CLI command implementations and their corresponding tests.
4
4
 
5
- Build Quire publication outputs.
5
+ ## Command Class Pattern
6
6
 
7
- ```sh
8
- quire build
9
- ```
7
+ All Quire CLI commands extend the base `Command` class and follow a consistent pattern:
10
8
 
11
- #### `epub`
9
+ ### Command Structure
12
10
 
13
- Build Quire publication EPUB format.
11
+ ```javascript
12
+ import Command from '#src/Command.js'
14
13
 
15
- ```sh
16
- quire build epub
17
- ```
14
+ export default class MyCommand extends Command {
15
+ static definition = {
16
+ name: 'my-command',
17
+ description: 'Description of what the command does',
18
+ summary: 'short summary for help text',
19
+ version: '1.0.0',
20
+ args: [
21
+ ['<required-arg>', 'description of required argument'],
22
+ ['[optional-arg]', 'description of optional argument', 'default-value']
23
+ ],
24
+ options: [
25
+ ['-f', '--flag', 'description of flag'],
26
+ ['-o', '--option <value>', 'description of option with value', 'default']
27
+ ]
28
+ }
18
29
 
19
- #### `info`
30
+ constructor() {
31
+ super(MyCommand.definition)
32
+ }
20
33
 
21
- List `quire-11ty`, `quire-cli`, and starter package versions; use the `--debug` option to include node, npm, and os versions.
34
+ async action(args, options, command) {
35
+ // Use this.debug for developer debugging (controlled by DEBUG env var)
36
+ this.debug('called with options %O', options)
22
37
 
23
- ```sh
24
- quire build info
25
- ```
38
+ // Use this.logger for user-facing output
39
+ this.logger.info('Starting operation...')
26
40
 
27
- #### `pdf`
41
+ // Command implementation
42
+ }
28
43
 
29
- Build Quire publication PDF format.
44
+ preAction(command) {
45
+ // Optional: runs before action()
46
+ }
30
47
 
31
- ```sh
32
- quire build pdf
48
+ postAction(command) {
49
+ // Optional: runs after action()
50
+ }
51
+ }
33
52
  ```
34
53
 
35
- #### `site`
54
+ ### Inherited Properties
36
55
 
37
- Build Quire publication HTML format.
56
+ The base `Command` class provides these properties to all commands:
38
57
 
39
- ```sh
40
- quire build site
41
- ```
58
+ | Property | Type | Description |
59
+ |----------|------|-------------|
60
+ | `this.logger` | Logger | User-facing output with prefix `commands:{name}` |
61
+ | `this.debug` | Function | Developer debugging via `DEBUG` env var |
62
+ | `this.config` | Conf | CLI configuration store |
42
63
 
43
- ### `clean`
64
+ #### Using Logger vs Debug
44
65
 
45
- Remove build outputs.
66
+ ```javascript
67
+ // Developer debugging - only shown when DEBUG=quire:commands:* is set
68
+ this.debug('processing file: %s', filename)
69
+ this.debug('options: %O', options)
46
70
 
47
- Note that this command is distinct from the [quire/11ty package](https://github.com/thegetty/quire/packages/11ty/package.json) script `clean`, to allow different behavoirs for Quire editors and developers.
48
-
49
- ```sh
50
- quire clean --dry-run
71
+ // User-facing output - always shown (unless silenced by log level)
72
+ this.logger.info('Building PDF...')
73
+ this.logger.warn('Deprecated option used')
74
+ this.logger.error('Build failed: %s', error.message)
51
75
  ```
52
76
 
53
- ### `configure` **not yet implemented**
77
+ ### File Organization
54
78
 
55
- Edit the Quire CLI configuration.
79
+ For each command, you'll find up to three types of files:
56
80
 
57
- ```sh
58
- quire configure
81
+ ```
82
+ src/commands/
83
+ ├── my-command.js # Command implementation
84
+ ├── my-command.spec.js # Unit tests (structure, options, definitions)
85
+ └── my-command.test.js # Integration tests (functionality with mocked deps)
59
86
  ```
60
87
 
61
- ### `install` **not yet implemented**
88
+ ## Testing Strategy
62
89
 
63
- Clone an existing Quire project from a git repository.
90
+ The Quire CLI uses a comprehensive three-tier testing strategy to ensure reliable command functionality:
64
91
 
65
- ```sh
66
- quire install <repository>
67
- ```
92
+ | Test Type | Pattern | Purpose | Location | Speed |
93
+ |-----------|---------|---------|----------|-------|
94
+ | **Unit** | `*.spec.js` | Command structure, options, definitions | `src/commands/` | Very Fast (seconds) |
95
+ | **Integration** | `*.test.js` | Command functionality with mocked dependencies | `src/commands/` | Fast (seconds) |
96
+ | **E2E** | `*.e2e.js` | Complete workflows with real dependencies | `test/e2e/` | Slow (minutes) |
68
97
 
69
- ### `new`
98
+ ### Unit Tests (`*.spec.js`)
70
99
 
71
- Start a new Quire publication from a template project or clone an existing project from a git repository (equivalent to `install`).
100
+ Unit tests verify the command structure and configuration without executing any logic.
72
101
 
73
- Running the `new` without any arguments will start an interactive prompt.
102
+ **What they test:**
103
+ - Command is properly defined: name, description, and version
104
+ - Command is properly instantiated
105
+ - Arguments and options are properly defined
106
+ - Method existence (action, preAction)
107
+ - No actual functionality testing
74
108
 
75
- ```sh
76
- quire new
109
+ **Example:**
110
+ ```javascript
111
+ test('build command should have a dry-run option', (t) => {
112
+ const { command } = t.context
113
+ const dryRunOption = command.options.find((opt) => opt.flags.includes('--dry-run'))
114
+ t.truthy(dryRunOption)
115
+ })
77
116
  ```
78
117
 
79
- To start a new Quire project using the default starter run the following command:
118
+ ### Integration Tests (`*.test.js`)
80
119
 
81
- ```sh
82
- quire new <path>
83
- ```
120
+ Integration tests verify command functionality with all external dependencies mocked.
84
121
 
85
- To create a new project from a starter template
122
+ **What they test:**
123
+ - Command execution flow
124
+ - Interaction with mocked dependencies
125
+ - Option handling
126
+ - Error scenarios
86
127
 
87
- ```sh
88
- quire new <path> <starter>
89
- ```
128
+ Test command functionality with mocked dependencies:
129
+ - Uses `esmock` for ES module mocking
130
+ - Uses `memfs` for in-memory file system
131
+ - Uses `sinon` for function stubs
90
132
 
91
- #### Specifying the `quire-11ty` version
133
+ **Example:**
134
+ ```javascript
135
+ test('build command should call eleventy CLI with default options', async (t) => {
136
+ const { sandbox, fs } = t.context
92
137
 
93
- When the `--quire` flag is used the new project will be started using the specified version of `quire-11ty`
138
+ const mockEleventyCli = {
139
+ build: sandbox.stub().resolves({ exitCode: 0 })
140
+ }
94
141
 
95
- ```sh
96
- quire new <path> <starter> --quire <version>
97
- ```
98
-
99
- ##### Version identifiers
142
+ const BuildCommand = await esmock('./build.js', {
143
+ '#lib/11ty/index.js': {
144
+ cli: mockEleventyCli,
145
+ paths: { output: '_site' },
146
+ projectRoot: '/project'
147
+ },
148
+ 'fs-extra': fs
149
+ })
100
150
 
101
- The `--quire` flag must be either a semantic version identifier or a npm distribution tag. For example:
151
+ const command = new BuildCommand()
152
+ await command.action({ '11ty': 'cli' }, command)
102
153
 
103
- ```sh
104
- quire new ./blargh --quire 1.0.0-rc.5
154
+ t.true(mockEleventyCli.build.called)
155
+ })
105
156
  ```
106
157
 
107
- ```sh
108
- quire new ./blargh --quire latest
109
- ```
158
+ ### E2E Tests (`*.e2e.js`)
110
159
 
111
- ### `preview`
160
+ E2E tests verify complete workflows using real dependencies.
112
161
 
113
- Build and server the Quire site in development mode.
162
+ **What they test:**
163
+ - Complete command workflows
164
+ - Cross-platform compatibility
165
+ - Real file system operations
166
+ - Real external processes
167
+ - Actual PDF/EPUB generation
114
168
 
115
- ```sh
116
- quire preview --port 8080
169
+ **Example:**
170
+ ```javascript
171
+ test('build command should create _site directory', async (t) => {
172
+ await exec('quire build')
173
+ t.true(fs.existsSync('./_site'))
174
+ })
117
175
  ```
118
176
 
119
- #### `epub` **not yet implemented**
120
-
121
- Preview the Quire publication epub in the default application.
177
+ ## Running Tests
122
178
 
123
179
  ```sh
124
- quire preview epub --open
125
- ```
180
+ # Run all tests (unit + integration + e2e)
181
+ npm test
126
182
 
127
- #### `pdf` **not yet implemented**
183
+ # Run only unit tests (fast)
184
+ npm run test:unit
128
185
 
129
- Preview the Quire publication PDF in the default application.
186
+ # Run only integration tests (fast, mocked)
187
+ npm run test:integration
130
188
 
131
- ```sh
132
- quire preview pdf --open
189
+ # Run only E2E tests (slow, real dependencies)
190
+ npm run test:e2e
191
+
192
+ # Watch mode for development
193
+ npm run test:watch
194
+
195
+ # Generate coverage report
196
+ npm run test:coverage
133
197
  ```
134
198
 
135
- #### `site` **default subcommand**
199
+ ## Writing Tests
136
200
 
137
- Build and serve the Quire site in development mode.
201
+ ### When to Write Each Type
138
202
 
139
- ```sh
140
- quire preview site
141
- ```
203
+ ✅ **Always write `*.spec.js`** - Unit tests for every command
204
+ **Always write `*.test.js`** - Integration tests for command logic
205
+ ⚠️ **Optionally write `*.e2e.js`** - E2E tests for critical workflows
142
206
 
143
- ### `server` **not yet implemented**
207
+ ### Integration Test Pattern
144
208
 
145
- Start a local web server to serve a previously built Quire site.
209
+ ```javascript
210
+ import test from 'ava'
211
+ import { Volume, createFsFromVolume } from 'memfs'
212
+ import sinon from 'sinon'
213
+ import esmock from 'esmock'
146
214
 
147
- ```sh
148
- quire server --port 8080
149
- ```
215
+ test.beforeEach((t) => {
216
+ // Create sinon sandbox for mocking
217
+ t.context.sandbox = sinon.createSandbox()
150
218
 
151
- ### `version` **partial implementation**
219
+ // Create in-memory file system
220
+ t.context.vol = new Volume()
221
+ t.context.fs = createFsFromVolume(t.context.vol)
152
222
 
153
- Sets the Quire version to use when running commands on the project.
223
+ // Setup mock directory structure
224
+ t.context.vol.fromJSON({
225
+ '/project/content/_data/config.yaml': 'title: Test Project',
226
+ '/project/package.json': JSON.stringify({ name: 'test-project' })
227
+ })
154
228
 
155
- ```sh
156
- quire version 1.0.0
157
- ```
229
+ t.context.projectRoot = '/project'
230
+ })
158
231
 
159
- To set the quire version globally use the `--global` command flag.
232
+ test.afterEach.always((t) => {
233
+ // Restore all mocks
234
+ t.context.sandbox.restore()
160
235
 
161
- ```sh
162
- quire version 1.0.0 --global
163
- ```
236
+ // Clear in-memory file system
237
+ t.context.vol.reset()
238
+ })
164
239
 
165
- #### `install`
240
+ test('command should perform expected operation', async (t) => {
241
+ const { sandbox, fs } = t.context
166
242
 
167
- ```sh
168
- quire version install <version>
169
- ```
243
+ // Mock dependencies
244
+ const mockDependency = sandbox.stub().resolves()
170
245
 
171
- To reinstall a `quire-11ty` version run
246
+ // Use esmock to load command with mocked dependencies
247
+ const MyCommand = await esmock('./mycommand.js', {
248
+ '#lib/dependency/index.js': mockDependency,
249
+ 'fs-extra': fs
250
+ })
172
251
 
173
- ```sh
174
- quire version install <version> --force
252
+ const command = new MyCommand()
253
+ await command.action({}, command)
254
+
255
+ t.true(mockDependency.called)
256
+ })
175
257
  ```
176
258
 
177
- #### `list`
259
+ ## Testing Dependencies
178
260
 
179
- List installed versions of `quire-11ty`
261
+ All testing dependencies are managed in [package.json](../../package.json):
180
262
 
181
- ```sh
182
- quire version list
183
- ```
263
+ - **ava** - Test runner with ES module support
264
+ - **esmock** - ES module mocking for imports
265
+ - **memfs** - In-memory file system for fast, isolated tests
266
+ - **sinon** - Stubbing and mocking for function calls
184
267
 
185
- #### `prune`
268
+ ## Test Quality Standards
186
269
 
187
- Remove outdated versions of `quire-11ty`
270
+ - Each command must have integration test scenarios
271
+ - Critical workflows must have end-to-end tests
272
+ - All error paths must be tested
273
+ - Test execution time should be < 5 minutes total (with mocking: < 30 seconds)
274
+ - Code coverage target: 80% for integration paths
188
275
 
189
- ```sh
190
- quire version prune
191
- ```
276
+ ## Creating a New Command
192
277
 
193
- #### `remove` (alias `uninstall`)
278
+ 1. **Create the command file** (`src/commands/my-command.js`)
279
+ - Extend the `Command` class
280
+ - Define static `definition` object
281
+ - Implement `action()` method
194
282
 
195
- ```sh
196
- quire version remove <version>
197
- ```
283
+ 2. **Create integration tests** (`src/commands/my-command.test.js`)
284
+ - Test main functionality with mocked dependencies
285
+ - Test error handling
286
+ - Test option passing
198
287
 
199
- ```sh
200
- quire version uninstall <version>
201
- ```
288
+ 3. **Create unit tests** (optional, `src/commands/my-command.spec.js`)
289
+ - Validate command structure and configuration
290
+
291
+ 4. **Create e2e tests** (optional, `test/e2e/my-command.e2e.js`)
292
+ - Test critical workflows with real dependencies
@@ -1,6 +1,10 @@
1
1
  import Command from '#src/Command.js'
2
- import { api, cli, paths, projectRoot } from '#lib/11ty/index.js'
2
+ import { Option } from 'commander'
3
+ import { withOutputModes } from '#lib/commander/index.js'
4
+ import { api, cli } from '#lib/11ty/index.js'
5
+ import paths from '#lib/project/index.js'
3
6
  import { clean } from '#helpers/clean.js'
7
+ import reporter from '#lib/reporter/index.js'
4
8
  import testcwd from '#helpers/test-cwd.js'
5
9
 
6
10
  /**
@@ -12,56 +16,61 @@ import testcwd from '#helpers/test-cwd.js'
12
16
  * @extends {Command}
13
17
  */
14
18
  export default class BuildCommand extends Command {
15
- static definition = {
19
+ static definition = withOutputModes({
16
20
  name: 'build',
17
21
  description: 'Generate publication outputs',
18
- summary: 'run build',
19
- version: '1.0.0',
20
- args: [
21
- // [
22
- // '[formats...]', 'output formats',
23
- // {
24
- // choices: ['pdf', 'epub'],
25
- // }
26
- // ],
27
- ],
22
+ summary: 'generate HTML site files',
23
+ docsLink: 'quire-commands/#output-files',
24
+ helpText: `
25
+ Examples:
26
+ quire build Build the site
27
+ quire build --verbose Build with detailed progress
28
+ quire build --debug Build with debug output
29
+
30
+ Note: Run before "quire pdf" or "quire epub" commands.
31
+ `,
32
+ version: '1.1.0',
28
33
  options: [
29
34
  [ '-d', '--dry-run', 'run build without writing files' ],
30
- [ '-q', '--quiet', 'run build with no console messages' ],
31
- [ '-v', '--verbose', 'run build with verbose console messages' ],
32
- [
33
- '--11ty <module>', 'use the specified 11ty module', 'cli',
34
- // { choices: ['api', 'cli'], default: 'cli' }
35
- ],
36
- [ '--debug', 'run build with debug output to console' ],
35
+ [ '--dryrun', 'alias for --dry-run', { hidden: true, implies: { dryRun: true } } ],
36
+ // Use Option object syntax to configure this as a hidden option
37
+ new Option('--11ty <module>', 'use the specified 11ty module')
38
+ .choices(['api', 'cli']).default('api').hideHelp(),
37
39
  ],
38
- }
40
+ })
39
41
 
40
42
  constructor() {
41
43
  super(BuildCommand.definition)
42
44
  }
43
45
 
44
- action(options, command) {
45
- if (options.debug) {
46
- console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
47
- }
46
+ async action(options, command) {
47
+ this.debug('called with options %O', options)
48
48
 
49
- if (options['11ty'] === 'cli') {
50
- console.debug('[CLI] running eleventy using lib/11ty cli')
51
- cli.build(options)
52
- } else {
53
- console.debug('[CLI] running eleventy using lib/11ty api')
54
- api.build(options)
49
+ // Configure reporter for this command
50
+ reporter.configure({ quiet: options.quiet, verbose: options.verbose })
51
+
52
+ reporter.start('Building site...', { showElapsed: true })
53
+
54
+ try {
55
+ if (options['11ty'] === 'api') {
56
+ this.debug('running eleventy using lib/11ty api')
57
+ await api.build(options)
58
+ } else {
59
+ this.debug('running eleventy using lib/11ty cli')
60
+ await cli.build(options)
61
+ }
62
+ reporter.succeed('Build complete')
63
+ } catch (error) {
64
+ reporter.fail('Build failed')
65
+ throw error
55
66
  }
56
67
  }
57
68
 
58
- preAction(command) {
59
- testcwd(command)
69
+ preAction(thisCommand, actionCommand) {
70
+ testcwd(thisCommand)
60
71
 
61
- const options = command.opts()
62
- if (options.debug) {
63
- console.debug('[CLI] Calling \'build\' command pre-action with options', options)
64
- }
65
- clean(projectRoot, paths, options)
72
+ const options = thisCommand.opts()
73
+ this.debug('pre-action with options %O', options)
74
+ clean(paths.getProjectRoot(), paths.toObject(), options)
66
75
  }
67
76
  }
@@ -0,0 +1,113 @@
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() === 'build')
15
+ })
16
+
17
+ test('command is registered in CLI program', (t) => {
18
+ const { command } = t.context
19
+
20
+ t.truthy(command, 'command "build" 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(), 'build')
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, 'build 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 dryRunOption = command.options.find((opt) => opt.long === '--dry-run')
44
+ const dryrunAlias = command.options.find((opt) => opt.long === '--dryrun')
45
+ const quietOption = command.options.find((opt) => opt.long === '--quiet')
46
+ const verboseOption = command.options.find((opt) => opt.long === '--verbose')
47
+ const progressOption = command.options.find((opt) => opt.long === '--progress')
48
+ const eleventyOption = command.options.find((opt) => opt.long === '--11ty')
49
+ const debugOption = command.options.find((opt) => opt.long === '--debug')
50
+
51
+ // Verify all options exist
52
+ t.truthy(dryRunOption, '--dry-run option should exist')
53
+ t.truthy(dryrunAlias, '--dryrun alias should exist')
54
+ t.truthy(quietOption, '--quiet option should exist')
55
+ t.truthy(verboseOption, '--verbose option should exist')
56
+ t.truthy(progressOption, '--progress option should exist')
57
+ t.truthy(eleventyOption, '--11ty option should exist')
58
+ t.truthy(debugOption, '--debug option should exist')
59
+
60
+ // Verify they are Option instances
61
+ t.true(dryRunOption instanceof Option, '--dry-run should be Option instance')
62
+ t.true(dryrunAlias instanceof Option, '--dryrun should be Option instance')
63
+ t.true(quietOption instanceof Option, '--quiet should be Option instance')
64
+ t.true(verboseOption instanceof Option, '--verbose should be Option instance')
65
+ t.true(progressOption instanceof Option, '--progress should be Option instance')
66
+ t.true(eleventyOption instanceof Option, '--11ty should be Option instance')
67
+ t.true(debugOption instanceof Option, '--debug should be Option instance')
68
+
69
+ // Verify option properties
70
+ t.is(dryRunOption.long, '--dry-run')
71
+ t.is(dryRunOption.short, '-d')
72
+ t.truthy(dryRunOption.description)
73
+
74
+ // --dryrun is a hidden alias for --dry-run
75
+ t.is(dryrunAlias.long, '--dryrun')
76
+ t.true(dryrunAlias.hidden, '--dryrun should be hidden from help')
77
+
78
+ t.is(quietOption.long, '--quiet')
79
+ t.is(quietOption.short, '-q')
80
+ t.truthy(quietOption.description)
81
+
82
+ t.is(verboseOption.long, '--verbose')
83
+ t.is(verboseOption.short, '-v')
84
+ t.truthy(verboseOption.description)
85
+
86
+ // --progress is a hidden alias for --verbose
87
+ t.is(progressOption.long, '--progress')
88
+ t.truthy(progressOption.description)
89
+ t.true(progressOption.hidden, '--progress should be hidden from help')
90
+
91
+ t.is(eleventyOption.long, '--11ty')
92
+ t.truthy(eleventyOption.description)
93
+ t.true(eleventyOption.required, '--11ty should require a value')
94
+
95
+ t.is(debugOption.long, '--debug')
96
+ t.truthy(debugOption.description)
97
+ t.false(debugOption.required, '--debug should not require a value')
98
+ })
99
+
100
+ test('command options are accessible via public API', (t) => {
101
+ const { command } = t.context
102
+
103
+ // Test that options can be accessed the way Commander.js does
104
+ const optionNames = command.options.map((opt) => opt.long)
105
+
106
+ t.true(optionNames.includes('--dry-run'))
107
+ t.true(optionNames.includes('--dryrun'))
108
+ t.true(optionNames.includes('--quiet'))
109
+ t.true(optionNames.includes('--verbose'))
110
+ t.true(optionNames.includes('--progress'))
111
+ t.true(optionNames.includes('--11ty'))
112
+ t.true(optionNames.includes('--debug'))
113
+ })