@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
@@ -0,0 +1,109 @@
1
+ import test from 'ava'
2
+ import { Command } from 'commander'
3
+ import { arrayToOption } from './index.js'
4
+ import { quietOption, verboseOption, debugOption } from './options.js'
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // Integration tests for option conflicts
8
+ //
9
+ // These tests verify that Commander.js correctly enforces the option conflicts
10
+ // at parse time.
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Helper to create a test program with the shared options
15
+ */
16
+ function createTestProgram() {
17
+ const program = new Command()
18
+ program
19
+ .addOption(arrayToOption(quietOption))
20
+ .addOption(arrayToOption(verboseOption))
21
+ .addOption(arrayToOption(debugOption))
22
+ .action(() => {})
23
+ .exitOverride() // Throw instead of process.exit
24
+ .configureOutput({
25
+ writeErr: () => {}, // Suppress error output
26
+ })
27
+ return program
28
+ }
29
+
30
+ test('--quiet alone is valid', (t) => {
31
+ const program = createTestProgram()
32
+
33
+ t.notThrows(() => {
34
+ program.parse(['node', 'test', '--quiet'])
35
+ })
36
+ })
37
+
38
+ test('--verbose alone is valid', (t) => {
39
+ const program = createTestProgram()
40
+
41
+ t.notThrows(() => {
42
+ program.parse(['node', 'test', '--verbose'])
43
+ })
44
+ })
45
+
46
+ test('--debug alone is valid', (t) => {
47
+ const program = createTestProgram()
48
+
49
+ t.notThrows(() => {
50
+ program.parse(['node', 'test', '--debug'])
51
+ })
52
+ })
53
+
54
+ test('--verbose --debug together is valid', (t) => {
55
+ const program = createTestProgram()
56
+
57
+ t.notThrows(() => {
58
+ program.parse(['node', 'test', '--verbose', '--debug'])
59
+ })
60
+ })
61
+
62
+ test('--quiet --verbose throws conflict error', (t) => {
63
+ const program = createTestProgram()
64
+
65
+ const error = t.throws(() => {
66
+ program.parse(['node', 'test', '--quiet', '--verbose'])
67
+ })
68
+
69
+ t.true(error.message.includes('cannot be used with'))
70
+ })
71
+
72
+ test('--quiet --debug throws conflict error', (t) => {
73
+ const program = createTestProgram()
74
+
75
+ const error = t.throws(() => {
76
+ program.parse(['node', 'test', '--quiet', '--debug'])
77
+ })
78
+
79
+ t.true(error.message.includes('cannot be used with'))
80
+ })
81
+
82
+ test('--quiet --verbose --debug throws conflict error', (t) => {
83
+ const program = createTestProgram()
84
+
85
+ const error = t.throws(() => {
86
+ program.parse(['node', 'test', '--quiet', '--verbose', '--debug'])
87
+ })
88
+
89
+ t.true(error.message.includes('cannot be used with'))
90
+ })
91
+
92
+ test('-q -v (short flags) throws conflict error', (t) => {
93
+ const program = createTestProgram()
94
+
95
+ const error = t.throws(() => {
96
+ program.parse(['node', 'test', '-q', '-v'])
97
+ })
98
+
99
+ t.true(error.message.includes('cannot be used with'))
100
+ })
101
+
102
+ test('options order does not affect conflict detection', (t) => {
103
+ const program1 = createTestProgram()
104
+ const program2 = createTestProgram()
105
+
106
+ // Both orders should throw
107
+ t.throws(() => program1.parse(['node', 'test', '--quiet', '--verbose']))
108
+ t.throws(() => program2.parse(['node', 'test', '--verbose', '--quiet']))
109
+ })
@@ -1,104 +1,115 @@
1
- ## CLI Configuration Manager
1
+ ## CLI Configuration Module
2
2
 
