@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,862 +0,0 @@
1
- import esmock from 'esmock'
2
- import sinon from 'sinon'
3
- import test from 'ava'
4
- import reporter, { Reporter } from './index.js'
5
-
6
- /**
7
- * Reporter Module Tests
8
- *
9
- * Tests the Reporter class and singleton behavior for CLI progress feedback.
10
- * Uses esmock to mock ora for controlled testing.
11
- */
12
-
13
- test.beforeEach((t) => {
14
- t.context.sandbox = sinon.createSandbox()
15
- // Reset reporter state before each test
16
- reporter.reset()
17
- })
18
-
19
- test.afterEach.always((t) => {
20
- t.context.sandbox.restore()
21
- reporter.reset()
22
- })
23
-
24
- // ─────────────────────────────────────────────────────────────────────────────
25
- // Singleton behavior tests
26
- // ─────────────────────────────────────────────────────────────────────────────
27
-
28
- test('default export is a Reporter instance', (t) => {
29
- t.true(reporter instanceof Reporter)
30
- })
31
-
32
- test('Reporter class is exported for testing', (t) => {
33
- t.is(typeof Reporter, 'function')
34
- const instance = new Reporter()
35
- t.true(instance instanceof Reporter)
36
- })
37
-
38
- // ─────────────────────────────────────────────────────────────────────────────
39
- // Configuration tests
40
- // ─────────────────────────────────────────────────────────────────────────────
41
-
42
- test('configure() sets quiet mode', (t) => {
43
- reporter.configure({ quiet: true })
44
- // Spinner should be suppressed - verify by checking isSpinning after start
45
- reporter.start('Test')
46
- t.false(reporter.isSpinning())
47
- })
48
-
49
- test('configure() sets json mode', (t) => {
50
- reporter.configure({ json: true })
51
- reporter.start('Test')
52
- t.false(reporter.isSpinning())
53
- })
54
-
55
- test('configure() returns this for chaining', (t) => {
56
- const result = reporter.configure({ quiet: false })
57
- t.is(result, reporter)
58
- })
59
-
60
- test('configure() resets previous configuration', (t) => {
61
- reporter.configure({ quiet: true })
62
- reporter.configure({ quiet: false })
63
- // After reset, spinner should work (if TTY)
64
- // Can't test TTY directly, but verify configure doesn't throw
65
- t.notThrows(() => reporter.configure({}))
66
- })
67
-
68
- // ─────────────────────────────────────────────────────────────────────────────
69
- // Start/stop tests
70
- // ─────────────────────────────────────────────────────────────────────────────
71
-
72
- test('start() returns this for chaining', (t) => {
73
- reporter.configure({ quiet: true })
74
- const result = reporter.start('Test')
75
- t.is(result, reporter)
76
- })
77
-
78
- test('stop() returns this for chaining', (t) => {
79
- reporter.configure({ quiet: true })
80
- reporter.start('Test')
81
- const result = reporter.stop()
82
- t.is(result, reporter)
83
- })
84
-
85
- test('stop() clears spinner state', (t) => {
86
- reporter.configure({ quiet: true })
87
- reporter.start('Test')
88
- reporter.stop()
89
- t.false(reporter.isSpinning())
90
- })
91
-
92
- test('start() stops previous spinner before starting new one', (t) => {
93
- reporter.configure({ quiet: true })
94
- reporter.start('First')
95
- reporter.start('Second')
96
- // Should not throw, previous spinner should be stopped
97
- t.pass()
98
- })
99
-
100
- // ─────────────────────────────────────────────────────────────────────────────
101
- // Update tests
102
- // ─────────────────────────────────────────────────────────────────────────────
103
-
104
- test('update() returns this for chaining', (t) => {
105
- reporter.configure({ quiet: true })
106
- reporter.start('Initial')
107
- const result = reporter.update('Updated')
108
- t.is(result, reporter)
109
- })
110
-
111
- test('update() starts spinner if none running', (t) => {
112
- reporter.configure({ quiet: true })
113
- reporter.update('Auto-start')
114
- // Should not throw
115
- t.pass()
116
- })
117
-
118
- // ─────────────────────────────────────────────────────────────────────────────
119
- // Status completion tests
120
- // ─────────────────────────────────────────────────────────────────────────────
121
-
122
- test('succeed() returns this for chaining', (t) => {
123
- reporter.configure({ quiet: true })
124
- reporter.start('Test')
125
- const result = reporter.succeed('Done')
126
- t.is(result, reporter)
127
- })
128
-
129
- test('succeed() clears spinner', (t) => {
130
- reporter.configure({ quiet: true })
131
- reporter.start('Test')
132
- reporter.succeed('Done')
133
- t.false(reporter.isSpinning())
134
- })
135
-
136
- test('succeed() without text uses base text', (t) => {
137
- reporter.configure({ quiet: true })
138
- reporter.start('Base message')
139
- reporter.succeed()
140
- // Should not throw
141
- t.pass()
142
- })
143
-
144
- test('fail() returns this for chaining', (t) => {
145
- reporter.configure({ quiet: true })
146
- reporter.start('Test')
147
- const result = reporter.fail('Failed')
148
- t.is(result, reporter)
149
- })
150
-
151
- test('fail() clears spinner', (t) => {
152
- reporter.configure({ quiet: true })
153
- reporter.start('Test')
154
- reporter.fail('Failed')
155
- t.false(reporter.isSpinning())
156
- })
157
-
158
- test('warn() returns this for chaining', (t) => {
159
- reporter.configure({ quiet: true })
160
- reporter.start('Test')
161
- const result = reporter.warn('Warning')
162
- t.is(result, reporter)
163
- })
164
-
165
- test('warn() clears spinner', (t) => {
166
- reporter.configure({ quiet: true })
167
- reporter.start('Test')
168
- reporter.warn('Warning')
169
- t.false(reporter.isSpinning())
170
- })
171
-
172
- test('info() returns this for chaining', (t) => {
173
- reporter.configure({ quiet: true })
174
- const result = reporter.info('Info message')
175
- t.is(result, reporter)
176
- })
177
-
178
- // ─────────────────────────────────────────────────────────────────────────────
179
- // Elapsed time tests
180
- // ─────────────────────────────────────────────────────────────────────────────
181
-
182
- test('getElapsed() returns null before start', (t) => {
183
- reporter.reset()
184
- t.is(reporter.getElapsed(), null)
185
- })
186
-
187
- test('getElapsed() returns elapsed time after start', async (t) => {
188
- // Create a fresh Reporter instance for this test to avoid singleton issues
189
- const rep = new Reporter()
190
- rep.configure({ quiet: true })
191
- rep.start('Test')
192
-
193
- // Wait a small amount
194
- await new Promise((resolve) => setTimeout(resolve, 50))
195
-
196
- const elapsed = rep.getElapsed()
197
- t.true(typeof elapsed === 'number', `elapsed should be a number, got: ${elapsed} (${typeof elapsed})`)
198
- t.true(elapsed >= 40, `elapsed (${elapsed}ms) should be >= 40ms`)
199
-
200
- // Clean up
201
- rep.stop()
202
- })
203
-
204
- test('elapsed timer is cleared on succeed', async (t) => {
205
- reporter.configure({ quiet: true })
206
- reporter.start('Test', { showElapsed: true })
207
- await new Promise((resolve) => setTimeout(resolve, 10))
208
- reporter.succeed()
209
- // Timer should be stopped - no way to directly test, but ensure no errors
210
- t.pass()
211
- })
212
-
213
- test('elapsed timer is cleared on fail', async (t) => {
214
- reporter.configure({ quiet: true })
215
- reporter.start('Test', { showElapsed: true })
216
- await new Promise((resolve) => setTimeout(resolve, 10))
217
- reporter.fail()
218
- t.pass()
219
- })
220
-
221
- test('elapsed timer is cleared on stop', async (t) => {
222
- reporter.configure({ quiet: true })
223
- reporter.start('Test', { showElapsed: true })
224
- await new Promise((resolve) => setTimeout(resolve, 10))
225
- reporter.stop()
226
- t.pass()
227
- })
228
-
229
- // ─────────────────────────────────────────────────────────────────────────────
230
- // Reset tests
231
- // ─────────────────────────────────────────────────────────────────────────────
232
-
233
- test('reset() returns this for chaining', (t) => {
234
- const result = reporter.reset()
235
- t.is(result, reporter)
236
- })
237
-
238
- test('reset() clears all state', (t) => {
239
- reporter.configure({ quiet: true, json: true })
240
- reporter.start('Test')
241
- reporter.reset()
242
-
243
- t.false(reporter.isSpinning())
244
- t.is(reporter.getElapsed(), null)
245
- })
246
-
247
- // ─────────────────────────────────────────────────────────────────────────────
248
- // isSpinning tests
249
- // ─────────────────────────────────────────────────────────────────────────────
250
-
251
- test('isSpinning() returns false initially', (t) => {
252
- reporter.reset()
253
- t.false(reporter.isSpinning())
254
- })
255
-
256
- test('isSpinning() returns false in quiet mode', (t) => {
257
- reporter.configure({ quiet: true })
258
- reporter.start('Test')
259
- t.false(reporter.isSpinning())
260
- })
261
-
262
- // ─────────────────────────────────────────────────────────────────────────────
263
- // Integration tests with mocked ora
264
- // ─────────────────────────────────────────────────────────────────────────────
265
-
266
- test.serial('start() calls ora with correct text', async (t) => {
267
- const { sandbox } = t.context
268
- const mockOraInstance = {
269
- start: sandbox.stub().returnsThis(),
270
- stop: sandbox.stub().returnsThis(),
271
- succeed: sandbox.stub().returnsThis(),
272
- fail: sandbox.stub().returnsThis(),
273
- warn: sandbox.stub().returnsThis(),
274
- info: sandbox.stub().returnsThis(),
275
- isSpinning: true,
276
- text: '',
277
- }
278
- const mockOra = sandbox.stub().returns(mockOraInstance)
279
-
280
- const { Reporter: MockedReporter } = await esmock('./index.js', {
281
- ora: { default: mockOra },
282
- })
283
-
284
- // Mock isTTY to enable spinner
285
- const originalIsTTY = process.stdout.isTTY
286
- process.stdout.isTTY = true
287
-
288
- try {
289
- const rep = new MockedReporter()
290
- rep.start('Building...')
291
-
292
- t.true(mockOra.calledOnce)
293
- t.deepEqual(mockOra.firstCall.args[0], {
294
- text: 'Building...',
295
- spinner: 'dots',
296
- })
297
- } finally {
298
- process.stdout.isTTY = originalIsTTY
299
- }
300
- })
301
-
302
- test.serial('succeed() calls ora.succeed with text', async (t) => {
303
- const { sandbox } = t.context
304
- const mockOraInstance = {
305
- start: sandbox.stub().returnsThis(),
306
- stop: sandbox.stub().returnsThis(),
307
- succeed: sandbox.stub().returnsThis(),
308
- fail: sandbox.stub().returnsThis(),
309
- warn: sandbox.stub().returnsThis(),
310
- info: sandbox.stub().returnsThis(),
311
- isSpinning: true,
312
- text: '',
313
- }
314
- const mockOra = sandbox.stub().returns(mockOraInstance)
315
-
316
- const { Reporter: MockedReporter } = await esmock('./index.js', {
317
- ora: { default: mockOra },
318
- })
319
-
320
- const originalIsTTY = process.stdout.isTTY
321
- process.stdout.isTTY = true
322
-
323
- try {
324
- const rep = new MockedReporter()
325
- rep.start('Building...')
326
- rep.succeed('Build complete')
327
-
328
- t.true(mockOraInstance.succeed.calledOnce)
329
- t.is(mockOraInstance.succeed.firstCall.args[0], 'Build complete')
330
- } finally {
331
- process.stdout.isTTY = originalIsTTY
332
- }
333
- })
334
-
335
- test.serial('fail() calls ora.fail with text', async (t) => {
336
- const { sandbox } = t.context
337
- const mockOraInstance = {
338
- start: sandbox.stub().returnsThis(),
339
- stop: sandbox.stub().returnsThis(),
340
- succeed: sandbox.stub().returnsThis(),
341
- fail: sandbox.stub().returnsThis(),
342
- warn: sandbox.stub().returnsThis(),
343
- info: sandbox.stub().returnsThis(),
344
- isSpinning: true,
345
- text: '',
346
- }
347
- const mockOra = sandbox.stub().returns(mockOraInstance)
348
-
349
- const { Reporter: MockedReporter } = await esmock('./index.js', {
350
- ora: { default: mockOra },
351
- })
352
-
353
- const originalIsTTY = process.stdout.isTTY
354
- process.stdout.isTTY = true
355
-
356
- try {
357
- const rep = new MockedReporter()
358
- rep.start('Generating PDF...')
359
- rep.fail('PDF generation failed')
360
-
361
- t.true(mockOraInstance.fail.calledOnce)
362
- t.is(mockOraInstance.fail.firstCall.args[0], 'PDF generation failed')
363
- } finally {
364
- process.stdout.isTTY = originalIsTTY
365
- }
366
- })
367
-
368
- test.serial('warn() calls ora.warn with text', async (t) => {
369
- const { sandbox } = t.context
370
- const mockOraInstance = {
371
- start: sandbox.stub().returnsThis(),
372
- stop: sandbox.stub().returnsThis(),
373
- succeed: sandbox.stub().returnsThis(),
374
- fail: sandbox.stub().returnsThis(),
375
- warn: sandbox.stub().returnsThis(),
376
- info: sandbox.stub().returnsThis(),
377
- isSpinning: true,
378
- text: '',
379
- }
380
- const mockOra = sandbox.stub().returns(mockOraInstance)
381
-
382
- const { Reporter: MockedReporter } = await esmock('./index.js', {
383
- ora: { default: mockOra },
384
- })
385
-
386
- const originalIsTTY = process.stdout.isTTY
387
- process.stdout.isTTY = true
388
-
389
- try {
390
- const rep = new MockedReporter()
391
- rep.start('Checking...')
392
- rep.warn('Dependencies outdated')
393
-
394
- t.true(mockOraInstance.warn.calledOnce)
395
- t.is(mockOraInstance.warn.firstCall.args[0], 'Dependencies outdated')
396
- } finally {
397
- process.stdout.isTTY = originalIsTTY
398
- }
399
- })
400
-
401
- test.serial('update() changes spinner text', async (t) => {
402
- const { sandbox } = t.context
403
- const mockOraInstance = {
404
- start: sandbox.stub().returnsThis(),
405
- stop: sandbox.stub().returnsThis(),
406
- succeed: sandbox.stub().returnsThis(),
407
- fail: sandbox.stub().returnsThis(),
408
- warn: sandbox.stub().returnsThis(),
409
- info: sandbox.stub().returnsThis(),
410
- isSpinning: true,
411
- text: '',
412
- }
413
- const mockOra = sandbox.stub().returns(mockOraInstance)
414
-
415
- const { Reporter: MockedReporter } = await esmock('./index.js', {
416
- ora: { default: mockOra },
417
- })
418
-
419
- const originalIsTTY = process.stdout.isTTY
420
- process.stdout.isTTY = true
421
-
422
- try {
423
- const rep = new MockedReporter()
424
- rep.start('Step 1...')
425
- rep.update('Step 2...')
426
-
427
- t.is(mockOraInstance.text, 'Step 2...')
428
- } finally {
429
- process.stdout.isTTY = originalIsTTY
430
- }
431
- })
432
-
433
- test.serial('spinner is not created when quiet=true', async (t) => {
434
- const { sandbox } = t.context
435
- const mockOra = sandbox.stub()
436
-
437
- const { Reporter: MockedReporter } = await esmock('./index.js', {
438
- ora: { default: mockOra },
439
- })
440
-
441
- const rep = new MockedReporter()
442
- rep.configure({ quiet: true })
443
- rep.start('Should not create spinner')
444
-
445
- t.false(mockOra.called)
446
- })
447
-
448
- test.serial('spinner is not created when json=true', async (t) => {
449
- const { sandbox } = t.context
450
- const mockOra = sandbox.stub()
451
-
452
- const { Reporter: MockedReporter } = await esmock('./index.js', {
453
- ora: { default: mockOra },
454
- })
455
-
456
- const rep = new MockedReporter()
457
- rep.configure({ json: true })
458
- rep.start('Should not create spinner')
459
-
460
- t.false(mockOra.called)
461
- })
462
-
463
- test.serial('spinner is not created when not TTY', async (t) => {
464
- const { sandbox } = t.context
465
- const mockOra = sandbox.stub()
466
-
467
- const { Reporter: MockedReporter } = await esmock('./index.js', {
468
- ora: { default: mockOra },
469
- })
470
-
471
- const originalIsTTY = process.stdout.isTTY
472
- process.stdout.isTTY = false
473
-
474
- try {
475
- const rep = new MockedReporter()
476
- rep.start('Should not create spinner')
477
-
478
- t.false(mockOra.called)
479
- } finally {
480
- process.stdout.isTTY = originalIsTTY
481
- }
482
- })
483
-
484
- // ─────────────────────────────────────────────────────────────────────────────
485
- // Elapsed time display tests
486
- // ─────────────────────────────────────────────────────────────────────────────
487
-
488
- test.serial('showElapsed option updates spinner text with time', async (t) => {
489
- const { sandbox } = t.context
490
- const mockOraInstance = {
491
- start: sandbox.stub().returnsThis(),
492
- stop: sandbox.stub().returnsThis(),
493
- succeed: sandbox.stub().returnsThis(),
494
- fail: sandbox.stub().returnsThis(),
495
- warn: sandbox.stub().returnsThis(),
496
- info: sandbox.stub().returnsThis(),
497
- isSpinning: true,
498
- text: '',
499
- }
500
- const mockOra = sandbox.stub().returns(mockOraInstance)
501
-
502
- const { Reporter: MockedReporter } = await esmock('./index.js', {
503
- ora: { default: mockOra },
504
- })
505
-
506
- const originalIsTTY = process.stdout.isTTY
507
- process.stdout.isTTY = true
508
-
509
- let rep
510
- try {
511
- rep = new MockedReporter()
512
- rep.start('Building...', { showElapsed: true })
513
-
514
- // Wait for timer to tick (use 1200ms to account for CI timing variations)
515
- await new Promise((resolve) => setTimeout(resolve, 1200))
516
-
517
- // Nota bene: text should include elapsed time in format "(Ns)" where N is a number.
518
- // We use a regex pattern to avoid flaky tests on slower CI systems.
519
- t.regex(mockOraInstance.text, /\(\d+s\)/, 'spinner text should include elapsed time')
520
- t.true(mockOraInstance.text.startsWith('Building...'), 'spinner text should include base text')
521
- } finally {
522
- // Ensure timer is stopped
523
- if (rep) rep.stop()
524
- process.stdout.isTTY = originalIsTTY
525
- }
526
- })
527
-
528
- // ─────────────────────────────────────────────────────────────────────────────
529
- // Method call safety tests
530
- // ─────────────────────────────────────────────────────────────────────────────
531
-
532
- test('calling succeed without start does not throw', (t) => {
533
- reporter.reset()
534
- t.notThrows(() => reporter.succeed('Done'))
535
- })
536
-
537
- test('calling fail without start does not throw', (t) => {
538
- reporter.reset()
539
- t.notThrows(() => reporter.fail('Failed'))
540
- })
541
-
542
- test('calling warn without start does not throw', (t) => {
543
- reporter.reset()
544
- t.notThrows(() => reporter.warn('Warning'))
545
- })
546
-
547
- test('calling stop without start does not throw', (t) => {
548
- reporter.reset()
549
- t.notThrows(() => reporter.stop())
550
- })
551
-
552
- test('calling update without start does not throw', (t) => {
553
- reporter.reset()
554
- t.notThrows(() => reporter.update('Update'))
555
- })
556
-
557
- // ─────────────────────────────────────────────────────────────────────────────
558
- // Workflow tests
559
- // ─────────────────────────────────────────────────────────────────────────────
560
-
561
- test('typical success workflow', (t) => {
562
- reporter.configure({ quiet: true })
563
- reporter.start('Step 1...')
564
- reporter.update('Step 2...')
565
- reporter.update('Step 3...')
566
- reporter.succeed('All steps complete')
567
- t.pass()
568
- })
569
-
570
- test('typical failure workflow', (t) => {
571
- reporter.configure({ quiet: true })
572
- reporter.start('Processing...')
573
- reporter.fail('Processing failed')
574
- t.pass()
575
- })
576
-
577
- test('multi-phase workflow with different outcomes', (t) => {
578
- reporter.configure({ quiet: true })
579
-
580
- // Phase 1: success
581
- reporter.start('Phase 1...')
582
- reporter.succeed('Phase 1 complete')
583
-
584
- // Phase 2: warning
585
- reporter.start('Phase 2...')
586
- reporter.warn('Phase 2 completed with warnings')
587
-
588
- // Phase 3: failure
589
- reporter.start('Phase 3...')
590
- reporter.fail('Phase 3 failed')
591
-
592
- t.pass()
593
- })
594
-
595
- // ─────────────────────────────────────────────────────────────────────────────
596
- // Reduced motion tests
597
- // ─────────────────────────────────────────────────────────────────────────────
598
-
599
- test.serial('reduced motion: start() does not create ora spinner', async (t) => {
600
- const { sandbox } = t.context
601
- const mockOra = sandbox.stub()
602
-
603
- const { Reporter: MockedReporter } = await esmock('./index.js', {
604
- ora: { default: mockOra },
605
- })
606
-
607
- const originalIsTTY = process.stdout.isTTY
608
- const originalReduceMotion = process.env.REDUCED_MOTION
609
- const consoleLog = sandbox.stub(console, 'log')
610
-
611
- try {
612
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
613
- process.env.REDUCED_MOTION = '1'
614
-
615
- const rep = new MockedReporter()
616
- rep.start('Building...')
617
-
618
- t.false(mockOra.called, 'ora should not be called in reduced motion mode')
619
- t.true(consoleLog.calledOnce, 'console.log should be called once')
620
- t.true(consoleLog.firstCall.args[0].includes('Building...'),
621
- 'output should include the text')
622
- } finally {
623
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
624
- if (originalReduceMotion === undefined) {
625
- delete process.env.REDUCED_MOTION
626
- } else {
627
- process.env.REDUCED_MOTION = originalReduceMotion
628
- }
629
- }
630
- })
631
-
632
- test.serial('reduced motion: update() prints new line instead of overwriting', async (t) => {
633
- const { sandbox } = t.context
634
- const mockOra = sandbox.stub()
635
-
636
- const { Reporter: MockedReporter } = await esmock('./index.js', {
637
- ora: { default: mockOra },
638
- })
639
-
640
- const originalIsTTY = process.stdout.isTTY
641
- const originalReduceMotion = process.env.REDUCED_MOTION
642
- const consoleLog = sandbox.stub(console, 'log')
643
-
644
- try {
645
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
646
- process.env.REDUCED_MOTION = '1'
647
-
648
- const rep = new MockedReporter()
649
- rep.start('Step 1...')
650
- rep.update('Step 2...')
651
-
652
- t.is(consoleLog.callCount, 2, 'should print two separate lines')
653
- t.true(consoleLog.firstCall.args[0].includes('Step 1...'))
654
- t.true(consoleLog.secondCall.args[0].includes('Step 2...'))
655
- } finally {
656
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
657
- if (originalReduceMotion === undefined) {
658
- delete process.env.REDUCED_MOTION
659
- } else {
660
- process.env.REDUCED_MOTION = originalReduceMotion
661
- }
662
- }
663
- })
664
-
665
- test.serial('reduced motion: succeed() prints static success line', async (t) => {
666
- const { sandbox } = t.context
667
- const mockOra = sandbox.stub()
668
-
669
- const { Reporter: MockedReporter } = await esmock('./index.js', {
670
- ora: { default: mockOra },
671
- })
672
-
673
- const originalIsTTY = process.stdout.isTTY
674
- const originalReduceMotion = process.env.REDUCED_MOTION
675
- const consoleLog = sandbox.stub(console, 'log')
676
-
677
- try {
678
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
679
- process.env.REDUCED_MOTION = '1'
680
-
681
- const rep = new MockedReporter()
682
- rep.start('Building...')
683
- rep.succeed('Build complete')
684
-
685
- t.is(consoleLog.callCount, 2)
686
- // Second call is succeed
687
- t.true(consoleLog.secondCall.args[0].includes('✔'))
688
- t.true(consoleLog.secondCall.args[0].includes('Build complete'))
689
- } finally {
690
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
691
- if (originalReduceMotion === undefined) {
692
- delete process.env.REDUCED_MOTION
693
- } else {
694
- process.env.REDUCED_MOTION = originalReduceMotion
695
- }
696
- }
697
- })
698
-
699
- test.serial('reduced motion: fail() prints static failure line', async (t) => {
700
- const { sandbox } = t.context
701
- const mockOra = sandbox.stub()
702
-
703
- const { Reporter: MockedReporter } = await esmock('./index.js', {
704
- ora: { default: mockOra },
705
- })
706
-
707
- const originalIsTTY = process.stdout.isTTY
708
- const originalReduceMotion = process.env.REDUCED_MOTION
709
- const consoleLog = sandbox.stub(console, 'log')
710
-
711
- try {
712
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
713
- process.env.REDUCED_MOTION = '1'
714
-
715
- const rep = new MockedReporter()
716
- rep.start('Generating PDF...')
717
- rep.fail('PDF generation failed')
718
-
719
- t.is(consoleLog.callCount, 2)
720
- t.true(consoleLog.secondCall.args[0].includes('✖'))
721
- t.true(consoleLog.secondCall.args[0].includes('PDF generation failed'))
722
- } finally {
723
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
724
- if (originalReduceMotion === undefined) {
725
- delete process.env.REDUCED_MOTION
726
- } else {
727
- process.env.REDUCED_MOTION = originalReduceMotion
728
- }
729
- }
730
- })
731
-
732
- test.serial('reduced motion: warn() prints static warning line', async (t) => {
733
- const { sandbox } = t.context
734
- const mockOra = sandbox.stub()
735
-
736
- const { Reporter: MockedReporter } = await esmock('./index.js', {
737
- ora: { default: mockOra },
738
- })
739
-
740
- const originalIsTTY = process.stdout.isTTY
741
- const originalReduceMotion = process.env.REDUCED_MOTION
742
- const consoleLog = sandbox.stub(console, 'log')
743
-
744
- try {
745
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
746
- process.env.REDUCED_MOTION = '1'
747
-
748
- const rep = new MockedReporter()
749
- rep.start('Checking...')
750
- rep.warn('Dependencies outdated')
751
-
752
- t.is(consoleLog.callCount, 2)
753
- t.true(consoleLog.secondCall.args[0].includes('⚠'))
754
- t.true(consoleLog.secondCall.args[0].includes('Dependencies outdated'))
755
- } finally {
756
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
757
- if (originalReduceMotion === undefined) {
758
- delete process.env.REDUCED_MOTION
759
- } else {
760
- process.env.REDUCED_MOTION = originalReduceMotion
761
- }
762
- }
763
- })
764
-
765
- test.serial('reduced motion: info() prints static info line', async (t) => {
766
- const { sandbox } = t.context
767
- const mockOra = sandbox.stub()
768
-
769
- const { Reporter: MockedReporter } = await esmock('./index.js', {
770
- ora: { default: mockOra },
771
- })
772
-
773
- const originalIsTTY = process.stdout.isTTY
774
- const originalReduceMotion = process.env.REDUCED_MOTION
775
- const consoleLog = sandbox.stub(console, 'log')
776
-
777
- try {
778
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
779
- process.env.REDUCED_MOTION = '1'
780
-
781
- const rep = new MockedReporter()
782
- rep.info('Using cached dependencies')
783
-
784
- t.true(consoleLog.calledOnce)
785
- t.true(consoleLog.firstCall.args[0].includes('ℹ'))
786
- t.true(consoleLog.firstCall.args[0].includes('Using cached dependencies'))
787
- } finally {
788
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
789
- if (originalReduceMotion === undefined) {
790
- delete process.env.REDUCED_MOTION
791
- } else {
792
- process.env.REDUCED_MOTION = originalReduceMotion
793
- }
794
- }
795
- })
796
-
797
- test.serial('reduced motion: multi-phase workflow outputs each phase on new line', async (t) => {
798
- const { sandbox } = t.context
799
- const mockOra = sandbox.stub()
800
-
801
- const { Reporter: MockedReporter } = await esmock('./index.js', {
802
- ora: { default: mockOra },
803
- })
804
-
805
- const originalIsTTY = process.stdout.isTTY
806
- const originalReduceMotion = process.env.REDUCED_MOTION
807
- const consoleLog = sandbox.stub(console, 'log')
808
-
809
- try {
810
- Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
811
- process.env.REDUCED_MOTION = '1'
812
-
813
- const rep = new MockedReporter()
814
- rep.start('Cloning starter...')
815
- rep.update('Installing dependencies...')
816
- rep.succeed('Project created')
817
-
818
- t.is(consoleLog.callCount, 3, 'each stage should print on a new line')
819
- t.true(consoleLog.getCall(0).args[0].includes('Cloning starter...'))
820
- t.true(consoleLog.getCall(1).args[0].includes('Installing dependencies...'))
821
- t.true(consoleLog.getCall(2).args[0].includes('✔'))
822
- t.true(consoleLog.getCall(2).args[0].includes('Project created'))
823
- t.false(mockOra.called, 'ora should never be called')
824
- } finally {
825
- Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
826
- if (originalReduceMotion === undefined) {
827
- delete process.env.REDUCED_MOTION
828
- } else {
829
- process.env.REDUCED_MOTION = originalReduceMotion
830
- }
831
- }
832
- })
833
-
834
- test.serial('reduced motion: quiet mode takes precedence over reduced motion', async (t) => {
835
- const { sandbox } = t.context
836
- const mockOra = sandbox.stub()
837
-
838
- const { Reporter: MockedReporter } = await esmock('./index.js', {
839
- ora: { default: mockOra },
840
- })
841
-
842
- const originalReduceMotion = process.env.REDUCED_MOTION
843
- const consoleLog = sandbox.stub(console, 'log')
844
-
845
- try {
846
- process.env.REDUCED_MOTION = '1'
847
-
848
- const rep = new MockedReporter()
849
- rep.configure({ quiet: true })
850
- rep.start('Building...')
851
- rep.succeed('Build complete')
852
-
853
- t.false(consoleLog.called, 'quiet mode should suppress all output')
854
- t.false(mockOra.called, 'ora should not be called')
855
- } finally {
856
- if (originalReduceMotion === undefined) {
857
- delete process.env.REDUCED_MOTION
858
- } else {
859
- process.env.REDUCED_MOTION = originalReduceMotion
860
- }
861
- }
862
- })