@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,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
- })