@thegetty/quire-cli 1.0.0-rc.45 → 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 (245) 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 +10 -27
  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/man/quire.1 +0 -341
  56. package/man/quire.1.md +0 -232
  57. package/schemas/config.schema.json +0 -194
  58. package/schemas/figures.schema.json +0 -56
  59. package/schemas/layout.schema.json +0 -5
  60. package/schemas/objects.schema.json +0 -62
  61. package/schemas/publication.schema.json +0 -140
  62. package/schemas/references.schema.json +0 -29
  63. package/src/Command.spec.js +0 -99
  64. package/src/commands/build.spec.js +0 -113
  65. package/src/commands/build.test.js +0 -397
  66. package/src/commands/clean.spec.js +0 -118
  67. package/src/commands/clean.test.js +0 -382
  68. package/src/commands/config.js +0 -238
  69. package/src/commands/config.spec.js +0 -107
  70. package/src/commands/config.test.js +0 -845
  71. package/src/commands/create.spec.js +0 -112
  72. package/src/commands/create.test.js +0 -415
  73. package/src/commands/doctor.js +0 -251
  74. package/src/commands/doctor.spec.js +0 -115
  75. package/src/commands/doctor.test.js +0 -1409
  76. package/src/commands/epub.spec.js +0 -114
  77. package/src/commands/epub.test.js +0 -503
  78. package/src/commands/help.js +0 -60
  79. package/src/commands/help.test.js +0 -132
  80. package/src/commands/info.spec.js +0 -72
  81. package/src/commands/info.test.js +0 -512
  82. package/src/commands/pdf.spec.js +0 -114
  83. package/src/commands/pdf.test.js +0 -464
  84. package/src/commands/preview.spec.js +0 -97
  85. package/src/commands/preview.test.js +0 -250
  86. package/src/commands/use.js +0 -56
  87. package/src/commands/use.spec.js +0 -61
  88. package/src/commands/use.test.js +0 -280
  89. package/src/commands/validate.spec.js +0 -90
  90. package/src/commands/validate.test.js +0 -378
  91. package/src/commands/workflows.js +0 -70
  92. package/src/errors/build/build-failed-error.js +0 -19
  93. package/src/errors/build/config-field-missing-error.js +0 -20
  94. package/src/errors/build/config-file-not-found-error.js +0 -20
  95. package/src/errors/build/index.js +0 -11
  96. package/src/errors/help/help-topic-not-found-error.js +0 -30
  97. package/src/errors/help/index.js +0 -8
  98. package/src/errors/index.js +0 -58
  99. package/src/errors/input/index.js +0 -8
  100. package/src/errors/input/invalid-input-error.js +0 -21
  101. package/src/errors/install/dependency-install-error.js +0 -19
  102. package/src/errors/install/directory-not-empty-error.js +0 -25
  103. package/src/errors/install/index.js +0 -12
  104. package/src/errors/install/invalid-path-error.js +0 -31
  105. package/src/errors/install/invalid-starter-error.js +0 -27
  106. package/src/errors/install/version-not-found-error.js +0 -35
  107. package/src/errors/output/epub-generation-error.js +0 -19
  108. package/src/errors/output/index.js +0 -14
  109. package/src/errors/output/invalid-epub-library-error.js +0 -20
  110. package/src/errors/output/invalid-pdf-library-error.js +0 -20
  111. package/src/errors/output/missing-build-output-error.js +0 -21
  112. package/src/errors/output/pdf-generation-error.js +0 -24
  113. package/src/errors/output/tool-not-found-error.js +0 -37
  114. package/src/errors/project/index.js +0 -10
  115. package/src/errors/project/not-in-project-error.js +0 -20
  116. package/src/errors/project/project-create-error.js +0 -21
  117. package/src/errors/quire-error.js +0 -27
  118. package/src/errors/validation/index.js +0 -3
  119. package/src/errors/validation/unknown-config-key-error.js +0 -27
  120. package/src/errors/validation/unknown-config-operation-error.js +0 -24
  121. package/src/helpers/docs-url.js +0 -32
  122. package/src/helpers/pager.js +0 -59
  123. package/src/helpers/pager.test.js +0 -85
  124. package/src/helpers/suggest-similar.js +0 -124
  125. package/src/helpers/suggest-similar.test.js +0 -108
  126. package/src/helpers/test-cwd.test.js +0 -192
  127. package/src/lib/11ty/index.test.js +0 -655
  128. package/src/lib/commander/index.js +0 -103
  129. package/src/lib/commander/index.test.js +0 -86
  130. package/src/lib/commander/options.js +0 -237
  131. package/src/lib/commander/options.test.js +0 -205
  132. package/src/lib/conf/build-status.js +0 -103
  133. package/src/lib/conf/build-status.test.js +0 -247
  134. package/src/lib/conf/config.test.js +0 -281
  135. package/src/lib/conf/format.js +0 -60
  136. package/src/lib/conf/format.test.js +0 -106
  137. package/src/lib/conf/helpers.js +0 -91
  138. package/src/lib/conf/helpers.test.js +0 -136
  139. package/src/lib/conf/index.js +0 -22
  140. package/src/lib/constants.js +0 -28
  141. package/src/lib/doctor/README.md +0 -667
  142. package/src/lib/doctor/checks/environment/cli-version.js +0 -62
  143. package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
  144. package/src/lib/doctor/checks/environment/git-available.js +0 -77
  145. package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
  146. package/src/lib/doctor/checks/environment/index.js +0 -13
  147. package/src/lib/doctor/checks/environment/node-version.js +0 -66
  148. package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
  149. package/src/lib/doctor/checks/environment/npm-available.js +0 -66
  150. package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
  151. package/src/lib/doctor/checks/environment/os-info.js +0 -48
  152. package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
  153. package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
  154. package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
  155. package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
  156. package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
  157. package/src/lib/doctor/checks/outputs/index.js +0 -10
  158. package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
  159. package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
  160. package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
  161. package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
  162. package/src/lib/doctor/checks/project/data-files.js +0 -56
  163. package/src/lib/doctor/checks/project/data-files.test.js +0 -125
  164. package/src/lib/doctor/checks/project/dependencies.js +0 -53
  165. package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
  166. package/src/lib/doctor/checks/project/index.js +0 -11
  167. package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
  168. package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
  169. package/src/lib/doctor/checks/project/quire-project.js +0 -38
  170. package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
  171. package/src/lib/doctor/checks/tools/index.js +0 -10
  172. package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
  173. package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
  174. package/src/lib/doctor/checks/tools/prince-available.js +0 -81
  175. package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
  176. package/src/lib/doctor/constants.js +0 -39
  177. package/src/lib/doctor/formatDuration.js +0 -108
  178. package/src/lib/doctor/formatDuration.test.js +0 -76
  179. package/src/lib/doctor/formatters/human.js +0 -257
  180. package/src/lib/doctor/formatters/human.test.js +0 -463
  181. package/src/lib/doctor/formatters/index.js +0 -8
  182. package/src/lib/doctor/formatters/json.js +0 -78
  183. package/src/lib/doctor/formatters/json.test.js +0 -174
  184. package/src/lib/doctor/formatters/shared.js +0 -129
  185. package/src/lib/doctor/formatters/shared.test.js +0 -194
  186. package/src/lib/doctor/index.js +0 -271
  187. package/src/lib/doctor/index.test.js +0 -797
  188. package/src/lib/epub/engines.js +0 -46
  189. package/src/lib/epub/index.test.js +0 -518
  190. package/src/lib/epub/pandoc.test.js +0 -122
  191. package/src/lib/epub/schema.js +0 -21
  192. package/src/lib/error/README.md +0 -170
  193. package/src/lib/error/handler.js +0 -107
  194. package/src/lib/git/index.spec.js +0 -80
  195. package/src/lib/git/index.test.js +0 -453
  196. package/src/lib/help/frontmatter.js +0 -77
  197. package/src/lib/help/frontmatter.test.js +0 -139
  198. package/src/lib/help/index.js +0 -146
  199. package/src/lib/help/index.test.js +0 -188
  200. package/src/lib/help/topics/configuration.md +0 -77
  201. package/src/lib/help/topics/debugging.md +0 -69
  202. package/src/lib/help/topics/epub.md +0 -74
  203. package/src/lib/help/topics/pdf.md +0 -74
  204. package/src/lib/help/topics/publishing.md +0 -80
  205. package/src/lib/help/topics/workflows.md +0 -50
  206. package/src/lib/installer/index.js +0 -309
  207. package/src/lib/installer/index.spec.js +0 -83
  208. package/src/lib/installer/index.test.js +0 -545
  209. package/src/lib/logger/README.md +0 -424
  210. package/src/lib/logger/debug.js +0 -132
  211. package/src/lib/logger/debug.spec.js +0 -130
  212. package/src/lib/logger/debug.test.js +0 -128
  213. package/src/lib/logger/index.js +0 -228
  214. package/src/lib/logger/index.spec.js +0 -131
  215. package/src/lib/logger/index.test.js +0 -578
  216. package/src/lib/npm/README.md +0 -127
  217. package/src/lib/npm/index.js +0 -198
  218. package/src/lib/npm/index.spec.js +0 -60
  219. package/src/lib/npm/index.test.js +0 -355
  220. package/src/lib/pdf/engines.js +0 -46
  221. package/src/lib/pdf/index.test.js +0 -708
  222. package/src/lib/pdf/paged.test.js +0 -366
  223. package/src/lib/pdf/prince.test.js +0 -202
  224. package/src/lib/pdf/schema.js +0 -21
  225. package/src/lib/pdf/split.test.js +0 -445
  226. package/src/lib/platform.js +0 -95
  227. package/src/lib/process/manager.js +0 -110
  228. package/src/lib/process/manager.test.js +0 -55
  229. package/src/lib/project/build.js +0 -162
  230. package/src/lib/project/build.test.js +0 -275
  231. package/src/lib/project/config.js +0 -48
  232. package/src/lib/project/config.test.js +0 -134
  233. package/src/lib/project/detect.js +0 -47
  234. package/src/lib/project/detect.test.js +0 -157
  235. package/src/lib/project/index.js +0 -56
  236. package/src/lib/project/output-paths.js +0 -87
  237. package/src/lib/project/output-paths.test.js +0 -66
  238. package/src/lib/project/paths.js +0 -272
  239. package/src/lib/project/version.js +0 -110
  240. package/src/lib/project/version.test.js +0 -350
  241. package/src/lib/reporter/index.test.js +0 -862
  242. package/src/main.spec.js +0 -61
  243. package/src/main.test.js +0 -347
  244. package/src/validators/validate-data-files.js +0 -154
  245. 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
- }