@thegetty/quire-cli 1.0.0-rc.34 → 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 (159) 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/schemas/config.schema.json +194 -0
  6. package/schemas/figures.schema.json +56 -0
  7. package/schemas/layout.schema.json +5 -0
  8. package/schemas/objects.schema.json +62 -0
  9. package/schemas/publication.schema.json +140 -0
  10. package/schemas/references.schema.json +29 -0
  11. package/src/Command.js +26 -6
  12. package/src/Command.spec.js +99 -0
  13. package/src/commands/README.md +213 -122
  14. package/src/commands/build.js +46 -37
  15. package/src/commands/build.spec.js +113 -0
  16. package/src/commands/build.test.js +402 -0
  17. package/src/commands/clean.js +25 -19
  18. package/src/commands/clean.spec.js +108 -0
  19. package/src/commands/clean.test.js +260 -0
  20. package/src/commands/config.js +251 -0
  21. package/src/commands/config.spec.js +107 -0
  22. package/src/commands/config.test.js +715 -0
  23. package/src/commands/create.js +42 -14
  24. package/src/commands/create.spec.js +112 -0
  25. package/src/commands/create.test.js +415 -0
  26. package/src/commands/epub.js +59 -30
  27. package/src/commands/epub.spec.js +114 -0
  28. package/src/commands/epub.test.js +503 -0
  29. package/src/commands/index.js +9 -2
  30. package/src/commands/info.js +18 -24
  31. package/src/commands/info.spec.js +64 -0
  32. package/src/commands/info.test.js +415 -0
  33. package/src/commands/pdf.js +58 -84
  34. package/src/commands/pdf.spec.js +114 -0
  35. package/src/commands/pdf.test.js +464 -0
  36. package/src/commands/preview.js +26 -31
  37. package/src/commands/preview.spec.js +97 -0
  38. package/src/commands/preview.test.js +250 -0
  39. package/src/commands/use.js +56 -0
  40. package/src/commands/use.spec.js +61 -0
  41. package/src/commands/use.test.js +280 -0
  42. package/src/commands/validate.js +31 -19
  43. package/src/commands/validate.spec.js +82 -0
  44. package/src/commands/validate.test.js +234 -0
  45. package/src/commands/workflows.js +70 -0
  46. package/src/errors/build/build-failed-error.js +19 -0
  47. package/src/errors/build/config-field-missing-error.js +20 -0
  48. package/src/errors/build/config-file-not-found-error.js +20 -0
  49. package/src/errors/build/index.js +11 -0
  50. package/src/errors/index.js +48 -0
  51. package/src/errors/install/dependency-install-error.js +19 -0
  52. package/src/errors/install/directory-not-empty-error.js +25 -0
  53. package/src/errors/install/index.js +12 -0
  54. package/src/errors/install/invalid-path-error.js +31 -0
  55. package/src/errors/install/invalid-starter-error.js +27 -0
  56. package/src/errors/install/version-not-found-error.js +35 -0
  57. package/src/errors/output/epub-generation-error.js +19 -0
  58. package/src/errors/output/index.js +14 -0
  59. package/src/errors/output/invalid-epub-library-error.js +20 -0
  60. package/src/errors/output/invalid-pdf-library-error.js +20 -0
  61. package/src/errors/output/missing-build-output-error.js +21 -0
  62. package/src/errors/output/pdf-generation-error.js +24 -0
  63. package/src/errors/output/tool-not-found-error.js +37 -0
  64. package/src/errors/project/index.js +10 -0
  65. package/src/errors/project/not-in-project-error.js +20 -0
  66. package/src/errors/project/project-create-error.js +21 -0
  67. package/src/errors/quire-error.js +27 -0
  68. package/src/errors/validation/validation-error.js +20 -11
  69. package/src/helpers/clean.js +1 -1
  70. package/src/helpers/docs-url.js +32 -0
  71. package/src/helpers/test-cwd.js +5 -6
  72. package/src/helpers/test-cwd.test.js +192 -0
  73. package/src/helpers/which.js +10 -4
  74. package/src/lib/11ty/README.md +135 -19
  75. package/src/lib/11ty/api.js +176 -93
  76. package/src/lib/11ty/cli.js +77 -35
  77. package/src/lib/11ty/index.js +64 -5
  78. package/src/lib/11ty/index.test.js +655 -0
  79. package/src/lib/README.md +275 -0
  80. package/src/lib/commander/index.js +100 -0
  81. package/src/lib/commander/index.test.js +86 -0
  82. package/src/lib/commander/options.js +195 -0
  83. package/src/lib/commander/options.test.js +109 -0
  84. package/src/lib/conf/README.md +84 -73
  85. package/src/lib/conf/config.js +5 -3
  86. package/src/lib/conf/config.test.js +281 -0
  87. package/src/lib/conf/defaults.js +44 -0
  88. package/src/lib/conf/format.js +60 -0
  89. package/src/lib/conf/format.test.js +106 -0
  90. package/src/lib/conf/helpers.js +91 -0
  91. package/src/lib/conf/helpers.test.js +136 -0
  92. package/src/lib/conf/index.js +22 -0
  93. package/src/lib/conf/schema.js +53 -8
  94. package/src/lib/epub/README.md +133 -2
  95. package/src/lib/epub/engines.js +46 -0
  96. package/src/lib/epub/epub.js +36 -11
  97. package/src/lib/epub/index.js +111 -21
  98. package/src/lib/epub/index.test.js +518 -0
  99. package/src/lib/epub/pandoc.js +33 -4
  100. package/src/lib/epub/pandoc.test.js +122 -0
  101. package/src/lib/epub/schema.js +21 -0
  102. package/src/lib/error/README.md +170 -0
  103. package/src/lib/error/handler.js +107 -0
  104. package/src/lib/git/README.md +151 -2
  105. package/src/lib/git/index.js +217 -13
  106. package/src/lib/git/index.spec.js +80 -0
  107. package/src/lib/git/index.test.js +453 -0
  108. package/src/lib/installer/index.js +309 -0
  109. package/src/lib/installer/index.spec.js +83 -0
  110. package/src/lib/installer/index.test.js +545 -0
  111. package/src/lib/logger/README.md +424 -0
  112. package/src/lib/logger/debug.js +90 -0
  113. package/src/lib/logger/debug.spec.js +130 -0
  114. package/src/lib/logger/index.js +228 -0
  115. package/src/lib/logger/index.spec.js +131 -0
  116. package/src/lib/logger/index.test.js +477 -0
  117. package/src/lib/npm/README.md +127 -0
  118. package/src/lib/npm/index.js +198 -0
  119. package/src/lib/npm/index.spec.js +60 -0
  120. package/src/lib/npm/index.test.js +355 -0
  121. package/src/lib/pdf/README.md +131 -0
  122. package/src/lib/pdf/engines.js +46 -0
  123. package/src/lib/pdf/index.js +124 -21
  124. package/src/lib/pdf/index.test.js +708 -0
  125. package/src/lib/pdf/paged.js +100 -52
  126. package/src/lib/pdf/paged.test.js +366 -0
  127. package/src/lib/pdf/prince.js +115 -37
  128. package/src/lib/pdf/prince.test.js +202 -0
  129. package/src/lib/pdf/schema.js +21 -0
  130. package/src/lib/pdf/split.js +61 -33
  131. package/src/lib/pdf/split.test.js +445 -0
  132. package/src/lib/process/manager.js +110 -0
  133. package/src/lib/process/manager.test.js +55 -0
  134. package/src/lib/project/build.js +143 -0
  135. package/src/lib/project/build.test.js +253 -0
  136. package/src/lib/project/config.js +48 -0
  137. package/src/lib/project/config.test.js +134 -0
  138. package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
  139. package/src/lib/project/detect.test.js +157 -0
  140. package/src/lib/project/index.js +40 -0
  141. package/src/lib/project/paths.js +224 -0
  142. package/src/lib/project/version.js +110 -0
  143. package/src/lib/project/version.test.js +350 -0
  144. package/src/lib/reporter/README.md +211 -2
  145. package/src/lib/reporter/index.js +512 -0
  146. package/src/lib/reporter/index.test.js +593 -0
  147. package/src/main.js +134 -47
  148. package/src/main.spec.js +61 -0
  149. package/src/main.test.js +347 -0
  150. package/src/validators/utils.js +2 -1
  151. package/src/commands/conf.js +0 -43
  152. package/src/commands/version.js +0 -43
  153. package/src/lib/11ty/paths.js +0 -103
  154. package/src/lib/i18n/README.md +0 -3
  155. package/src/lib/i18n/config.js +0 -53
  156. package/src/lib/i18n/index.js +0 -43
  157. package/src/lib/i18n/localeService.js +0 -58
  158. package/src/lib/quire/README.md +0 -19
  159. package/src/lib/quire/index.js +0 -350