3
- This `quire-cli/lib/config` module manages reading and writing (persisting) options for the Quire CLI using the [`conf`](https://github.com/sindresorhus/conf) package.
3
+ The `conf/` module manages reading and writing (persisting) settings for the Quire CLI using the [`conf`](https://github.com/sindresorhus/conf) package.
4
4
 
5
5
  `conf` stores the config in the system default [user config directory](https://github.com/sindresorhus/env-paths#pathsconfig). For example, on macOS, the config file will be stored in the `~/Library/Preferences/@thegetty/quire-cli` directory.
6
6
 
7
7
  > Changes are written to disk atomically, so if the process crashes during a write, it will not corrupt the existing config.
8
8
 
9
- ### Configuration
9
+ ### Module Structure
10
10
 
11
- `logLevel` The default logging level for the Quire CLI output; default `'info'`.
12
-
13
- `projectTemplate` A default project starter template to use when creating new projects; default `'quire-starter-default'`.
14
-
15
- `quirePath` The relative path to `quire-11ty` installed in the project directory; default `./11ty`. When set to `null`, `quire-11ty` is installed to the CLI `lib/quire/versions/<version>/` directory.
16
-
17
- ```sh
18
- ❯ quire config quire-path '.'
19
11
  ```
20
-
21
- `quireVersion` The default version of `quire-11ty` to install when creating new Quire projects; default `'latest'`.
22
-
23
- ```sh
24
- quire config quire-version '1.0.0'
12
+ conf/
13
+ ├── index.js Barrel export (config singleton + helpers + format)
14
+ ├── config.js Conf singleton instance
15
+ ├── helpers.js Pure schema-aware validation and coercion
16
+ ├── format.js Display formatting with optional chalk styling
17
+ ├── defaults.js Default configuration values
18
+ ├── schema.js JSON Schema definitions
19
+ ├── migrations.js Version migration functions
20
+ ├── config.test.js Integration tests for Conf instance
21
+ ├── helpers.test.js Unit tests for validation/coercion helpers
22
+ └── format.test.js Unit tests for display formatting
25
23
  ```
26
24
 
27
- `telemetry` Send anonymous data about Quire usage; default `false`.
28
-
29
- ```sh
30
- ❯ quire config telemetry --enabled
31
- ```
25
+ ### Architecture: Barrel Export vs Singleton Wrapper
32
26
 
33
- ```sh
34
- ❯ quire config telemetry --disabled
35
- ```
27
+ The module uses a **barrel export** pattern (`index.js`) rather than wrapping the `Conf` singleton in a class. This is a deliberate design choice:
36
28
 
37
- `updateChannels` A list of distribution tags to use when checking for version updates; default `['latest']`. To show notifications for pre-releases version updates include `'pre-release'` in the array of channels. This can be set using the configuration `--pre-release` command flag.
29
+ - **`config.js`** exports the `Conf` singleton that owns mutable state (the on-disk JSON store, file watching, schema validation, migrations). This is a thin wrapper around the `conf` package.
30
+ - **`helpers.js`** exports pure functions (`isValidKey`, `coerceValue`, `formatValidationError`, etc.) for schema-aware validation and coercion. These import only `schema.js` and `defaults.js` (pure data), keeping them free of transitive dependencies.
31
+ - **`format.js`** exports display formatting functions (`formatSettings`, `getKeyDescription`) that depend on `chalk` for optional coloured output. Separated from `helpers.js` to isolate the chalk dependency from the pure validation/coercion functions.
32
+ - **`index.js`** re-exports all three, providing a single import path for consumers.
38
33
 
39
- ```sh
40
- ❯ quire config update-channels
41
- Quire configured to check for updates tagged 'latest'
42
- ```
34
+ A wrapper class was considered but rejected for three reasons:
43
35
 
44
- ```sh
45
- quire config update-channels --add 'pre-release'
46
- Quire configured to check for updates tagged 'latest', 'pre-release'
47
- ```
36
+ 1. **No shared state.** The helpers operate on schema/defaults (static data), not the config store (runtime state). Binding them to the singleton would create a false coupling.
37
+ 2. **Circular dependency avoidance.** The logger imports `config.js`. If helpers were methods on the config singleton, importing them would trigger the full `Conf` instantiation chain. By depending only on `schema.js` and `defaults.js` (pure data modules), `helpers.js` stays free of transitive dependencies.
38
+ 3. **Testability.** Pure functions are directly testable with simple import + assert — no esmock, no sinon, no constructor stubbing. Compare `helpers.test.js` (pure function tests) with `config.test.js` (integration tests requiring esmock to mock the `Conf` constructor).
48
39
 
49
- ```sh
50
- ❯ quire config update-channels --rm 'pre-release'
51
- Quire configured to check for updates tagged 'latest'
52
- ```
40
+ ### Exports
53
41
 
54
- `updateInterval` Interval at which to check for updates to the Quire CLI and project's `quire-11ty` version; default `'DAILY'`.
42
+ ```javascript
43
+ // Barrel import — config singleton + named helpers
44
+ import config, { isValidKey, coerceValue } from '#lib/conf/index.js'
55
45
 
56
- ```sh
57
- quire config update-interval
58
- Quire configured to check for updates DAILY
46
+ // Direct import — config singleton only (used by logger, etc.)
47
+ import config from '#lib/conf/config.js'
59
48
  ```
60
49
 
61
- ```sh
62
- ❯ quire config update-interval WEEKLY
63
- ```
50
+ | Export | Source | Description |
51
+ |--------|--------|-------------|
52
+ | `default` | `config.js` | `Conf` singleton instance |
53
+ | `isValidKey(key)` | `helpers.js` | Check if key exists in schema |
54
+ | `getValidKeys()` | `helpers.js` | Sorted array of all schema keys |
55
+ | `coerceValue(key, value)` | `helpers.js` | Coerce CLI string to schema type |
56
+ | `formatValidationError(key, value)` | `helpers.js` | Format error with enum/description hints |
57
+ | `getDefault(key)` | `helpers.js` | Get default value for a key |
58
+ | `getKeyDescription(key)` | `format.js` | Get schema description for a key |
59
+ | `formatSettings(store, options)` | `format.js` | Format all settings for display (with optional chalk styling) |
64
60
 
65
- `versionFile ['.quire-version']` The default file name for the `quire-11ty` version file.
61
+ ### Display Formatting
66
62
 
67
- ```sh
68
- ❯ quire config version-file '.blargh'
69
- ```
63
+ `formatSettings` accepts an options object controlling output:
70
64
 
71
- ### Quire CLI `config` Command
65
+ | Option | Type | Default | Description |
66
+ |--------|------|---------|-------------|
67
+ | `configPath` | string | — | Path shown in header (e.g. `quire-cli configuration /path/to/config`) |
68
+ | `showInternal` | boolean | `false` | Include `__internal__`-prefixed keys |
69
+ | `useColor` | boolean | `false` | Apply chalk styling (bold header, cyan keys, dim descriptions) |
72
70
 
73
- Running the `config` command without any arguments will start an interactive prompt to configure Quire.
71
+ When `useColor` is `true`, output uses:
72
+ - **Bold** for the header line
73
+ - **Cyan** for key names
74
+ - **Dim** for descriptions and the help hint
74
75
 
75
- To view an individual configuration value and its default value use:
76
+ When `useColor` is `false` (default), no ANSI escape codes are emitted.
76
77
 
77
- ```sh
78
- ❯ quire config [key]
79
- ```
78
+ The settings command reads `logUseColor` from the config to determine whether to pass `useColor: true`.
80
79
 
81
- To view the current `logLevel` setting for example:
80
+ ### Settings Reference
82
81
 
83
- ```sh
84
- ❯ quire config logLevel
85
- loglevel: 'debug' (default 'info')
86
- ```
87
-
88
- To set an individual configuration value, use the `set` argument:
89
-
90
- ```sh
91
- ❯ quire config set <key> <value>
92
- ```
93
-
94
- To reset *all* keys to their default values, use the `reset` argument:
82
+ Use `quire settings` to view all settings with descriptions, or manage individual values:
95
83
 
96
84
  ```sh
97
- quire config reset
85
+ quire settings # Show all settings (coloured if logUseColor is true)
86
+ quire settings get <key> # Get a single value
87
+ quire settings set <key> <value> # Set a value
88
+ quire settings delete <key> # Delete (reset to default)
89
+ quire settings reset [key] # Reset all or single key
90
+ quire settings path # Show settings file path
91
+ quire settings --json # Output all settings as JSON
92
+ quire settings get <key> --json # Output single value as JSON
93
+ quire settings --json --debug # Include __internal__ keys in JSON output
98
94
  ```
99
95
 
100
- To reset an individual key to its default value:
101
-
102
- ```sh
103
- ❯ quire config reset [key]
104
- ```
96
+ The `--json` flag outputs raw JSON to stdout (bypassing the logger), suitable for piping to `jq` or other tools. `__internal__` keys are excluded from JSON output unless `--debug` is also passed, consistent with the plain-text display.
97
+
98
+ | Key | Type | Default | Description |
99
+ |-----|------|---------|-------------|
100
+ | `debug` | boolean | `false` | Enable debug output by default |
101
+ | `epubEngine` | string | `'epubjs'` | EPUB engine (`epubjs`, `pandoc`) |
102
+ | `logLevel` | string | `'info'` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `silent`) |
103
+ | `logPrefix` | string | `'quire'` | Prefix text for log messages |
104
+ | `logPrefixStyle` | string | `'bracket'` | Prefix style (`bracket`, `emoji`, `plain`, `none`) |
105
+ | `logShowLevel` | boolean | `false` | Show level label in output |
106
+ | `logUseColor` | boolean | `true` | Use colored output |
107
+ | `logColorMessages` | boolean | `true` | Color message text by level |
108
+ | `pdfEngine` | string | `'pagedjs'` | PDF engine (`pagedjs`, `prince`) |
109
+ | `projectTemplate` | string | (GitHub URL) | Default starter template |
110
+ | `quire11tyPath` | string | `'.'` | Path to quire-11ty package |
111
+ | `quireVersion` | string | `'latest'` | Version of quire-11ty to install |
112
+ | `updateChannel` | string | `'rc'` | Release channel (`stable`, `rc`, `beta`, `alpha`) |
113
+ | `updateInterval` | string | `'DAILY'` | Update check frequency (`DAILY`, `WEEKLY`, `MONTHLY`, `NEVER`) |
114
+ | `verbose` | boolean | `false` | Enable verbose output by default |
115
+ | `versionFile` | string | `'.quire'` | Filename to identify Quire projects |
@@ -1,14 +1,16 @@
1
1
  import Conf from 'conf'
2
+ import packageConfig from '#src/packageConfig.js'
2
3
  import defaults from './defaults.js'
3
4
  import migrations from './migrations.js'
4
- import packageConfig from '#src/packageConfig.js'
5
5
  import schema from './schema.js'
6
6
 
7
7
  const { name, version } = packageConfig
8
8
 
9
- const beforeEachMigration = (store, context) => {
9
+ const beforeEachMigration = (_store, context) => {
10
10
  const { fromVersion, toVersion } = context
11
- console.info(`quire-cli migrating config from ${fromVersion} ${toVersion}`)
11
+ // Call console directly to avoid circular dependency (logger imports config)
12
+ // Migration messages are rare, so the simpler formatting is acceptable
13
+ console.info(`[quire] Migrating config from ${fromVersion} → ${toVersion}`)
12
14
  }
13
15
 
14
16
  /**
@@ -0,0 +1,281 @@
1
+ import esmock from 'esmock'
2
+ import sinon from 'sinon'
3
+ import test from 'ava'
4
+
5
+ /**
6
+ * Configuration Module Integration Tests
7
+ *
8
+ * Tests the Quire CLI configuration module behavior with mocked dependencies.
9
+ * Uses esmock to isolate the module from the actual Conf library and filesystem.
10
+ */
11
+
12
+ test.beforeEach((t) => {
13
+ t.context.sandbox = sinon.createSandbox()
14
+ })
15
+
16
+ test.afterEach.always((t) => {
17
+ t.context.sandbox.restore()
18
+ })
19
+
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // Configuration instantiation tests
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ test('config is created with correct project name and version', async (t) => {
25
+ const { sandbox } = t.context
26
+
27
+ let capturedOptions = null
28
+ const MockConf = sandbox.stub().callsFake(function (options) {
29
+ capturedOptions = options
30
+ return {
31
+ get: sandbox.stub(),
32
+ set: sandbox.stub()
33
+ }
34
+ })
35
+
36
+ await esmock('./config.js', {
37
+ conf: { default: MockConf },
38
+ '#src/packageConfig.js': { name: '@thegetty/quire-cli', version: '1.0.0' }
39
+ })
40
+
41
+ t.true(MockConf.calledOnce)
42
+ t.is(capturedOptions.projectName, '@thegetty/quire-cli')
43
+ t.is(capturedOptions.projectVersion, '1.0.0')
44
+ t.is(capturedOptions.projectSuffix, '')
45
+ })
46
+
47
+ test('config is created with correct options', async (t) => {
48
+ const { sandbox } = t.context
49
+
50
+ let capturedOptions = null
51
+ const MockConf = sandbox.stub().callsFake(function (options) {
52
+ capturedOptions = options
53
+ return {
54
+ get: sandbox.stub(),
55
+ set: sandbox.stub()
56
+ }
57
+ })
58
+
59
+ await esmock('./config.js', {
60
+ conf: { default: MockConf },
61
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
62
+ })
63
+
64
+ t.true(capturedOptions.clearInvalidConfig)
65
+ t.true(capturedOptions.watch)
66
+ t.is(typeof capturedOptions.beforeEachMigration, 'function')
67
+ })
68
+
69
+ test('config passes defaults to Conf constructor', async (t) => {
70
+ const { sandbox } = t.context
71
+
72
+ let capturedOptions = null
73
+ const MockConf = sandbox.stub().callsFake(function (options) {
74
+ capturedOptions = options
75
+ return {
76
+ get: sandbox.stub(),
77
+ set: sandbox.stub()
78
+ }
79
+ })
80
+
81
+ const mockDefaults = {
82
+ logLevel: 'debug',
83
+ quireVersion: '2.0.0'
84
+ }
85
+
86
+ await esmock('./config.js', {
87
+ conf: { default: MockConf },
88
+ './defaults.js': { default: mockDefaults },
89
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
90
+ })
91
+
92
+ t.deepEqual(capturedOptions.defaults, mockDefaults)
93
+ })
94
+
95
+ test('config passes schema to Conf constructor', async (t) => {
96
+ const { sandbox } = t.context
97
+
98
+ let capturedOptions = null
99
+ const MockConf = sandbox.stub().callsFake(function (options) {
100
+ capturedOptions = options
101
+ return {
102
+ get: sandbox.stub(),
103
+ set: sandbox.stub()
104
+ }
105
+ })
106
+
107
+ const mockSchema = {
108
+ logLevel: { type: 'string' },
109
+ quireVersion: { type: 'string' }
110
+ }
111
+
112
+ await esmock('./config.js', {
113
+ conf: { default: MockConf },
114
+ './schema.js': { default: mockSchema },
115
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
116
+ })
117
+
118
+ t.deepEqual(capturedOptions.schema, mockSchema)
119
+ })
120
+
121
+ test('config passes migrations to Conf constructor', async (t) => {
122
+ const { sandbox } = t.context
123
+
124
+ let capturedOptions = null
125
+ const MockConf = sandbox.stub().callsFake(function (options) {
126
+ capturedOptions = options
127
+ return {
128
+ get: sandbox.stub(),
129
+ set: sandbox.stub()
130
+ }
131
+ })
132
+
133
+ const mockMigrations = {
134
+ '1.0.0': () => {},
135
+ '2.0.0': () => {}
136
+ }
137
+
138
+ await esmock('./config.js', {
139
+ conf: { default: MockConf },
140
+ './migrations.js': { default: mockMigrations },
141
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
142
+ })
143
+
144
+ t.deepEqual(capturedOptions.migrations, mockMigrations)
145
+ })
146
+
147
+ // ─────────────────────────────────────────────────────────────────────────────
148
+ // Migration callback tests
149
+ // ─────────────────────────────────────────────────────────────────────────────
150
+
151
+ test.serial('beforeEachMigration logs migration info', async (t) => {
152
+ const { sandbox } = t.context
153
+
154
+ let capturedOptions = null
155
+ const MockConf = sandbox.stub().callsFake(function (options) {
156
+ capturedOptions = options
157
+ return {
158
+ get: sandbox.stub(),
159
+ set: sandbox.stub()
160
+ }
161
+ })
162
+
163
+ const consoleInfoStub = sandbox.stub(console, 'info')
164
+
165
+ await esmock('./config.js', {
166
+ conf: { default: MockConf },
167
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
168
+ })
169
+
170
+ // Call the beforeEachMigration callback
171
+ const mockStore = { get: sandbox.stub(), set: sandbox.stub() }
172
+ const mockContext = { fromVersion: '0.9.0', toVersion: '1.0.0' }
173
+ capturedOptions.beforeEachMigration(mockStore, mockContext)
174
+
175
+ t.true(consoleInfoStub.calledOnce)
176
+ t.true(consoleInfoStub.firstCall.args[0].includes('0.9.0'))
177
+ t.true(consoleInfoStub.firstCall.args[0].includes('1.0.0'))
178
+ t.true(consoleInfoStub.firstCall.args[0].includes('Migrating'))
179
+ })
180
+
181
+ // ─────────────────────────────────────────────────────────────────────────────
182
+ // Module export tests
183
+ // ─────────────────────────────────────────────────────────────────────────────
184
+
185
+ test('config module exports Conf instance as default', async (t) => {
186
+ const { sandbox } = t.context
187
+
188
+ const mockInstance = {
189
+ get: sandbox.stub().returns('value'),
190
+ set: sandbox.stub(),
191
+ has: sandbox.stub().returns(true),
192
+ delete: sandbox.stub()
193
+ }
194
+
195
+ const MockConf = sandbox.stub().returns(mockInstance)
196
+
197
+ const config = await esmock('./config.js', {
198
+ conf: { default: MockConf },
199
+ '#src/packageConfig.js': { name: 'test', version: '1.0.0' }
200
+ })
201
+
202
+ t.is(config.default, mockInstance)
203
+ })
204
+
205
+ // ─────────────────────────────────────────────────────────────────────────────
206
+ // Default values tests
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+
209
+ test('defaults module exports expected configuration keys', async (t) => {
210
+ const defaults = await import('./defaults.js')
211
+
212
+ t.true('logLevel' in defaults.default)
213
+ t.true('projectTemplate' in defaults.default)
214
+ t.true('quire11tyPath' in defaults.default)
215
+ t.true('quireVersion' in defaults.default)
216
+ t.true('updateChannel' in defaults.default)
217
+ t.true('updateInterval' in defaults.default)
218
+ t.true('versionFile' in defaults.default)
219
+ })
220
+
221
+ test('defaults module has reasonable default values', async (t) => {
222
+ const defaults = await import('./defaults.js')
223
+
224
+ t.is(defaults.default.logLevel, 'info')
225
+ t.is(defaults.default.quireVersion, 'latest')
226
+ t.is(defaults.default.versionFile, '.quire')
227
+ t.true(defaults.default.projectTemplate.includes('github.com'))
228
+ })
229
+
230
+ // ─────────────────────────────────────────────────────────────────────────────
231
+ // Schema tests
232
+ // ─────────────────────────────────────────────────────────────────────────────
233
+
234
+ test('schema module defines types for all default keys', async (t) => {
235
+ const schema = await import('./schema.js')
236
+ const defaults = await import('./defaults.js')
237
+
238
+ const schemaKeys = Object.keys(schema.default)
239
+ const defaultKeys = Object.keys(defaults.default)
240
+
241
+ // All default keys should have schema definitions
242
+ for (const key of defaultKeys) {
243
+ t.true(schemaKeys.includes(key), `Schema should define ${key}`)
244
+ }
245
+ })
246
+
247
+ test('schema module defines valid types for all properties', async (t) => {
248
+ const schema = await import('./schema.js')
249
+
250
+ const validTypes = ['string', 'boolean', 'number', 'object', 'array']
251
+ for (const [key, value] of Object.entries(schema.default)) {
252
+ t.true(validTypes.includes(value.type), `${key} should have a valid type`)
253
+ }
254
+ })
255
+
256
+ // ─────────────────────────────────────────────────────────────────────────────
257
+ // Migrations tests
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+
260
+ test('migrations module exports migration functions', async (t) => {
261
+ const migrations = await import('./migrations.js')
262
+
263
+ t.is(typeof migrations.default, 'object')
264
+ t.true('1.0.0' in migrations.default)
265
+ })
266
+
267
+ test('migrations 1.0.0 sets updateChannel', async (t) => {
268
+ const { sandbox } = t.context
269
+
270
+ const migrations = await import('./migrations.js')
271
+
272
+ const mockStore = {
273
+ get: sandbox.stub(),
274
+ set: sandbox.stub()
275
+ }
276
+
277
+ migrations.default['1.0.0'](mockStore)
278
+
279
+ t.true(mockStore.set.calledOnce)
280
+ t.true(mockStore.set.calledWith('updateChannel', 'latest'))
281
+ })
@@ -5,10 +5,49 @@
5
5
  * @see https://github.com/sindresorhus/conf#defaults
6
6
  */
7
7
  export default {
8
+ /**
9
+ * Enable debug output by default (DEBUG=quire:* namespace logging).
10
+ * Can be overridden per-command with --debug or --no-debug.
11
+ */
12
+ debug: false,
13
+ /**
14
+ * Default EPUB engine for quire epub command.
15
+ */
16
+ epubEngine: 'epubjs',
8
17
  /**
9
18
  * Logging level for the Quire CLI output.
10
19
  */
11
20
  logLevel: 'info',
21
+ /**
22
+ * Prefix text for log output messages.
23
+ */
24
+ logPrefix: 'quire',
25
+ /**
26
+ * Style of the log prefix.
27
+ * - 'bracket': [quire] message
28
+ * - 'emoji': 📖 message
29
+ * - 'plain': quire: message
30
+ * - 'none': message (no prefix)
31
+ */
32
+ logPrefixStyle: 'bracket',
33
+ /**
34
+ * Whether to show log level labels (INFO, WARN, ERROR).
35
+ * Defaults to false for cleaner user output.
36
+ */
37
+ logShowLevel: false,
38
+ /**
39
+ * Whether to use colored output in terminal.
40
+ */
41
+ logUseColor: true,
42
+ /**
43
+ * Whether to color message text by log level (e.g., red for errors).
44
+ * Requires logUseColor to be enabled.
45
+ */
46
+ logColorMessages: true,
47
+ /**
48
+ * Default PDF engine for quire pdf command.
49
+ */
50
+ pdfEngine: 'pagedjs',
12
51
  /**
13
52
  * Project starter template to use when creating new projects.
14
53
  */
@@ -30,6 +69,11 @@ export default {
30
69
  * and to quire-11ty version for a prject.
31
70
  */
32
71
  updateInterval: 'DAILY',
72
+ /**
73
+ * Enable verbose output by default (detailed progress with paths, timing).
74
+ * Can be overridden per-command with --verbose or --no-verbose.
75
+ */
76
+ verbose: false,
33
77
  /**
34
78
  * File name for the quire-11ty version file.
35
79
  */