@thegetty/quire-cli 1.0.0-rc.44 → 1.0.0-rc.46

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 (243) hide show
  1. package/CHANGELOG.md +0 -36
  2. package/README.md +0 -9
  3. package/bin/cli.js +1 -46
  4. package/package.json +9 -25
  5. package/src/Command.js +6 -26
  6. package/src/commands/README.md +122 -213
  7. package/src/commands/build.js +37 -45
  8. package/src/commands/clean.js +19 -33
  9. package/src/commands/conf.js +43 -0
  10. package/src/commands/create.js +14 -42
  11. package/src/commands/epub.js +31 -67
  12. package/src/commands/index.js +2 -9
  13. package/src/commands/info.js +86 -88
  14. package/src/commands/pdf.js +82 -63
  15. package/src/commands/preview.js +31 -32
  16. package/src/commands/validate.js +20 -64
  17. package/src/commands/version.js +43 -0
  18. package/src/errors/validation/validation-error.js +11 -20
  19. package/src/helpers/clean.js +1 -1
  20. package/src/helpers/is-quire.js +31 -0
  21. package/src/helpers/test-cwd.js +6 -5
  22. package/src/helpers/which.js +4 -10
  23. package/src/lib/11ty/README.md +19 -135
  24. package/src/lib/11ty/api.js +93 -235
  25. package/src/lib/11ty/cli.js +37 -112
  26. package/src/lib/11ty/index.js +5 -64
  27. package/src/lib/11ty/paths.js +103 -0
  28. package/src/lib/README.md +0 -330
  29. package/src/lib/conf/README.md +69 -86
  30. package/src/lib/conf/config.js +3 -5
  31. package/src/lib/conf/defaults.js +0 -65
  32. package/src/lib/conf/schema.js +9 -103
  33. package/src/lib/epub/README.md +2 -133
  34. package/src/lib/epub/epub.js +11 -36
  35. package/src/lib/epub/index.js +21 -111
  36. package/src/lib/epub/pandoc.js +4 -33
  37. package/src/lib/git/README.md +2 -151
  38. package/src/lib/git/index.js +13 -217
  39. package/src/lib/i18n/README.md +3 -0
  40. package/src/lib/i18n/config.js +53 -0
  41. package/src/lib/i18n/index.js +43 -0
  42. package/src/lib/i18n/localeService.js +58 -0
  43. package/src/lib/pdf/README.md +0 -131
  44. package/src/lib/pdf/index.js +21 -124
  45. package/src/lib/pdf/paged.js +52 -100
  46. package/src/lib/pdf/prince.js +37 -115
  47. package/src/lib/pdf/split.js +33 -61
  48. package/src/lib/quire/README.md +19 -0
  49. package/src/lib/quire/index.js +353 -0
  50. package/src/lib/reporter/README.md +2 -211
  51. package/src/lib/reporter/index.js +0 -607
  52. package/src/main.js +48 -221
  53. package/src/packageConfig.js +0 -17
  54. package/src/validators/utils.js +1 -2
  55. package/schemas/config.schema.json +0 -194
  56. package/schemas/figures.schema.json +0 -56
  57. package/schemas/layout.schema.json +0 -5
  58. package/schemas/objects.schema.json +0 -62
  59. package/schemas/publication.schema.json +0 -140
  60. package/schemas/references.schema.json +0 -29
  61. package/src/Command.spec.js +0 -99
  62. package/src/commands/build.spec.js +0 -113
  63. package/src/commands/build.test.js +0 -397
  64. package/src/commands/clean.spec.js +0 -118
  65. package/src/commands/clean.test.js +0 -382
  66. package/src/commands/config.js +0 -238
  67. package/src/commands/config.spec.js +0 -107
  68. package/src/commands/config.test.js +0 -845
  69. package/src/commands/create.spec.js +0 -112
  70. package/src/commands/create.test.js +0 -415
  71. package/src/commands/doctor.js +0 -251
  72. package/src/commands/doctor.spec.js +0 -115
  73. package/src/commands/doctor.test.js +0 -1409
  74. package/src/commands/epub.spec.js +0 -114
  75. package/src/commands/epub.test.js +0 -503
  76. package/src/commands/help.js +0 -60
  77. package/src/commands/help.test.js +0 -132
  78. package/src/commands/info.spec.js +0 -72
  79. package/src/commands/info.test.js +0 -512
  80. package/src/commands/pdf.spec.js +0 -114
  81. package/src/commands/pdf.test.js +0 -464
  82. package/src/commands/preview.spec.js +0 -97
  83. package/src/commands/preview.test.js +0 -250
  84. package/src/commands/use.js +0 -56
  85. package/src/commands/use.spec.js +0 -61
  86. package/src/commands/use.test.js +0 -280
  87. package/src/commands/validate.spec.js +0 -90
  88. package/src/commands/validate.test.js +0 -378
  89. package/src/commands/workflows.js +0 -70
  90. package/src/errors/build/build-failed-error.js +0 -19
  91. package/src/errors/build/config-field-missing-error.js +0 -20
  92. package/src/errors/build/config-file-not-found-error.js +0 -20
  93. package/src/errors/build/index.js +0 -11
  94. package/src/errors/help/help-topic-not-found-error.js +0 -30
  95. package/src/errors/help/index.js +0 -8
  96. package/src/errors/index.js +0 -58
  97. package/src/errors/input/index.js +0 -8
  98. package/src/errors/input/invalid-input-error.js +0 -21
  99. package/src/errors/install/dependency-install-error.js +0 -19
  100. package/src/errors/install/directory-not-empty-error.js +0 -25
  101. package/src/errors/install/index.js +0 -12
  102. package/src/errors/install/invalid-path-error.js +0 -31
  103. package/src/errors/install/invalid-starter-error.js +0 -27
  104. package/src/errors/install/version-not-found-error.js +0 -35
  105. package/src/errors/output/epub-generation-error.js +0 -19
  106. package/src/errors/output/index.js +0 -14
  107. package/src/errors/output/invalid-epub-library-error.js +0 -20
  108. package/src/errors/output/invalid-pdf-library-error.js +0 -20
  109. package/src/errors/output/missing-build-output-error.js +0 -21
  110. package/src/errors/output/pdf-generation-error.js +0 -24
  111. package/src/errors/output/tool-not-found-error.js +0 -37
  112. package/src/errors/project/index.js +0 -10
  113. package/src/errors/project/not-in-project-error.js +0 -20
  114. package/src/errors/project/project-create-error.js +0 -21
  115. package/src/errors/quire-error.js +0 -27
  116. package/src/errors/validation/index.js +0 -3
  117. package/src/errors/validation/unknown-config-key-error.js +0 -27
  118. package/src/errors/validation/unknown-config-operation-error.js +0 -24
  119. package/src/helpers/docs-url.js +0 -32
  120. package/src/helpers/pager.js +0 -59
  121. package/src/helpers/pager.test.js +0 -85
  122. package/src/helpers/suggest-similar.js +0 -124
  123. package/src/helpers/suggest-similar.test.js +0 -108
  124. package/src/helpers/test-cwd.test.js +0 -192
  125. package/src/lib/11ty/index.test.js +0 -655
  126. package/src/lib/commander/index.js +0 -103
  127. package/src/lib/commander/index.test.js +0 -86
  128. package/src/lib/commander/options.js +0 -237
  129. package/src/lib/commander/options.test.js +0 -205
  130. package/src/lib/conf/build-status.js +0 -103
  131. package/src/lib/conf/build-status.test.js +0 -247
  132. package/src/lib/conf/config.test.js +0 -281
  133. package/src/lib/conf/format.js +0 -60
  134. package/src/lib/conf/format.test.js +0 -106
  135. package/src/lib/conf/helpers.js +0 -91
  136. package/src/lib/conf/helpers.test.js +0 -136
  137. package/src/lib/conf/index.js +0 -22
  138. package/src/lib/constants.js +0 -28
  139. package/src/lib/doctor/README.md +0 -667
  140. package/src/lib/doctor/checks/environment/cli-version.js +0 -62
  141. package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
  142. package/src/lib/doctor/checks/environment/git-available.js +0 -77
  143. package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
  144. package/src/lib/doctor/checks/environment/index.js +0 -13
  145. package/src/lib/doctor/checks/environment/node-version.js +0 -66
  146. package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
  147. package/src/lib/doctor/checks/environment/npm-available.js +0 -66
  148. package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
  149. package/src/lib/doctor/checks/environment/os-info.js +0 -48
  150. package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
  151. package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
  152. package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
  153. package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
  154. package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
  155. package/src/lib/doctor/checks/outputs/index.js +0 -10
  156. package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
  157. package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
  158. package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
  159. package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
  160. package/src/lib/doctor/checks/project/data-files.js +0 -56
  161. package/src/lib/doctor/checks/project/data-files.test.js +0 -125
  162. package/src/lib/doctor/checks/project/dependencies.js +0 -53
  163. package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
  164. package/src/lib/doctor/checks/project/index.js +0 -11
  165. package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
  166. package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
  167. package/src/lib/doctor/checks/project/quire-project.js +0 -38
  168. package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
  169. package/src/lib/doctor/checks/tools/index.js +0 -10
  170. package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
  171. package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
  172. package/src/lib/doctor/checks/tools/prince-available.js +0 -81
  173. package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
  174. package/src/lib/doctor/constants.js +0 -39
  175. package/src/lib/doctor/formatDuration.js +0 -108
  176. package/src/lib/doctor/formatDuration.test.js +0 -76
  177. package/src/lib/doctor/formatters/human.js +0 -257
  178. package/src/lib/doctor/formatters/human.test.js +0 -463
  179. package/src/lib/doctor/formatters/index.js +0 -8
  180. package/src/lib/doctor/formatters/json.js +0 -78
  181. package/src/lib/doctor/formatters/json.test.js +0 -174
  182. package/src/lib/doctor/formatters/shared.js +0 -129
  183. package/src/lib/doctor/formatters/shared.test.js +0 -194
  184. package/src/lib/doctor/index.js +0 -271
  185. package/src/lib/doctor/index.test.js +0 -797
  186. package/src/lib/epub/engines.js +0 -46
  187. package/src/lib/epub/index.test.js +0 -518
  188. package/src/lib/epub/pandoc.test.js +0 -122
  189. package/src/lib/epub/schema.js +0 -21
  190. package/src/lib/error/README.md +0 -170
  191. package/src/lib/error/handler.js +0 -107
  192. package/src/lib/git/index.spec.js +0 -80
  193. package/src/lib/git/index.test.js +0 -453
  194. package/src/lib/help/frontmatter.js +0 -77
  195. package/src/lib/help/frontmatter.test.js +0 -139
  196. package/src/lib/help/index.js +0 -146
  197. package/src/lib/help/index.test.js +0 -188
  198. package/src/lib/help/topics/configuration.md +0 -77
  199. package/src/lib/help/topics/debugging.md +0 -69
  200. package/src/lib/help/topics/epub.md +0 -74
  201. package/src/lib/help/topics/pdf.md +0 -74
  202. package/src/lib/help/topics/publishing.md +0 -80
  203. package/src/lib/help/topics/workflows.md +0 -50
  204. package/src/lib/installer/index.js +0 -309
  205. package/src/lib/installer/index.spec.js +0 -83
  206. package/src/lib/installer/index.test.js +0 -545
  207. package/src/lib/logger/README.md +0 -424
  208. package/src/lib/logger/debug.js +0 -132
  209. package/src/lib/logger/debug.spec.js +0 -130
  210. package/src/lib/logger/debug.test.js +0 -128
  211. package/src/lib/logger/index.js +0 -228
  212. package/src/lib/logger/index.spec.js +0 -131
  213. package/src/lib/logger/index.test.js +0 -578
  214. package/src/lib/npm/README.md +0 -127
  215. package/src/lib/npm/index.js +0 -198
  216. package/src/lib/npm/index.spec.js +0 -60
  217. package/src/lib/npm/index.test.js +0 -355
  218. package/src/lib/pdf/engines.js +0 -46
  219. package/src/lib/pdf/index.test.js +0 -708
  220. package/src/lib/pdf/paged.test.js +0 -366
  221. package/src/lib/pdf/prince.test.js +0 -202
  222. package/src/lib/pdf/schema.js +0 -21
  223. package/src/lib/pdf/split.test.js +0 -445
  224. package/src/lib/platform.js +0 -95
  225. package/src/lib/process/manager.js +0 -110
  226. package/src/lib/process/manager.test.js +0 -55
  227. package/src/lib/project/build.js +0 -162
  228. package/src/lib/project/build.test.js +0 -275
  229. package/src/lib/project/config.js +0 -48
  230. package/src/lib/project/config.test.js +0 -134
  231. package/src/lib/project/detect.js +0 -47
  232. package/src/lib/project/detect.test.js +0 -157
  233. package/src/lib/project/index.js +0 -56
  234. package/src/lib/project/output-paths.js +0 -87
  235. package/src/lib/project/output-paths.test.js +0 -66
  236. package/src/lib/project/paths.js +0 -272
  237. package/src/lib/project/version.js +0 -110
  238. package/src/lib/project/version.test.js +0 -350
  239. package/src/lib/reporter/index.test.js +0 -862
  240. package/src/main.spec.js +0 -61
  241. package/src/main.test.js +0 -347
  242. package/src/validators/validate-data-files.js +0 -154
  243. package/src/validators/validate-data-files.test.js +0 -217