package/src/Command.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import config from '#src/lib/conf/config.js'
2
+ import createLogger from '#lib/logger/index.js'
3
+ import createDebug from '#debug'
2
4
 
3
5
  /**
4
6
  * Command
@@ -13,10 +15,13 @@ export default class Command {
13
15
 
14
16
  /**
15
17
  * @typedef CommandDefinition
16
- * @property {String} name
17
- * @property {String} alias
18
- * @property {Array<String>} aliases
19
- * @property {String} descriptions
18
+ * @property {String} name - Command name used to invoke it
19
+ * @property {String} alias - Single alias for the command
20
+ * @property {Array<String>} aliases - Multiple aliases for the command
21
+ * @property {String} description - Full description shown in command's own help
22
+ * @property {String} summary - One-line summary shown in parent help listing
23
+ * @property {String} [docsLink] - Path appended to docs base URL (e.g., 'quire-commands/#output-files')
24
+ * @property {String} [helpText] - Custom help text shown after built-in help (examples, notes)
20
25
  * @property {Array<CommandArgument>} args
21
26
  * @property {Array<CommandOption>} options
22
27
  * @property {String} version
@@ -32,12 +37,24 @@ export default class Command {
32
37
  * @param {CommandDefinition} definition The definition
33
38
  */
34
39
  constructor(definition) {
35
- if (this.constructor.prototype === Command) {
40
+ if (this.constructor === Command) {
36
41
  throw new Error('Command is an *abstract* class')
37
42
  }
38
43
 
39
44
  this.config = config // quire-cli configuration
40
45
 
46
+ /**
47
+ * Command-specific debug instance for internal debugging
48
+ * Enable via: DEBUG=quire:commands:name or DEBUG=quire:commands:*
49
+ */
50
+ this.debug = createDebug(`commands:${definition.name}`)
51
+
52
+ /**
53
+ * Command-specific logger with prefix including command name
54
+ * Output format: [quire] LEVEL commands:name message
55
+ */
56
+ this.logger = createLogger(`commands:${definition.name}`)
57
+
41
58
  /**
42
59
  * Merge and deduplicate command definition alias and aliases
43
60
  * Nota bene: Only the first command alias is displayed in the help.
@@ -48,6 +65,9 @@ export default class Command {
48
65
  this.name = definition.name
49
66
  this.aliases = definition.aliases
50
67
  this.description = definition.description
68
+ this.summary = definition.summary
69
+ this.docsLink = definition.docsLink
70
+ this.helpText = definition.helpText
51
71
  this.args = definition.args
52
72
  this.options = definition.options
53
73
  this.version = definition.version
@@ -55,7 +75,7 @@ export default class Command {
55
75
  }
56
76
 
57
77
  definition() {
58
- return this.prototype.definition
78
+ return this.constructor.definition
59
79
  }
60
80
 
61
81
  action() {
@@ -0,0 +1,99 @@
1
+ import Command from './Command.js'
2
+ import test from 'ava'
3
+
4
+ /**
5
+ * Command Base Class Unit Tests
6
+ *
7
+ * Tests the abstract Command base class API contract.
8
+ * These tests verify the class cannot be instantiated directly
9
+ * and that the base methods behave correctly.
10
+ */
11
+
12
+ test('Command cannot be instantiated directly', (t) => {
13
+ const error = t.throws(() => {
14
+ new Command({ name: 'test', description: 'test command' })
15
+ })
16
+
17
+ t.truthy(error)
18
+ t.regex(error.message, /abstract/i, 'error message should mention abstract')
19
+ })
20
+
21
+ test('Command.action() throws not-implemented error', (t) => {
22
+ // Create a minimal subclass that does not override action
23
+ class TestCommand extends Command {
24
+ constructor() {
25
+ super({ name: 'test', description: 'test command' })
26
+ }
27
+ }
28
+
29
+ const command = new TestCommand()
30
+ const error = t.throws(() => command.action())
31
+
32
+ t.truthy(error)
33
+ t.regex(error.message, /not been implemented/i, 'error message should indicate not implemented')
34
+ t.regex(error.message, /test/i, 'error message should include the command name')
35
+ })
36
+
37
+ test('subclass can override action without error', (t) => {
38
+ class TestCommand extends Command {
39
+ constructor() {
40
+ super({ name: 'test', description: 'test command' })
41
+ }
42
+
43
+ action() {
44
+ return 'action executed'
45
+ }
46
+ }
47
+
48
+ const command = new TestCommand()
49
+ const result = command.action()
50
+
51
+ t.is(result, 'action executed')
52
+ })
53
+
54
+ test('Command constructor sets properties from definition', (t) => {
55
+ const definition = {
56
+ name: 'test',
57
+ description: 'A test command',
58
+ summary: 'test summary',
59
+ aliases: ['t', 'tst'],
60
+ args: [['<input>', 'input file']],
61
+ options: [['--verbose', 'enable verbose output']],
62
+ version: '1.0.0',
63
+ hidden: true,
64
+ }
65
+
66
+ class TestCommand extends Command {
67
+ constructor() {
68
+ super(definition)
69
+ }
70
+
71
+ action() {}
72
+ }
73
+
74
+ const command = new TestCommand()
75
+
76
+ t.is(command.name, 'test')
77
+ t.is(command.description, 'A test command')
78
+ t.is(command.summary, 'test summary')
79
+ t.deepEqual(command.aliases, ['t', 'tst'])
80
+ t.deepEqual(command.args, [['<input>', 'input file']])
81
+ t.deepEqual(command.options, [['--verbose', 'enable verbose output']])
82
+ t.is(command.version, '1.0.0')
83
+ t.is(command.hidden, true)
84
+ })
85
+
86
+ test('Command constructor injects shared configuration', (t) => {
87
+ class TestCommand extends Command {
88
+ constructor() {
89
+ super({ name: 'test', description: 'test command' })
90
+ }
91
+
92
+ action() {}
93
+ }
94
+
95
+ const command = new TestCommand()
96
+
97
+ t.truthy(command.config, 'config should be injected')
98
+ t.is(typeof command.config.get, 'function', 'config should have get method')
99
+ })
@@ -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