@thegetty/quire-cli 1.0.0-rc.45 → 1.0.0-rc.47

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,108 +0,0 @@
1
- /**
2
- * Duration formatting utilities for doctor module
3
- *
4
- * Provides human-readable time duration formatting for diagnostic checks,
5
- * particularly useful for displaying how stale a build output is compared
6
- * to source files.
7
- *
8
- * The stale build check uses this to display appropriate durations like
9
- * "Build output is 2 weeks older than source files" or
10
- * "Build output is 3 months older than source files" for long-abandoned builds.
11
- *
12
- * @module lib/doctor/formatDuration
13
- */
14
-
15
- /**
16
- * Time unit definitions in milliseconds
17
- * @constant {Object}
18
- */
19
- const TIME_UNITS = {
20
- second: 1000,
21
- minute: 60 * 1000,
22
- hour: 60 * 60 * 1000,
23
- day: 24 * 60 * 60 * 1000,
24
- week: 7 * 24 * 60 * 60 * 1000,
25
- month: 30 * 24 * 60 * 60 * 1000,
26
- year: 365 * 24 * 60 * 60 * 1000,
27
- }
28
-
29
- /**
30
- * Format a duration in milliseconds as a human-readable string.
31
- *
32
- * Converts a time duration to the most appropriate unit (seconds through years),
33
- * using singular or plural form as needed. The function selects the largest
34
- * applicable unit to keep output concise.
35
- *
36
- * ## Duration Thresholds
37
- *
38
- * | Duration | Example Output |
39
- * |----------------|-----------------|
40
- * | < 1 minute | "45 seconds" |
41
- * | < 1 hour | "30 minutes" |
42
- * | < 1 day | "5 hours" |
43
- * | < 1 week | "3 days" |
44
- * | < 1 month | "2 weeks" |
45
- * | < 1 year | "3 months" |
46
- * | >= 1 year | "2 years" |
47
- *
48
- * @param {number} ms - Duration in milliseconds. Must be a non-negative number.
49
- *
50
- * @returns {string} Human-readable duration string with unit.
51
- *
52
- * @example
53
- * // Returns "45 seconds"
54
- * formatDuration(45000)
55
- *
56
- * @example
57
- * // Returns "1 hour"
58
- * formatDuration(3600000)
59
- *
60
- * @example
61
- * // Returns "2 weeks"
62
- * formatDuration(14 * 24 * 60 * 60 * 1000)
63
- *
64
- * @example
65
- * // Returns "3 months"
66
- * formatDuration(90 * 24 * 60 * 60 * 1000)
67
- *
68
- * @example
69
- * // Returns "1 year"
70
- * formatDuration(365 * 24 * 60 * 60 * 1000)
71
- *
72
- * @remarks
73
- * - Uses approximate values for months (30 days) and years (365 days)
74
- * - Always returns the largest applicable unit (e.g., "2 weeks" not "14 days")
75
- * - Handles singular/plural forms automatically
76
- * - Returns "0 seconds" for zero or negative input
77
- */
78
- export function formatDuration(ms) {
79
- const seconds = Math.floor(ms / TIME_UNITS.second)
80
- const minutes = Math.floor(ms / TIME_UNITS.minute)
81
- const hours = Math.floor(ms / TIME_UNITS.hour)
82
- const days = Math.floor(ms / TIME_UNITS.day)
83
- const weeks = Math.floor(ms / TIME_UNITS.week)
84
- const months = Math.floor(ms / TIME_UNITS.month)
85
- const years = Math.floor(ms / TIME_UNITS.year)
86
-
87
- if (years > 0) {
88
- return `${years} year${years === 1 ? '' : 's'}`
89
- }
90
- if (months > 0) {
91
- return `${months} month${months === 1 ? '' : 's'}`
92
- }
93
- if (weeks > 0) {
94
- return `${weeks} week${weeks === 1 ? '' : 's'}`
95
- }
96
- if (days > 0) {
97
- return `${days} day${days === 1 ? '' : 's'}`
98
- }
99
- if (hours > 0) {
100
- return `${hours} hour${hours === 1 ? '' : 's'}`
101
- }
102
- if (minutes > 0) {
103
- return `${minutes} minute${minutes === 1 ? '' : 's'}`
104
- }
105
- return `${seconds} second${seconds === 1 ? '' : 's'}`
106
- }
107
-
108
- export default formatDuration
@@ -1,76 +0,0 @@
1
- import test from 'ava'
2
- import { formatDuration } from './formatDuration.js'
3
-
4
- test('formatDuration returns seconds for durations under 1 minute', (t) => {
5
- t.is(formatDuration(0), '0 seconds')
6
- t.is(formatDuration(1000), '1 second')
7
- t.is(formatDuration(45000), '45 seconds')
8
- t.is(formatDuration(59000), '59 seconds')
9
- })
10
-
11
- test('formatDuration returns minutes for durations under 1 hour', (t) => {
12
- t.is(formatDuration(60000), '1 minute')
13
- t.is(formatDuration(120000), '2 minutes')
14
- t.is(formatDuration(30 * 60 * 1000), '30 minutes')
15
- t.is(formatDuration(59 * 60 * 1000), '59 minutes')
16
- })
17
-
18
- test('formatDuration returns hours for durations under 1 day', (t) => {
19
- t.is(formatDuration(60 * 60 * 1000), '1 hour')
20
- t.is(formatDuration(2 * 60 * 60 * 1000), '2 hours')
21
- t.is(formatDuration(12 * 60 * 60 * 1000), '12 hours')
22
- t.is(formatDuration(23 * 60 * 60 * 1000), '23 hours')
23
- })
24
-
25
- test('formatDuration returns days for durations under 1 week', (t) => {
26
- t.is(formatDuration(24 * 60 * 60 * 1000), '1 day')
27
- t.is(formatDuration(2 * 24 * 60 * 60 * 1000), '2 days')
28
- t.is(formatDuration(6 * 24 * 60 * 60 * 1000), '6 days')
29
- })
30
-
31
- test('formatDuration returns weeks for durations under 1 month', (t) => {
32
- t.is(formatDuration(7 * 24 * 60 * 60 * 1000), '1 week')
33
- t.is(formatDuration(14 * 24 * 60 * 60 * 1000), '2 weeks')
34
- t.is(formatDuration(21 * 24 * 60 * 60 * 1000), '3 weeks')
35
- t.is(formatDuration(28 * 24 * 60 * 60 * 1000), '4 weeks')
36
- })
37
-
38
- test('formatDuration returns months for durations under 1 year', (t) => {
39
- t.is(formatDuration(30 * 24 * 60 * 60 * 1000), '1 month')
40
- t.is(formatDuration(60 * 24 * 60 * 60 * 1000), '2 months')
41
- t.is(formatDuration(90 * 24 * 60 * 60 * 1000), '3 months')
42
- t.is(formatDuration(180 * 24 * 60 * 60 * 1000), '6 months')
43
- t.is(formatDuration(364 * 24 * 60 * 60 * 1000), '12 months')
44
- })
45
-
46
- test('formatDuration returns years for durations of 1 year or more', (t) => {
47
- t.is(formatDuration(365 * 24 * 60 * 60 * 1000), '1 year')
48
- t.is(formatDuration(2 * 365 * 24 * 60 * 60 * 1000), '2 years')
49
- t.is(formatDuration(5 * 365 * 24 * 60 * 60 * 1000), '5 years')
50
- t.is(formatDuration(10 * 365 * 24 * 60 * 60 * 1000), '10 years')
51
- })
52
-
53
- test('formatDuration handles singular forms correctly', (t) => {
54
- t.is(formatDuration(1000), '1 second')
55
- t.is(formatDuration(60 * 1000), '1 minute')
56
- t.is(formatDuration(60 * 60 * 1000), '1 hour')
57
- t.is(formatDuration(24 * 60 * 60 * 1000), '1 day')
58
- t.is(formatDuration(7 * 24 * 60 * 60 * 1000), '1 week')
59
- t.is(formatDuration(30 * 24 * 60 * 60 * 1000), '1 month')
60
- t.is(formatDuration(365 * 24 * 60 * 60 * 1000), '1 year')
61
- })
62
-
63
- test('formatDuration handles plural forms correctly', (t) => {
64
- t.is(formatDuration(2000), '2 seconds')
65
- t.is(formatDuration(2 * 60 * 1000), '2 minutes')
66
- t.is(formatDuration(2 * 60 * 60 * 1000), '2 hours')
67
- t.is(formatDuration(2 * 24 * 60 * 60 * 1000), '2 days')
68
- t.is(formatDuration(2 * 7 * 24 * 60 * 60 * 1000), '2 weeks')
69
- t.is(formatDuration(2 * 30 * 24 * 60 * 60 * 1000), '2 months')
70
- t.is(formatDuration(2 * 365 * 24 * 60 * 60 * 1000), '2 years')
71
- })
72
-
73
- test('default export is the same as named export', async (t) => {
74
- const module = await import('./formatDuration.js')
75
- t.is(module.default, module.formatDuration)
76
- })
@@ -1,257 +0,0 @@
1
- /**
2
- * Human-readable formatter for doctor check results
3
- *
4
- * Formats check results for terminal display with colors and icons.
5
- *
6
- * @module lib/doctor/formatters/human
7
- */
8
- import chalk from 'chalk'
9
- import wrapAnsi from 'wrap-ansi'
10
- import {
11
- STATUS_ICONS,
12
- countResults,
13
- filterResults,
14
- getStatus,
15
- getTerminalWidth
16
- } from './shared.js'
17
-
18
- /**
19
- * Log level for output routing
20
- * @typedef {'info'|'warn'|'error'} LogLevel
21
- */
22
-
23
- /**
24
- * Formatted line with routing information
25
- * @typedef {Object} FormattedLine
26
- * @property {string} text - The formatted text to display
27
- * @property {LogLevel} level - The log level for routing (info, warn, error)
28
- */
29
-
30
- /**
31
- * Wrap text to fit within the terminal, preserving indentation
32
- *
33
- * Each line in the input is indented by `indent` spaces. The text is
34
- * wrapped so that the total line width (indent + content) does not
35
- * exceed the terminal width. Continuation lines receive the same indent.
36
- *
37
- * @param {string} text - Text to wrap (may contain newlines)
38
- * @param {number} indent - Number of leading spaces for each line
39
- * @param {number} columns - Terminal width in columns
40
- * @returns {string} Wrapped and indented text
41
- */
42
- function wrapIndented(text, indent, columns) {
43
- const prefix = ' '.repeat(indent)
44
- const contentWidth = Math.max(columns - indent, 20)
45
-
46
- return text
47
- .split('\n')
48
- .map((line) => {
49
- const wrapped = wrapAnsi(line, contentWidth, { hard: true, trim: false })
50
- return wrapped
51
- .split('\n')
52
- .map((wl) => `${prefix}${wl}`)
53
- .join('\n')
54
- })
55
- .join('\n')
56
- }
57
-
58
- /**
59
- * Format a single check result for human display
60
- *
61
- * @param {Object} result - Check result
62
- * @param {Object} [options] - Formatting options
63
- * @param {boolean} [options.verbose] - Include additional details
64
- * @param {number} [options.columns] - Terminal width in columns
65
- * @returns {FormattedLine}
66
- */
67
- function formatCheck(result, options = {}) {
68
- const { name, ok, level, message, details, remediation, docsUrl } = result
69
- const status = getStatus(result)
70
- const statusIcon = STATUS_ICONS[status]
71
- const columns = options.columns || getTerminalWidth()
72
-
73
- const statusLine = message ? ` ${statusIcon} ${name}: ${message}` : ` ${statusIcon} ${name}`
74
- const lines = [statusLine]
75
-
76
- // Show details when verbose is enabled
77
- if (options.verbose && details) {
78
- lines.push(wrapIndented(details, 6, columns))
79
- }
80
-
81
- // Show remediation guidance for failed/timeout checks
82
- if (!ok && remediation) {
83
- lines.push('')
84
- lines.push(` How to fix:`)
85
- lines.push(wrapIndented(remediation, 4, columns))
86
- }
87
-
88
- if (!ok && docsUrl) {
89
- lines.push('')
90
- lines.push(` Documentation: ${docsUrl}`)
91
- lines.push('')
92
- }
93
-
94
- // Determine log level for routing
95
- let logLevel = 'info'
96
- if (!ok && level !== 'na') {
97
- if (level === 'warn' || level === 'timeout') {
98
- logLevel = 'warn'
99
- } else {
100
- logLevel = 'error'
101
- }
102
- }
103
-
104
- return {
105
- text: lines.join('\n'),
106
- level: logLevel,
107
- }
108
- }
109
-
110
- /**
111
- * Build summary text and exit code from unfiltered section counts
112
- *
113
- * @param {Array} sections - Unfiltered check results organized by section
114
- * @returns {{summary: FormattedLine, exitCode: number}}
115
- */
116
- function buildSummary(sections) {
117
- const counts = countResults(sections)
118
- const naSuffix = counts.na > 0
119
- ? ` (${counts.na} not applicable)`
120
- : ''
121
-
122
- let summary
123
- let exitCode = 0
124
-
125
- if (counts.failed > 0) {
126
- const errorText = counts.failed === 1 ? '1 check failed' : `${counts.failed} checks failed`
127
- const extras = []
128
- if (counts.warnings > 0) extras.push(counts.warnings === 1 ? '1 warning' : `${counts.warnings} warnings`)
129
- if (counts.timeouts > 0) extras.push(counts.timeouts === 1 ? '1 timed out' : `${counts.timeouts} timed out`)
130
- const extrasText = extras.length > 0 ? `, ${extras.join(', ')}` : ''
131
- summary = { text: `${errorText}${extrasText}. See above for details.${naSuffix}`, level: 'error' }
132
- exitCode = 1
133
- } else if (counts.warnings > 0 || counts.timeouts > 0) {
134
- const parts = []
135
- if (counts.warnings > 0) parts.push(counts.warnings === 1 ? '1 warning' : `${counts.warnings} warnings`)
136
- if (counts.timeouts > 0) parts.push(counts.timeouts === 1 ? '1 timed out' : `${counts.timeouts} timed out`)
137
- summary = { text: `All checks passed with ${parts.join(', ')}.${naSuffix}`, level: 'warn' }
138
- } else {
139
- summary = { text: `All checks passed!${naSuffix}`, level: 'info' }
140
- }
141
-
142
- return { summary, exitCode }
143
- }
144
-
145
- /**
146
- * Format check results for human-readable display
147
- *
148
- * @param {Array} sections - Check results organized by section
149
- * @param {Object} [options] - Formatting options
150
- * @param {boolean} [options.errors] - Only include failed checks
151
- * @param {boolean} [options.warnings] - Only include warning checks
152
- * @param {boolean} [options.verbose] - Include additional details
153
- * @param {number} [options.columns] - Terminal width override (default: auto-detected)
154
- * @param {string} [options.label='diagnostic checks'] - Description of checks being run
155
- * @returns {{lines: FormattedLine[], summary: FormattedLine|null, key: FormattedLine|null, exitCode: number, isEmpty: boolean}}
156
- *
157
- * @example
158
- * const { lines, summary, key, exitCode } = formatHuman(sections, { verbose: true })
159
- * for (const { text, level } of lines) {
160
- * logger[level](text)
161
- * }
162
- * if (summary) logger[summary.level](summary.text)
163
- */
164
- export function formatHuman(sections, options = {}) {
165
- const { label = 'diagnostic checks', verbose = false, columns } = options
166
- const terminalColumns = columns || getTerminalWidth()
167
- const filteredSections = filterResults(sections, options)
168
-
169
- const lines = []
170
- let errorCount = 0
171
- let warningCount = 0
172
- let timeoutCount = 0
173
- let naCount = 0
174
- let displayedCount = 0
175
-
176
- // Header (append filter description when active)
177
- let filterSuffix = ''
178
- if (options.errors && options.warnings) {
179
- filterSuffix = ' (warnings and errors only)'
180
- } else if (options.errors) {
181
- filterSuffix = ' (errors only)'
182
- } else if (options.warnings) {
183
- filterSuffix = ' (warnings only)'
184
- }
185
- lines.push({ text: `Running ${label}${filterSuffix}...\n`, level: 'info' })
186
-
187
- // Process each section
188
- for (const { section, results } of filteredSections) {
189
- // Section header
190
- lines.push({ text: chalk.bold(section), level: 'info' })
191
-
192
- for (const result of results) {
193
- displayedCount++
194
-
195
- // Count by status category
196
- if (result.level === 'na') {
197
- naCount++
198
- } else if (!result.ok) {
199
- if (result.level === 'warn') {
200
- warningCount++
201
- } else if (result.level === 'timeout') {
202
- timeoutCount++
203
- } else {
204
- errorCount++
205
- }
206
- }
207
-
208
- lines.push(formatCheck(result, { verbose, columns: terminalColumns }))
209
- }
210
-
211
- // Blank line between sections
212
- lines.push({ text: '', level: 'info' })
213
- }
214
-
215
- // Handle case where filters excluded all results
216
- if (displayedCount === 0) {
217
- let emptyMessage = null
218
- if (options.errors && options.warnings) {
219
- emptyMessage = 'No failed checks or warnings found.'
220
- } else if (options.errors) {
221
- emptyMessage = 'No failed checks found.'
222
- } else if (options.warnings) {
223
- emptyMessage = 'No warnings found.'
224
- }
225
-
226
- // Even when the filter shows nothing, summary and exitCode reflect unfiltered status
227
- const { summary, exitCode } = buildSummary(sections)
228
- return {
229
- lines: emptyMessage ? [{ text: emptyMessage, level: 'info' }] : [],
230
- summary,
231
- key: null,
232
- exitCode,
233
- isEmpty: true,
234
- }
235
- }
236
-
237
- // Build summary from unfiltered counts so filters don't hide the overall status
238
- const { summary, exitCode } = buildSummary(sections)
239
-
240
- // Build key (hidden when filtering to --errors or --warnings only)
241
- const key = (options.errors || options.warnings)
242
- ? null
243
- : {
244
- text: `Key: ${STATUS_ICONS.passed} passed ${STATUS_ICONS.failed} failed ${STATUS_ICONS.warning} warning ${STATUS_ICONS.timeout} timed out ${STATUS_ICONS.na} not applicable / not yet generated`,
245
- level: 'info',
246
- }
247
-
248
- return {
249
- lines,
250
- summary,
251
- key,
252
- exitCode,
253
- isEmpty: false,
254
- }
255
- }
256
-
257
- export default formatHuman