@thegetty/quire-cli 1.0.0-rc.45 → 1.0.0-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -36
- package/README.md +0 -9
- package/bin/cli.js +1 -46
- package/package.json +10 -27
- package/src/Command.js +6 -26
- package/src/commands/README.md +122 -213
- package/src/commands/build.js +37 -45
- package/src/commands/clean.js +19 -33
- package/src/commands/conf.js +43 -0
- package/src/commands/create.js +14 -42
- package/src/commands/epub.js +31 -67
- package/src/commands/index.js +2 -9
- package/src/commands/info.js +86 -88
- package/src/commands/pdf.js +82 -63
- package/src/commands/preview.js +31 -32
- package/src/commands/validate.js +20 -64
- package/src/commands/version.js +43 -0
- package/src/errors/validation/validation-error.js +11 -20
- package/src/helpers/clean.js +1 -1
- package/src/helpers/is-quire.js +31 -0
- package/src/helpers/test-cwd.js +6 -5
- package/src/helpers/which.js +4 -10
- package/src/lib/11ty/README.md +19 -135
- package/src/lib/11ty/api.js +93 -235
- package/src/lib/11ty/cli.js +37 -112
- package/src/lib/11ty/index.js +5 -64
- package/src/lib/11ty/paths.js +103 -0
- package/src/lib/README.md +0 -330
- package/src/lib/conf/README.md +69 -86
- package/src/lib/conf/config.js +3 -5
- package/src/lib/conf/defaults.js +0 -65
- package/src/lib/conf/schema.js +9 -103
- package/src/lib/epub/README.md +2 -133
- package/src/lib/epub/epub.js +11 -36
- package/src/lib/epub/index.js +21 -111
- package/src/lib/epub/pandoc.js +4 -33
- package/src/lib/git/README.md +2 -151
- package/src/lib/git/index.js +13 -217
- package/src/lib/i18n/README.md +3 -0
- package/src/lib/i18n/config.js +53 -0
- package/src/lib/i18n/index.js +43 -0
- package/src/lib/i18n/localeService.js +58 -0
- package/src/lib/pdf/README.md +0 -131
- package/src/lib/pdf/index.js +21 -124
- package/src/lib/pdf/paged.js +52 -100
- package/src/lib/pdf/prince.js +37 -115
- package/src/lib/pdf/split.js +33 -61
- package/src/lib/quire/README.md +19 -0
- package/src/lib/quire/index.js +353 -0
- package/src/lib/reporter/README.md +2 -211
- package/src/lib/reporter/index.js +0 -607
- package/src/main.js +48 -221
- package/src/packageConfig.js +0 -17
- package/src/validators/utils.js +1 -2
- package/man/quire.1 +0 -341
- package/man/quire.1.md +0 -232
- package/schemas/config.schema.json +0 -194
- package/schemas/figures.schema.json +0 -56
- package/schemas/layout.schema.json +0 -5
- package/schemas/objects.schema.json +0 -62
- package/schemas/publication.schema.json +0 -140
- package/schemas/references.schema.json +0 -29
- package/src/Command.spec.js +0 -99
- package/src/commands/build.spec.js +0 -113
- package/src/commands/build.test.js +0 -397
- package/src/commands/clean.spec.js +0 -118
- package/src/commands/clean.test.js +0 -382
- package/src/commands/config.js +0 -238
- package/src/commands/config.spec.js +0 -107
- package/src/commands/config.test.js +0 -845
- package/src/commands/create.spec.js +0 -112
- package/src/commands/create.test.js +0 -415
- package/src/commands/doctor.js +0 -251
- package/src/commands/doctor.spec.js +0 -115
- package/src/commands/doctor.test.js +0 -1409
- package/src/commands/epub.spec.js +0 -114
- package/src/commands/epub.test.js +0 -503
- package/src/commands/help.js +0 -60
- package/src/commands/help.test.js +0 -132
- package/src/commands/info.spec.js +0 -72
- package/src/commands/info.test.js +0 -512
- package/src/commands/pdf.spec.js +0 -114
- package/src/commands/pdf.test.js +0 -464
- package/src/commands/preview.spec.js +0 -97
- package/src/commands/preview.test.js +0 -250
- package/src/commands/use.js +0 -56
- package/src/commands/use.spec.js +0 -61
- package/src/commands/use.test.js +0 -280
- package/src/commands/validate.spec.js +0 -90
- package/src/commands/validate.test.js +0 -378
- package/src/commands/workflows.js +0 -70
- package/src/errors/build/build-failed-error.js +0 -19
- package/src/errors/build/config-field-missing-error.js +0 -20
- package/src/errors/build/config-file-not-found-error.js +0 -20
- package/src/errors/build/index.js +0 -11
- package/src/errors/help/help-topic-not-found-error.js +0 -30
- package/src/errors/help/index.js +0 -8
- package/src/errors/index.js +0 -58
- package/src/errors/input/index.js +0 -8
- package/src/errors/input/invalid-input-error.js +0 -21
- package/src/errors/install/dependency-install-error.js +0 -19
- package/src/errors/install/directory-not-empty-error.js +0 -25
- package/src/errors/install/index.js +0 -12
- package/src/errors/install/invalid-path-error.js +0 -31
- package/src/errors/install/invalid-starter-error.js +0 -27
- package/src/errors/install/version-not-found-error.js +0 -35
- package/src/errors/output/epub-generation-error.js +0 -19
- package/src/errors/output/index.js +0 -14
- package/src/errors/output/invalid-epub-library-error.js +0 -20
- package/src/errors/output/invalid-pdf-library-error.js +0 -20
- package/src/errors/output/missing-build-output-error.js +0 -21
- package/src/errors/output/pdf-generation-error.js +0 -24
- package/src/errors/output/tool-not-found-error.js +0 -37
- package/src/errors/project/index.js +0 -10
- package/src/errors/project/not-in-project-error.js +0 -20
- package/src/errors/project/project-create-error.js +0 -21
- package/src/errors/quire-error.js +0 -27
- package/src/errors/validation/index.js +0 -3
- package/src/errors/validation/unknown-config-key-error.js +0 -27
- package/src/errors/validation/unknown-config-operation-error.js +0 -24
- package/src/helpers/docs-url.js +0 -32
- package/src/helpers/pager.js +0 -59
- package/src/helpers/pager.test.js +0 -85
- package/src/helpers/suggest-similar.js +0 -124
- package/src/helpers/suggest-similar.test.js +0 -108
- package/src/helpers/test-cwd.test.js +0 -192
- package/src/lib/11ty/index.test.js +0 -655
- package/src/lib/commander/index.js +0 -103
- package/src/lib/commander/index.test.js +0 -86
- package/src/lib/commander/options.js +0 -237
- package/src/lib/commander/options.test.js +0 -205
- package/src/lib/conf/build-status.js +0 -103
- package/src/lib/conf/build-status.test.js +0 -247
- package/src/lib/conf/config.test.js +0 -281
- package/src/lib/conf/format.js +0 -60
- package/src/lib/conf/format.test.js +0 -106
- package/src/lib/conf/helpers.js +0 -91
- package/src/lib/conf/helpers.test.js +0 -136
- package/src/lib/conf/index.js +0 -22
- package/src/lib/constants.js +0 -28
- package/src/lib/doctor/README.md +0 -667
- package/src/lib/doctor/checks/environment/cli-version.js +0 -62
- package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
- package/src/lib/doctor/checks/environment/git-available.js +0 -77
- package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
- package/src/lib/doctor/checks/environment/index.js +0 -13
- package/src/lib/doctor/checks/environment/node-version.js +0 -66
- package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
- package/src/lib/doctor/checks/environment/npm-available.js +0 -66
- package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
- package/src/lib/doctor/checks/environment/os-info.js +0 -48
- package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
- package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
- package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
- package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
- package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
- package/src/lib/doctor/checks/outputs/index.js +0 -10
- package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
- package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
- package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
- package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
- package/src/lib/doctor/checks/project/data-files.js +0 -56
- package/src/lib/doctor/checks/project/data-files.test.js +0 -125
- package/src/lib/doctor/checks/project/dependencies.js +0 -53
- package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
- package/src/lib/doctor/checks/project/index.js +0 -11
- package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
- package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
- package/src/lib/doctor/checks/project/quire-project.js +0 -38
- package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
- package/src/lib/doctor/checks/tools/index.js +0 -10
- package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
- package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
- package/src/lib/doctor/checks/tools/prince-available.js +0 -81
- package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
- package/src/lib/doctor/constants.js +0 -39
- package/src/lib/doctor/formatDuration.js +0 -108
- package/src/lib/doctor/formatDuration.test.js +0 -76
- package/src/lib/doctor/formatters/human.js +0 -257
- package/src/lib/doctor/formatters/human.test.js +0 -463
- package/src/lib/doctor/formatters/index.js +0 -8
- package/src/lib/doctor/formatters/json.js +0 -78
- package/src/lib/doctor/formatters/json.test.js +0 -174
- package/src/lib/doctor/formatters/shared.js +0 -129
- package/src/lib/doctor/formatters/shared.test.js +0 -194
- package/src/lib/doctor/index.js +0 -271
- package/src/lib/doctor/index.test.js +0 -797
- package/src/lib/epub/engines.js +0 -46
- package/src/lib/epub/index.test.js +0 -518
- package/src/lib/epub/pandoc.test.js +0 -122
- package/src/lib/epub/schema.js +0 -21
- package/src/lib/error/README.md +0 -170
- package/src/lib/error/handler.js +0 -107
- package/src/lib/git/index.spec.js +0 -80
- package/src/lib/git/index.test.js +0 -453
- package/src/lib/help/frontmatter.js +0 -77
- package/src/lib/help/frontmatter.test.js +0 -139
- package/src/lib/help/index.js +0 -146
- package/src/lib/help/index.test.js +0 -188
- package/src/lib/help/topics/configuration.md +0 -77
- package/src/lib/help/topics/debugging.md +0 -69
- package/src/lib/help/topics/epub.md +0 -74
- package/src/lib/help/topics/pdf.md +0 -74
- package/src/lib/help/topics/publishing.md +0 -80
- package/src/lib/help/topics/workflows.md +0 -50
- package/src/lib/installer/index.js +0 -309
- package/src/lib/installer/index.spec.js +0 -83
- package/src/lib/installer/index.test.js +0 -545
- package/src/lib/logger/README.md +0 -424
- package/src/lib/logger/debug.js +0 -132
- package/src/lib/logger/debug.spec.js +0 -130
- package/src/lib/logger/debug.test.js +0 -128
- package/src/lib/logger/index.js +0 -228
- package/src/lib/logger/index.spec.js +0 -131
- package/src/lib/logger/index.test.js +0 -578
- package/src/lib/npm/README.md +0 -127
- package/src/lib/npm/index.js +0 -198
- package/src/lib/npm/index.spec.js +0 -60
- package/src/lib/npm/index.test.js +0 -355
- package/src/lib/pdf/engines.js +0 -46
- package/src/lib/pdf/index.test.js +0 -708
- package/src/lib/pdf/paged.test.js +0 -366
- package/src/lib/pdf/prince.test.js +0 -202
- package/src/lib/pdf/schema.js +0 -21
- package/src/lib/pdf/split.test.js +0 -445
- package/src/lib/platform.js +0 -95
- package/src/lib/process/manager.js +0 -110
- package/src/lib/process/manager.test.js +0 -55
- package/src/lib/project/build.js +0 -162
- package/src/lib/project/build.test.js +0 -275
- package/src/lib/project/config.js +0 -48
- package/src/lib/project/config.test.js +0 -134
- package/src/lib/project/detect.js +0 -47
- package/src/lib/project/detect.test.js +0 -157
- package/src/lib/project/index.js +0 -56
- package/src/lib/project/output-paths.js +0 -87
- package/src/lib/project/output-paths.test.js +0 -66
- package/src/lib/project/paths.js +0 -272
- package/src/lib/project/version.js +0 -110
- package/src/lib/project/version.test.js +0 -350
- package/src/lib/reporter/index.test.js +0 -862
- package/src/main.spec.js +0 -61
- package/src/main.test.js +0 -347
- package/src/validators/validate-data-files.js +0 -154
- 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
|