@@ -1,103 +0,0 @@
1
- /**
2
- * Per-project build status persistence
3
- *
4
- * Records command outcomes (ok/failed) for build, pdf, and epub commands
5
- * so that `quire doctor` can distinguish "never ran" from "ran and failed"
6
- * when no output files exist on disk.
7
- *
8
- * Status is stored in the global CLI config under the `projects` key,
9
- * keyed by a SHA-256 hash of the project's absolute path to avoid
10
- * dot-notation traversal issues.
11
- *
12
- * @module lib/conf/build-status
13
- */
14
- import { createHash } from 'node:crypto'
15
- import config from '#lib/conf/config.js'
16
- import createDebug from '#debug'
17
-
18
- const debug = createDebug('lib:conf:build-status')
19
-
20
- /**
21
- * Commands whose status is tracked
22
- * @type {readonly string[]}
23
- */
24
- export const TRACKED_COMMANDS = Object.freeze(['build', 'pdf', 'epub'])
25
-
26
- /**
27
- * Derive a fixed-length, dot-safe config key from a project path
28
- *
29
- * @param {string} projectPath - Absolute path to the project directory
30
- * @returns {string} First 12 hex characters of the SHA-256 hash
31
- */
32
- export function projectKey(projectPath) {
33
- return createHash('sha256').update(projectPath).digest('hex').slice(0, 12)
34
- }
35
-
36
- /**
37
- * Record the outcome of a command for a project
38
- *
39
- * @param {string} projectPath - Absolute path to the project directory
40
- * @param {string} command - Command name ('build', 'pdf', or 'epub')
41
- * @param {'ok'|'failed'} status - Outcome of the command
42
- */
43
- export function recordStatus(projectPath, command, status) {
44
- if (!TRACKED_COMMANDS.includes(command)) {
45
- debug('ignoring untracked command: %s', command)
46
- return
47
- }
48
-
49
- const key = projectKey(projectPath)
50
- const projects = config.get('projects') || {}
51
-
52
- const entry = projects[key] || { projectPath }
53
- entry.projectPath = projectPath
54
- entry.buildStatus = {
55
- ...entry.buildStatus,
56
- [command]: {
57
- status,
58
- timestamp: Date.now(),
59
- },
60
- }
61
- projects[key] = entry
62
-
63
- config.set('projects', projects)
64
- debug('recorded %s=%s for %s (key=%s)', command, status, projectPath, key)
65
- }
66
-
67
- /**
68
- * Retrieve the stored status for a command in a project
69
- *
70
- * @param {string} projectPath - Absolute path to the project directory
71
- * @param {string} command - Command name ('build', 'pdf', or 'epub')
72
- * @returns {{ status: 'ok'|'failed', timestamp: number } | undefined}
73
- */
74
- export function getStatus(projectPath, command) {
75
- const key = projectKey(projectPath)
76
- const projects = config.get('projects') || {}
77
- const entry = projects[key]
78
-
79
- if (!entry || !entry.buildStatus) {
80
- debug('no status entry for key=%s', key)
81
- return undefined
82
- }
83
-
84
- return entry.buildStatus[command]
85
- }
86
-
87
- /**
88
- * Clear all stored status for a project
89
- *
90
- * @param {string} projectPath - Absolute path to the project directory
91
- */
92
- export function clearStatus(projectPath) {
93
- const key = projectKey(projectPath)
94
- const projects = config.get('projects') || {}
95
- const entry = projects[key]
96
-
97
- if (entry && entry.buildStatus) {
98
- delete entry.buildStatus
99
- projects[key] = entry
100
- config.set('projects', projects)
101
- debug('cleared build status for %s (key=%s)', projectPath, key)
102
- }
103
- }
@@ -1,247 +0,0 @@
1
- import esmock from 'esmock'
2
- import sinon from 'sinon'
3
- import test from 'ava'
4
-
5
- /**
6
- * Build Status Module Tests
7
- *
8
- * Tests the per-project build status persistence API.
9
- * Mocks the config module with a simple in-memory store.
10
- */
11
-
12
- test.beforeEach(async (t) => {
13
- const sandbox = sinon.createSandbox()
14
-
15
- // In-memory store that mimics config.get/set for 'projects'
16
- const store = {}
17
- const configMock = {
18
- default: {
19
- get: sandbox.stub().callsFake((key) => store[key]),
20
- set: sandbox.stub().callsFake((key, value) => { store[key] = value }),
21
- },
22
- }
23
-
24
- const mod = await esmock('./build-status.js', {
25
- '#lib/conf/config.js': configMock,
26
- })
27
-
28
- t.context = { sandbox, store, configMock, ...mod }
29
- })
30
-
31
- test.afterEach.always((t) => {
32
- t.context.sandbox.restore()
33
- })
34
-
35
- // ─────────────────────────────────────────────────────────────────────────────
36
- // projectKey
37
- // ─────────────────────────────────────────────────────────────────────────────
38
-
39
- test('projectKey returns a 12-character hex string', (t) => {
40
- const { projectKey } = t.context
41
- const key = projectKey('/Users/test/my-project')
42
- t.is(key.length, 12)
43
- t.regex(key, /^[0-9a-f]{12}$/)
44
- })
45
-
46
- test('projectKey returns the same key for the same path', (t) => {
47
- const { projectKey } = t.context
48
- const key1 = projectKey('/Users/test/my-project')
49
- const key2 = projectKey('/Users/test/my-project')
50
- t.is(key1, key2)
51
- })
52
-
53
- test('projectKey returns different keys for different paths', (t) => {
54
- const { projectKey } = t.context
55
- const key1 = projectKey('/Users/test/project-a')
56
- const key2 = projectKey('/Users/test/project-b')
57
- t.not(key1, key2)
58
- })
59
-
60
- // ─────────────────────────────────────────────────────────────────────────────
61
- // recordStatus
62
- // ─────────────────────────────────────────────────────────────────────────────
63
-
64
- test('recordStatus writes status with timestamp to config', (t) => {
65
- const { recordStatus, projectKey, configMock } = t.context
66
- const projectPath = '/Users/test/my-project'
67
-
68
- recordStatus(projectPath, 'build', 'ok')
69
-
70
- t.true(configMock.default.set.calledOnce)
71
- const [setKey, setValue] = configMock.default.set.firstCall.args
72
- t.is(setKey, 'projects')
73
-
74
- const key = projectKey(projectPath)
75
- const entry = setValue[key]
76
- t.is(entry.projectPath, projectPath)
77
- t.is(entry.buildStatus.build.status, 'ok')
78
- t.is(typeof entry.buildStatus.build.timestamp, 'number')
79
- })
80
-
81
- test('recordStatus records failed status', (t) => {
82
- const { recordStatus, projectKey, configMock } = t.context
83
- const projectPath = '/Users/test/my-project'
84
-
85
- recordStatus(projectPath, 'pdf', 'failed')
86
-
87
- const [, setValue] = configMock.default.set.firstCall.args
88
- const key = projectKey(projectPath)
89
- t.is(setValue[key].buildStatus.pdf.status, 'failed')
90
- })
91
-
92
- test('recordStatus silently ignores untracked commands', (t) => {
93
- const { recordStatus, configMock } = t.context
94
-
95
- recordStatus('/Users/test/my-project', 'preview', 'ok')
96
-
97
- t.false(configMock.default.set.called)
98
- })
99
-
100
- test('recordStatus preserves existing command entries', (t) => {
101
- const { recordStatus, projectKey, store } = t.context
102
- const projectPath = '/Users/test/my-project'
103
- const key = projectKey(projectPath)
104
-
105
- // Pre-populate with a build entry
106
- store.projects = {
107
- [key]: {
108
- projectPath,
109
- buildStatus: {
110
- build: { status: 'ok', timestamp: 1000 },
111
- },
112
- },
113
- }
114
-
115
- recordStatus(projectPath, 'pdf', 'failed')
116
-
117
- const entry = store.projects[key]
118
- t.is(entry.buildStatus.build.status, 'ok')
119
- t.is(entry.buildStatus.build.timestamp, 1000)
120
- t.is(entry.buildStatus.pdf.status, 'failed')
121
- })
122
-
123
- // ─────────────────────────────────────────────────────────────────────────────
124
- // getStatus
125
- // ─────────────────────────────────────────────────────────────────────────────
126
-
127
- test('getStatus returns stored status entry', (t) => {
128
- const { getStatus, projectKey, store } = t.context
129
- const projectPath = '/Users/test/my-project'
130
- const key = projectKey(projectPath)
131
-
132
- store.projects = {
133
- [key]: {
134
- projectPath,
135
- buildStatus: {
136
- build: { status: 'ok', timestamp: 1000 },
137
- },
138
- },
139
- }
140
-
141
- const result = getStatus(projectPath, 'build')
142
- t.deepEqual(result, { status: 'ok', timestamp: 1000 })
143
- })
144
-
145
- test('getStatus returns undefined for unknown project', (t) => {
146
- const { getStatus } = t.context
147
-
148
- const result = getStatus('/Users/test/unknown-project', 'build')
149
- t.is(result, undefined)
150
- })
151
-
152
- test('getStatus returns undefined for unrecorded command', (t) => {
153
- const { getStatus, projectKey, store } = t.context
154
- const projectPath = '/Users/test/my-project'
155
- const key = projectKey(projectPath)
156
-
157
- store.projects = {
158
- [key]: {
159
- projectPath,
160
- buildStatus: {
161
- build: { status: 'ok', timestamp: 1000 },
162
- },
163
- },
164
- }
165
-
166
- const result = getStatus(projectPath, 'epub')
167
- t.is(result, undefined)
168
- })
169
-
170
- // ─────────────────────────────────────────────────────────────────────────────
171
- // clearStatus
172
- // ─────────────────────────────────────────────────────────────────────────────
173
-
174
- test('clearStatus removes project entry', (t) => {
175
- const { clearStatus, projectKey, store, configMock } = t.context
176
- const projectPath = '/Users/test/my-project'
177
- const key = projectKey(projectPath)
178
-
179
- store.projects = {
180
- [key]: {
181
- projectPath,
182
- buildStatus: {
183
- build: { status: 'ok', timestamp: 1000 },
184
- },
185
- },
186
- }
187
-
188
- clearStatus(projectPath)
189
-
190
- const [, setValue] = configMock.default.set.firstCall.args
191
- t.truthy(setValue[key], 'project entry should still exist')
192
- t.is(setValue[key].projectPath, projectPath, 'projectPath should be preserved')
193
- t.is(setValue[key].buildStatus, undefined, 'buildStatus should be removed')
194
- })
195
-
196
- test('clearStatus is a no-op for unknown project', (t) => {
197
- const { clearStatus, configMock, store } = t.context
198
-
199
- store.projects = {}
200
- clearStatus('/Users/test/unknown-project')
201
-
202
- t.false(configMock.default.set.called)
203
- })
204
-
205
- test('clearStatus is a no-op when project has no buildStatus', (t) => {
206
- const { clearStatus, projectKey, configMock, store } = t.context
207
- const projectPath = '/Users/test/my-project'
208
- const key = projectKey(projectPath)
209
-
210
- store.projects = {
211
- [key]: { projectPath },
212
- }
213
-
214
- clearStatus(projectPath)
215
-
216
- t.false(configMock.default.set.called)
217
- })
218
-
219
- // ─────────────────────────────────────────────────────────────────────────────
220
- // Multiple projects
221
- // ─────────────────────────────────────────────────────────────────────────────
222
-
223
- test('multiple projects are tracked independently', (t) => {
224
- const { recordStatus, getStatus } = t.context
225
- const projectA = '/Users/test/project-a'
226
- const projectB = '/Users/test/project-b'
227
-
228
- recordStatus(projectA, 'build', 'ok')
229
- recordStatus(projectB, 'build', 'failed')
230
-
231
- t.is(getStatus(projectA, 'build').status, 'ok')
232
- t.is(getStatus(projectB, 'build').status, 'failed')
233
- })
234
-
235
- // ─────────────────────────────────────────────────────────────────────────────
236
- // TRACKED_COMMANDS
237
- // ─────────────────────────────────────────────────────────────────────────────
238
-
239
- test('TRACKED_COMMANDS contains build, pdf, and epub', (t) => {
240
- const { TRACKED_COMMANDS } = t.context
241
- t.deepEqual([...TRACKED_COMMANDS], ['build', 'pdf', 'epub'])
242
- })
243
-
244
- test('TRACKED_COMMANDS is frozen', (t) => {
245
- const { TRACKED_COMMANDS } = t.context
246
- t.true(Object.isFrozen(TRACKED_COMMANDS))
247
- })
@@ -1,281 +0,0 @@
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
- })
@@ -1,60 +0,0 @@
1
- /**
2
- * Display formatting functions for Quire configuration
3
- *
4
- * Provides human-readable formatting with optional chalk styling.
5
- * Separated from helpers.js to isolate the chalk dependency
6
- * from the pure validation/coercion functions.
7
- *
8
- * @see ./helpers.js for schema-aware validation and coercion
9
- */
10
- import chalk from 'chalk'
11
- import schema from './schema.js'
12
-
13
- /**
14
- * Get the description for a configuration key from the schema
15
- *
16
- * @param {string} key - Configuration key
17
- * @returns {string|undefined} Description string, or undefined if not found
18
- */
19
- export function getKeyDescription(key) {
20
- return schema[key]?.description
21
- }
22
-
23
- /**
24
- * Format all settings as a human-readable string
25
- *
26
- * @param {Object} store - Configuration store (key-value pairs)
27
- * @param {Object} [options]
28
- * @param {string} [options.configPath] - Path to the config file (shown in header)
29
- * @param {boolean} [options.showInternal=false] - Include __internal__ keys
30
- * @param {boolean} [options.useColor=false] - Apply chalk styling to output
31
- * @returns {string} Formatted multiline settings display
32
- */
33
- export function formatSettings(store, { configPath, showInternal = false, useColor = false } = {}) {
34
- const style = useColor
35
- ? { bold: chalk.bold, cyan: chalk.cyan, dim: chalk.dim }
36
- : { bold: (s) => s, cyan: (s) => s, dim: (s) => s }
37
-
38
- const lines = []
39
-
40
- if (configPath) {
41
- lines.push(style.bold(`quire-cli configuration ${configPath}`))
42
- } else {
43
- lines.push(style.bold('quire-cli configuration'))
44
- }
45
- lines.push('')
46
-
47
- for (const [key, value] of Object.entries(store)) {
48
- if ((key.startsWith('__internal__') || key === 'projects') && !showInternal) continue
49
- const description = getKeyDescription(key)
50
- lines.push(` ${style.cyan(key)}: ${JSON.stringify(value)}`)
51
- if (description) {
52
- lines.push(` ${style.dim(description)}`)
53
- }
54
- }
55
-
56
- lines.push('')
57
- lines.push(style.dim('Use "quire settings set <key> <value>" to change a setting'))
58
-
59
- return lines.join('\n')
60
- }