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