@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,1409 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import sinon from 'sinon'
|
|
3
|
-
import esmock from 'esmock'
|
|
4
|
-
|
|
5
|
-
test.beforeEach((t) => {
|
|
6
|
-
t.context.sandbox = sinon.createSandbox()
|
|
7
|
-
|
|
8
|
-
// Create mock logger (still used by outputJson for file-write messages)
|
|
9
|
-
t.context.mockLogger = {
|
|
10
|
-
info: t.context.sandbox.stub(),
|
|
11
|
-
error: t.context.sandbox.stub(),
|
|
12
|
-
warn: t.context.sandbox.stub(),
|
|
13
|
-
debug: t.context.sandbox.stub(),
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test.afterEach.always((t) => {
|
|
18
|
-
t.context.sandbox.restore()
|
|
19
|
-
// Reset process.exitCode to prevent tests that exercise failed checks
|
|
20
|
-
// from causing AVA to exit with a non-zero code
|
|
21
|
-
process.exitCode = undefined
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Helper to create a DoctorCommand with mocked dependencies
|
|
26
|
-
*/
|
|
27
|
-
async function createMockedDoctorCommand(sandbox, mockSections) {
|
|
28
|
-
return esmock('./doctor.js', {
|
|
29
|
-
'#lib/doctor/index.js': {
|
|
30
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
31
|
-
checkSections: [],
|
|
32
|
-
SECTION_NAMES: ['environment', 'project', 'outputs'],
|
|
33
|
-
CHECK_IDS: ['os', 'cli', 'node', 'npm', 'git', 'project', 'deps', '11ty', 'data', 'build', 'pdf', 'epub'],
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Helper to stub console methods and return the stubs
|
|
40
|
-
*
|
|
41
|
-
* Nota bene: doctor outputHuman writes directly to console (not the logger)
|
|
42
|
-
* to avoid the [quire] prefix on diagnostic output.
|
|
43
|
-
*/
|
|
44
|
-
function stubConsole(sandbox) {
|
|
45
|
-
return {
|
|
46
|
-
log: sandbox.stub(console, 'log'),
|
|
47
|
-
warn: sandbox.stub(console, 'warn'),
|
|
48
|
-
error: sandbox.stub(console, 'error'),
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Helper to collect all calls from a stub as strings
|
|
54
|
-
*/
|
|
55
|
-
function getCalls(stub) {
|
|
56
|
-
return stub.getCalls().map((call) => call.args[0])
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Helper to check if any stub call matches a pattern
|
|
61
|
-
*/
|
|
62
|
-
function calledWithMatch(stub, pattern) {
|
|
63
|
-
return getCalls(stub).some((arg) => arg != null && pattern.test(String(arg)))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
-
// Human-readable output tests
|
|
68
|
-
//
|
|
69
|
-
// Nota bene: these tests are serial because they stub global console methods.
|
|
70
|
-
// Doctor outputHuman bypasses the logger to avoid the [quire] prefix.
|
|
71
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
test.serial('doctor command should run all diagnostic checks with sections', async (t) => {
|
|
74
|
-
const { sandbox, mockLogger } = t.context
|
|
75
|
-
const consoleStubs = stubConsole(sandbox)
|
|
76
|
-
|
|
77
|
-
const mockSections = [
|
|
78
|
-
{
|
|
79
|
-
section: 'Environment',
|
|
80
|
-
results: [
|
|
81
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
82
|
-
{ name: 'npm available', ok: true, message: null },
|
|
83
|
-
{ name: 'Git available', ok: true, message: null },
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
section: 'Project',
|
|
88
|
-
results: [
|
|
89
|
-
{ name: 'Quire project', ok: true, message: 'Detected via .quire' },
|
|
90
|
-
{ name: 'Dependencies', ok: true, message: null },
|
|
91
|
-
{ name: 'Build status', ok: true, message: 'Build output is up to date' },
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
97
|
-
'#lib/doctor/index.js': {
|
|
98
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
99
|
-
},
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const command = new DoctorCommand()
|
|
103
|
-
command.logger = mockLogger
|
|
104
|
-
|
|
105
|
-
await command.action([], {}, command)
|
|
106
|
-
|
|
107
|
-
t.true(consoleStubs.log.called, 'should write to console')
|
|
108
|
-
t.true(
|
|
109
|
-
calledWithMatch(consoleStubs.log, /Running diagnostic checks/),
|
|
110
|
-
'should display diagnostic header'
|
|
111
|
-
)
|
|
112
|
-
t.true(
|
|
113
|
-
calledWithMatch(consoleStubs.log, /Environment/),
|
|
114
|
-
'should display Environment section header'
|
|
115
|
-
)
|
|
116
|
-
t.true(
|
|
117
|
-
calledWithMatch(consoleStubs.log, /Project/),
|
|
118
|
-
'should display Project section header'
|
|
119
|
-
)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test.serial('doctor command should report failed checks', async (t) => {
|
|
123
|
-
const { sandbox, mockLogger } = t.context
|
|
124
|
-
const consoleStubs = stubConsole(sandbox)
|
|
125
|
-
|
|
126
|
-
const mockSections = [
|
|
127
|
-
{
|
|
128
|
-
section: 'Environment',
|
|
129
|
-
results: [
|
|
130
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
131
|
-
{
|
|
132
|
-
name: 'npm available',
|
|
133
|
-
ok: false,
|
|
134
|
-
message: 'npm not found in PATH',
|
|
135
|
-
remediation: 'Install Node.js to get npm',
|
|
136
|
-
docsUrl: 'https://quire.getty.edu/docs-v1/install-uninstall/',
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
143
|
-
|
|
144
|
-
const command = new DoctorCommand()
|
|
145
|
-
command.logger = mockLogger
|
|
146
|
-
|
|
147
|
-
await command.action([], {}, command)
|
|
148
|
-
|
|
149
|
-
t.true(consoleStubs.error.called, 'should write errors to stderr')
|
|
150
|
-
t.true(
|
|
151
|
-
calledWithMatch(consoleStubs.error, /1 check failed/),
|
|
152
|
-
'should report failure count'
|
|
153
|
-
)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test.serial('doctor command should display remediation guidance for failed checks', async (t) => {
|
|
157
|
-
const { sandbox, mockLogger } = t.context
|
|
158
|
-
const consoleStubs = stubConsole(sandbox)
|
|
159
|
-
|
|
160
|
-
const mockSections = [
|
|
161
|
-
{
|
|
162
|
-
section: 'Environment',
|
|
163
|
-
results: [
|
|
164
|
-
{
|
|
165
|
-
name: 'npm available',
|
|
166
|
-
ok: false,
|
|
167
|
-
message: 'npm not found in PATH',
|
|
168
|
-
remediation: 'Install Node.js to get npm',
|
|
169
|
-
docsUrl: 'https://quire.getty.edu/docs-v1/install-uninstall/',
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
]
|
|
174
|
-
|
|
175
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
176
|
-
|
|
177
|
-
const command = new DoctorCommand()
|
|
178
|
-
command.logger = mockLogger
|
|
179
|
-
|
|
180
|
-
await command.action([], {}, command)
|
|
181
|
-
|
|
182
|
-
// Failed checks output goes through console.error as a single joined string
|
|
183
|
-
t.true(
|
|
184
|
-
calledWithMatch(consoleStubs.error, /How to fix/),
|
|
185
|
-
'should display "How to fix" header'
|
|
186
|
-
)
|
|
187
|
-
t.true(
|
|
188
|
-
calledWithMatch(consoleStubs.error, /Install Node.js/),
|
|
189
|
-
'should display remediation text'
|
|
190
|
-
)
|
|
191
|
-
t.true(
|
|
192
|
-
calledWithMatch(consoleStubs.error, /Documentation:/),
|
|
193
|
-
'should display documentation label'
|
|
194
|
-
)
|
|
195
|
-
t.true(
|
|
196
|
-
calledWithMatch(consoleStubs.error, /quire\.getty\.edu/),
|
|
197
|
-
'should display documentation URL'
|
|
198
|
-
)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
test.serial('doctor command should display warnings with warning indicator', async (t) => {
|
|
202
|
-
const { sandbox, mockLogger } = t.context
|
|
203
|
-
const consoleStubs = stubConsole(sandbox)
|
|
204
|
-
|
|
205
|
-
const mockSections = [
|
|
206
|
-
{
|
|
207
|
-
section: 'Project',
|
|
208
|
-
results: [
|
|
209
|
-
{
|
|
210
|
-
name: 'Build status',
|
|
211
|
-
ok: false,
|
|
212
|
-
level: 'warn',
|
|
213
|
-
message: 'Build output is 1 hour older than source files',
|
|
214
|
-
remediation: 'Run quire build to regenerate',
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
},
|
|
218
|
-
]
|
|
219
|
-
|
|
220
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
221
|
-
'#lib/doctor/index.js': {
|
|
222
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
223
|
-
},
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
const command = new DoctorCommand()
|
|
227
|
-
command.logger = mockLogger
|
|
228
|
-
|
|
229
|
-
await command.action([], {}, command)
|
|
230
|
-
|
|
231
|
-
// Warnings go through console.warn
|
|
232
|
-
t.true(consoleStubs.warn.called, 'should write warnings to stderr')
|
|
233
|
-
t.true(
|
|
234
|
-
calledWithMatch(consoleStubs.warn, /⚠/),
|
|
235
|
-
'should display warning indicator'
|
|
236
|
-
)
|
|
237
|
-
t.true(
|
|
238
|
-
calledWithMatch(consoleStubs.warn, /All checks passed with 1 warning/),
|
|
239
|
-
'should report passed with warning count'
|
|
240
|
-
)
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
test.serial('doctor command should report all checks passed when healthy', async (t) => {
|
|
244
|
-
const { sandbox, mockLogger } = t.context
|
|
245
|
-
const consoleStubs = stubConsole(sandbox)
|
|
246
|
-
|
|
247
|
-
const mockSections = [
|
|
248
|
-
{
|
|
249
|
-
section: 'Environment',
|
|
250
|
-
results: [
|
|
251
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
252
|
-
{ name: 'npm available', ok: true, message: null },
|
|
253
|
-
{ name: 'Git available', ok: true, message: null },
|
|
254
|
-
],
|
|
255
|
-
},
|
|
256
|
-
]
|
|
257
|
-
|
|
258
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
259
|
-
'#lib/doctor/index.js': {
|
|
260
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
261
|
-
},
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
const command = new DoctorCommand()
|
|
265
|
-
command.logger = mockLogger
|
|
266
|
-
|
|
267
|
-
await command.action([], {}, command)
|
|
268
|
-
|
|
269
|
-
t.true(
|
|
270
|
-
calledWithMatch(consoleStubs.log, /All checks passed/),
|
|
271
|
-
'should report all checks passed'
|
|
272
|
-
)
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
test.serial('doctor command should display check messages', async (t) => {
|
|
276
|
-
const { sandbox, mockLogger } = t.context
|
|
277
|
-
const consoleStubs = stubConsole(sandbox)
|
|
278
|
-
|
|
279
|
-
const mockSections = [
|
|
280
|
-
{
|
|
281
|
-
section: 'Environment',
|
|
282
|
-
results: [
|
|
283
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0 (>= 22 required)' },
|
|
284
|
-
],
|
|
285
|
-
},
|
|
286
|
-
]
|
|
287
|
-
|
|
288
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
289
|
-
'#lib/doctor/index.js': {
|
|
290
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
291
|
-
},
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
const command = new DoctorCommand()
|
|
295
|
-
command.logger = mockLogger
|
|
296
|
-
|
|
297
|
-
await command.action([], {}, command)
|
|
298
|
-
|
|
299
|
-
t.true(
|
|
300
|
-
calledWithMatch(consoleStubs.log, /v22\.0\.0/),
|
|
301
|
-
'should display check message'
|
|
302
|
-
)
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
|
-
// Exit code tests
|
|
307
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
308
|
-
|
|
309
|
-
test.serial('doctor command should set exitCode 1 when checks fail', async (t) => {
|
|
310
|
-
const { sandbox, mockLogger } = t.context
|
|
311
|
-
stubConsole(sandbox)
|
|
312
|
-
|
|
313
|
-
const savedExitCode = process.exitCode
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
const mockSections = [
|
|
317
|
-
{
|
|
318
|
-
section: 'Environment',
|
|
319
|
-
results: [
|
|
320
|
-
{
|
|
321
|
-
name: 'npm available',
|
|
322
|
-
ok: false,
|
|
323
|
-
message: 'npm not found in PATH',
|
|
324
|
-
remediation: 'Install Node.js to get npm',
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
|
-
},
|
|
328
|
-
]
|
|
329
|
-
|
|
330
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
331
|
-
|
|
332
|
-
const command = new DoctorCommand()
|
|
333
|
-
command.logger = mockLogger
|
|
334
|
-
|
|
335
|
-
await command.action([], {}, command)
|
|
336
|
-
|
|
337
|
-
t.is(process.exitCode, 1, 'should set process.exitCode to 1')
|
|
338
|
-
} finally {
|
|
339
|
-
process.exitCode = savedExitCode
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
test.serial('doctor command should not set exitCode when all checks pass', async (t) => {
|
|
344
|
-
const { sandbox, mockLogger } = t.context
|
|
345
|
-
stubConsole(sandbox)
|
|
346
|
-
|
|
347
|
-
const savedExitCode = process.exitCode
|
|
348
|
-
process.exitCode = undefined
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
const mockSections = [
|
|
352
|
-
{
|
|
353
|
-
section: 'Environment',
|
|
354
|
-
results: [
|
|
355
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
356
|
-
{ name: 'npm available', ok: true, message: null },
|
|
357
|
-
],
|
|
358
|
-
},
|
|
359
|
-
]
|
|
360
|
-
|
|
361
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
362
|
-
|
|
363
|
-
const command = new DoctorCommand()
|
|
364
|
-
command.logger = mockLogger
|
|
365
|
-
|
|
366
|
-
await command.action([], {}, command)
|
|
367
|
-
|
|
368
|
-
t.is(process.exitCode, undefined, 'should not set process.exitCode on success')
|
|
369
|
-
} finally {
|
|
370
|
-
process.exitCode = savedExitCode
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
test.serial('doctor command should not set exitCode when only warnings exist', async (t) => {
|
|
375
|
-
const { sandbox, mockLogger } = t.context
|
|
376
|
-
const consoleStubs = stubConsole(sandbox)
|
|
377
|
-
|
|
378
|
-
const savedExitCode = process.exitCode
|
|
379
|
-
process.exitCode = undefined
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const mockSections = [
|
|
383
|
-
{
|
|
384
|
-
section: 'Project',
|
|
385
|
-
results: [
|
|
386
|
-
{
|
|
387
|
-
name: 'Build status',
|
|
388
|
-
ok: false,
|
|
389
|
-
level: 'warn',
|
|
390
|
-
message: 'Build output is stale',
|
|
391
|
-
remediation: 'Run quire build',
|
|
392
|
-
},
|
|
393
|
-
],
|
|
394
|
-
},
|
|
395
|
-
]
|
|
396
|
-
|
|
397
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
398
|
-
|
|
399
|
-
const command = new DoctorCommand()
|
|
400
|
-
command.logger = mockLogger
|
|
401
|
-
|
|
402
|
-
await command.action([], {}, command)
|
|
403
|
-
|
|
404
|
-
t.is(process.exitCode, undefined, 'should not set process.exitCode for warnings')
|
|
405
|
-
t.true(
|
|
406
|
-
calledWithMatch(consoleStubs.warn, /All checks passed with 1 warning/),
|
|
407
|
-
'should report passed with warning count'
|
|
408
|
-
)
|
|
409
|
-
} finally {
|
|
410
|
-
process.exitCode = savedExitCode
|
|
411
|
-
}
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
415
|
-
// Summary count tests
|
|
416
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
417
|
-
|
|
418
|
-
test.serial('doctor command should display plural error count', async (t) => {
|
|
419
|
-
const { sandbox, mockLogger } = t.context
|
|
420
|
-
const consoleStubs = stubConsole(sandbox)
|
|
421
|
-
|
|
422
|
-
const mockSections = [
|
|
423
|
-
{
|
|
424
|
-
section: 'Environment',
|
|
425
|
-
results: [
|
|
426
|
-
{ name: 'npm', ok: false, message: 'not found' },
|
|
427
|
-
{ name: 'Git', ok: false, message: 'not found' },
|
|
428
|
-
],
|
|
429
|
-
},
|
|
430
|
-
]
|
|
431
|
-
|
|
432
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
433
|
-
|
|
434
|
-
const command = new DoctorCommand()
|
|
435
|
-
command.logger = mockLogger
|
|
436
|
-
|
|
437
|
-
await command.action([], {}, command)
|
|
438
|
-
|
|
439
|
-
t.true(
|
|
440
|
-
calledWithMatch(consoleStubs.error, /2 checks failed/),
|
|
441
|
-
'should use plural for multiple errors'
|
|
442
|
-
)
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
test.serial('doctor command should display errors and warnings together', async (t) => {
|
|
446
|
-
const { sandbox, mockLogger } = t.context
|
|
447
|
-
const consoleStubs = stubConsole(sandbox)
|
|
448
|
-
|
|
449
|
-
const mockSections = [
|
|
450
|
-
{
|
|
451
|
-
section: 'Environment',
|
|
452
|
-
results: [
|
|
453
|
-
{ name: 'npm', ok: false, message: 'not found' },
|
|
454
|
-
{ name: 'Git', ok: false, level: 'warn', message: 'outdated' },
|
|
455
|
-
{ name: 'Prince', ok: false, level: 'warn', message: 'not found' },
|
|
456
|
-
],
|
|
457
|
-
},
|
|
458
|
-
]
|
|
459
|
-
|
|
460
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
461
|
-
|
|
462
|
-
const command = new DoctorCommand()
|
|
463
|
-
command.logger = mockLogger
|
|
464
|
-
|
|
465
|
-
await command.action([], {}, command)
|
|
466
|
-
|
|
467
|
-
t.true(
|
|
468
|
-
calledWithMatch(consoleStubs.error, /1 check failed, 2 warnings/),
|
|
469
|
-
'should display both error and warning counts'
|
|
470
|
-
)
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
test.serial('doctor command should display plural warning count', async (t) => {
|
|
474
|
-
const { sandbox, mockLogger } = t.context
|
|
475
|
-
const consoleStubs = stubConsole(sandbox)
|
|
476
|
-
|
|
477
|
-
const mockSections = [
|
|
478
|
-
{
|
|
479
|
-
section: 'Project',
|
|
480
|
-
results: [
|
|
481
|
-
{ name: 'Build', ok: false, level: 'warn', message: 'stale' },
|
|
482
|
-
{ name: 'PDF', ok: false, level: 'warn', message: 'stale' },
|
|
483
|
-
],
|
|
484
|
-
},
|
|
485
|
-
]
|
|
486
|
-
|
|
487
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
488
|
-
'#lib/doctor/index.js': {
|
|
489
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
490
|
-
},
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
const command = new DoctorCommand()
|
|
494
|
-
command.logger = mockLogger
|
|
495
|
-
|
|
496
|
-
await command.action([], {}, command)
|
|
497
|
-
|
|
498
|
-
t.true(
|
|
499
|
-
calledWithMatch(consoleStubs.warn, /All checks passed with 2 warnings/),
|
|
500
|
-
'should use plural for multiple warnings'
|
|
501
|
-
)
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
505
|
-
// N/A (not applicable) state tests
|
|
506
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
507
|
-
|
|
508
|
-
test.serial('doctor command should display N/A checks with open circle indicator', async (t) => {
|
|
509
|
-
const { sandbox, mockLogger } = t.context
|
|
510
|
-
const consoleStubs = stubConsole(sandbox)
|
|
511
|
-
|
|
512
|
-
const mockSections = [
|
|
513
|
-
{
|
|
514
|
-
section: 'Outputs',
|
|
515
|
-
results: [
|
|
516
|
-
{
|
|
517
|
-
name: 'Build status',
|
|
518
|
-
ok: true,
|
|
519
|
-
level: 'na',
|
|
520
|
-
message: 'No build output yet (run quire build)',
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
name: 'PDF output',
|
|
524
|
-
ok: true,
|
|
525
|
-
level: 'na',
|
|
526
|
-
message: 'No PDF output (run quire pdf to generate)',
|
|
527
|
-
},
|
|
528
|
-
],
|
|
529
|
-
},
|
|
530
|
-
]
|
|
531
|
-
|
|
532
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
533
|
-
'#lib/doctor/index.js': {
|
|
534
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
535
|
-
},
|
|
536
|
-
})
|
|
537
|
-
|
|
538
|
-
const command = new DoctorCommand()
|
|
539
|
-
command.logger = mockLogger
|
|
540
|
-
|
|
541
|
-
await command.action([], {}, command)
|
|
542
|
-
|
|
543
|
-
// N/A checks should use ○ indicator
|
|
544
|
-
t.true(
|
|
545
|
-
calledWithMatch(consoleStubs.log, /○/),
|
|
546
|
-
'should display open circle indicator for N/A checks'
|
|
547
|
-
)
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
test.serial('doctor command should not count N/A checks as failures in summary', async (t) => {
|
|
551
|
-
const { sandbox, mockLogger } = t.context
|
|
552
|
-
const consoleStubs = stubConsole(sandbox)
|
|
553
|
-
|
|
554
|
-
const mockSections = [
|
|
555
|
-
{
|
|
556
|
-
section: 'Outputs',
|
|
557
|
-
results: [
|
|
558
|
-
{ name: 'Build status', ok: true, level: 'na', message: 'No build output' },
|
|
559
|
-
{ name: 'PDF output', ok: true, level: 'na', message: 'No PDF output' },
|
|
560
|
-
{ name: 'EPUB output', ok: true, level: 'na', message: 'No EPUB output' },
|
|
561
|
-
],
|
|
562
|
-
},
|
|
563
|
-
]
|
|
564
|
-
|
|
565
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
566
|
-
'#lib/doctor/index.js': {
|
|
567
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
568
|
-
},
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
const command = new DoctorCommand()
|
|
572
|
-
command.logger = mockLogger
|
|
573
|
-
|
|
574
|
-
await command.action([], {}, command)
|
|
575
|
-
|
|
576
|
-
// Should report all checks passed (N/A checks don't count as failures)
|
|
577
|
-
t.true(
|
|
578
|
-
calledWithMatch(consoleStubs.log, /All checks passed/),
|
|
579
|
-
'should report all checks passed when only N/A checks exist'
|
|
580
|
-
)
|
|
581
|
-
// Should include N/A count in summary
|
|
582
|
-
t.true(
|
|
583
|
-
calledWithMatch(consoleStubs.log, /3 not applicable/),
|
|
584
|
-
'should display N/A count in summary'
|
|
585
|
-
)
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
test.serial('doctor command should not count N/A checks as failures when mixed with errors', async (t) => {
|
|
589
|
-
const { sandbox, mockLogger } = t.context
|
|
590
|
-
const consoleStubs = stubConsole(sandbox)
|
|
591
|
-
|
|
592
|
-
const mockSections = [
|
|
593
|
-
{
|
|
594
|
-
section: 'Environment',
|
|
595
|
-
results: [
|
|
596
|
-
{ name: 'npm', ok: false, message: 'not found' },
|
|
597
|
-
],
|
|
598
|
-
},
|
|
599
|
-
{
|
|
600
|
-
section: 'Outputs',
|
|
601
|
-
results: [
|
|
602
|
-
{ name: 'Build status', ok: true, level: 'na', message: 'No build output' },
|
|
603
|
-
{ name: 'PDF output', ok: true, level: 'na', message: 'No PDF output' },
|
|
604
|
-
],
|
|
605
|
-
},
|
|
606
|
-
]
|
|
607
|
-
|
|
608
|
-
const DoctorCommand = await createMockedDoctorCommand(sandbox, mockSections)
|
|
609
|
-
|
|
610
|
-
const command = new DoctorCommand()
|
|
611
|
-
command.logger = mockLogger
|
|
612
|
-
|
|
613
|
-
await command.action([], {}, command)
|
|
614
|
-
|
|
615
|
-
// Should only report 1 error (N/A checks not counted as failures)
|
|
616
|
-
t.true(
|
|
617
|
-
calledWithMatch(consoleStubs.error, /1 check failed/),
|
|
618
|
-
'should only count actual errors, not N/A checks'
|
|
619
|
-
)
|
|
620
|
-
// Should include N/A count in summary
|
|
621
|
-
t.true(
|
|
622
|
-
calledWithMatch(consoleStubs.error, /2 not applicable/),
|
|
623
|
-
'should display N/A count alongside error count'
|
|
624
|
-
)
|
|
625
|
-
})
|
|
626
|
-
|
|
627
|
-
test.serial('doctor command should route N/A checks to stdout', async (t) => {
|
|
628
|
-
const { sandbox, mockLogger } = t.context
|
|
629
|
-
const consoleStubs = stubConsole(sandbox)
|
|
630
|
-
|
|
631
|
-
const mockSections = [
|
|
632
|
-
{
|
|
633
|
-
section: 'Outputs',
|
|
634
|
-
results: [
|
|
635
|
-
{ name: 'PDF output', ok: true, level: 'na', message: 'No PDF output' },
|
|
636
|
-
],
|
|
637
|
-
},
|
|
638
|
-
]
|
|
639
|
-
|
|
640
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
641
|
-
'#lib/doctor/index.js': {
|
|
642
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
643
|
-
},
|
|
644
|
-
})
|
|
645
|
-
|
|
646
|
-
const command = new DoctorCommand()
|
|
647
|
-
command.logger = mockLogger
|
|
648
|
-
|
|
649
|
-
await command.action([], {}, command)
|
|
650
|
-
|
|
651
|
-
// N/A should go through console.log (stdout), not console.warn or console.error
|
|
652
|
-
const logCalls = getCalls(consoleStubs.log)
|
|
653
|
-
const hasNaInLog = logCalls.some((arg) => arg && arg.includes('○') && arg.includes('PDF output'))
|
|
654
|
-
t.true(hasNaInLog, 'N/A checks should be routed to stdout via console.log')
|
|
655
|
-
})
|
|
656
|
-
|
|
657
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
658
|
-
// Verbose flag tests
|
|
659
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
660
|
-
|
|
661
|
-
test.serial('doctor command should display details when --verbose is passed', async (t) => {
|
|
662
|
-
const { sandbox, mockLogger } = t.context
|
|
663
|
-
const consoleStubs = stubConsole(sandbox)
|
|
664
|
-
|
|
665
|
-
const mockSections = [
|
|
666
|
-
{
|
|
667
|
-
section: 'Environment',
|
|
668
|
-
results: [
|
|
669
|
-
{ name: 'npm', ok: true, message: '10.2.4', details: '/usr/local/bin/npm' },
|
|
670
|
-
{ name: 'Git', ok: true, message: '2.43.0', details: '/usr/bin/git' },
|
|
671
|
-
],
|
|
672
|
-
},
|
|
673
|
-
]
|
|
674
|
-
|
|
675
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
676
|
-
'#lib/doctor/index.js': {
|
|
677
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
678
|
-
},
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
const command = new DoctorCommand()
|
|
682
|
-
command.logger = mockLogger
|
|
683
|
-
|
|
684
|
-
await command.action([], { verbose: true }, command)
|
|
685
|
-
|
|
686
|
-
// Should display details for each check
|
|
687
|
-
t.true(
|
|
688
|
-
calledWithMatch(consoleStubs.log, /\/usr\/local\/bin\/npm/),
|
|
689
|
-
'should display npm path in verbose mode'
|
|
690
|
-
)
|
|
691
|
-
t.true(
|
|
692
|
-
calledWithMatch(consoleStubs.log, /\/usr\/bin\/git/),
|
|
693
|
-
'should display git path in verbose mode'
|
|
694
|
-
)
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
test.serial('doctor command should not display details when --verbose is not passed', async (t) => {
|
|
698
|
-
const { sandbox, mockLogger } = t.context
|
|
699
|
-
const consoleStubs = stubConsole(sandbox)
|
|
700
|
-
|
|
701
|
-
const mockSections = [
|
|
702
|
-
{
|
|
703
|
-
section: 'Environment',
|
|
704
|
-
results: [
|
|
705
|
-
{ name: 'npm', ok: true, message: '10.2.4', details: '/usr/local/bin/npm' },
|
|
706
|
-
],
|
|
707
|
-
},
|
|
708
|
-
]
|
|
709
|
-
|
|
710
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
711
|
-
'#lib/doctor/index.js': {
|
|
712
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
713
|
-
},
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
const command = new DoctorCommand()
|
|
717
|
-
command.logger = mockLogger
|
|
718
|
-
|
|
719
|
-
await command.action([], {}, command)
|
|
720
|
-
|
|
721
|
-
// Should not display details path
|
|
722
|
-
const allCalls = getCalls(consoleStubs.log)
|
|
723
|
-
const hasPathInOutput = allCalls.some((arg) => arg && arg.includes('/usr/local/bin/npm'))
|
|
724
|
-
t.false(hasPathInOutput, 'should not display npm path without verbose flag')
|
|
725
|
-
})
|
|
726
|
-
|
|
727
|
-
test.serial('doctor command should handle checks without details in verbose mode', async (t) => {
|
|
728
|
-
const { sandbox, mockLogger } = t.context
|
|
729
|
-
const consoleStubs = stubConsole(sandbox)
|
|
730
|
-
|
|
731
|
-
const mockSections = [
|
|
732
|
-
{
|
|
733
|
-
section: 'Environment',
|
|
734
|
-
results: [
|
|
735
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' }, // no details field
|
|
736
|
-
],
|
|
737
|
-
},
|
|
738
|
-
]
|
|
739
|
-
|
|
740
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
741
|
-
'#lib/doctor/index.js': {
|
|
742
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
743
|
-
},
|
|
744
|
-
})
|
|
745
|
-
|
|
746
|
-
const command = new DoctorCommand()
|
|
747
|
-
command.logger = mockLogger
|
|
748
|
-
|
|
749
|
-
// Should not throw when details is undefined
|
|
750
|
-
await t.notThrowsAsync(async () => {
|
|
751
|
-
await command.action([], { verbose: true }, command)
|
|
752
|
-
})
|
|
753
|
-
|
|
754
|
-
t.true(calledWithMatch(consoleStubs.log, /v22\.0\.0/), 'should still display message')
|
|
755
|
-
})
|
|
756
|
-
|
|
757
|
-
test.serial('doctor command should display symbol key in verbose mode', async (t) => {
|
|
758
|
-
const { sandbox, mockLogger } = t.context
|
|
759
|
-
const consoleStubs = stubConsole(sandbox)
|
|
760
|
-
|
|
761
|
-
const mockSections = [
|
|
762
|
-
{
|
|
763
|
-
section: 'Environment',
|
|
764
|
-
results: [
|
|
765
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
766
|
-
],
|
|
767
|
-
},
|
|
768
|
-
]
|
|
769
|
-
|
|
770
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
771
|
-
'#lib/doctor/index.js': {
|
|
772
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
773
|
-
},
|
|
774
|
-
})
|
|
775
|
-
|
|
776
|
-
const command = new DoctorCommand()
|
|
777
|
-
command.logger = mockLogger
|
|
778
|
-
|
|
779
|
-
await command.action([], { verbose: true }, command)
|
|
780
|
-
|
|
781
|
-
t.true(
|
|
782
|
-
calledWithMatch(consoleStubs.log, /Key:.*✓.*passed.*✗.*failed.*⚠.*warning.*○.*not applicable.*not yet generated/),
|
|
783
|
-
'should display symbol key'
|
|
784
|
-
)
|
|
785
|
-
})
|
|
786
|
-
|
|
787
|
-
test.serial('doctor command should display symbol key in default mode', async (t) => {
|
|
788
|
-
const { sandbox, mockLogger } = t.context
|
|
789
|
-
const consoleStubs = stubConsole(sandbox)
|
|
790
|
-
|
|
791
|
-
const mockSections = [
|
|
792
|
-
{
|
|
793
|
-
section: 'Environment',
|
|
794
|
-
results: [
|
|
795
|
-
{ name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
796
|
-
],
|
|
797
|
-
},
|
|
798
|
-
]
|
|
799
|
-
|
|
800
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
801
|
-
'#lib/doctor/index.js': {
|
|
802
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
803
|
-
},
|
|
804
|
-
})
|
|
805
|
-
|
|
806
|
-
const command = new DoctorCommand()
|
|
807
|
-
command.logger = mockLogger
|
|
808
|
-
|
|
809
|
-
await command.action([], {}, command)
|
|
810
|
-
|
|
811
|
-
const allCalls = getCalls(consoleStubs.log)
|
|
812
|
-
const hasKey = allCalls.some((arg) => arg && arg.includes('Key:'))
|
|
813
|
-
t.true(hasKey, 'should display symbol key in default mode')
|
|
814
|
-
})
|
|
815
|
-
|
|
816
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
817
|
-
// JSON output tests
|
|
818
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
819
|
-
|
|
820
|
-
test.serial('doctor command should output valid JSON when --json flag is used', async (t) => {
|
|
821
|
-
const { sandbox, mockLogger } = t.context
|
|
822
|
-
|
|
823
|
-
const mockSections = [
|
|
824
|
-
{
|
|
825
|
-
section: 'Environment',
|
|
826
|
-
results: [
|
|
827
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
828
|
-
{ id: 'npm', name: 'npm', ok: true, message: '10.2.4', details: '/usr/local/bin/npm' },
|
|
829
|
-
],
|
|
830
|
-
},
|
|
831
|
-
]
|
|
832
|
-
|
|
833
|
-
// Capture console.log output
|
|
834
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
835
|
-
|
|
836
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
837
|
-
'#lib/doctor/index.js': {
|
|
838
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
839
|
-
},
|
|
840
|
-
})
|
|
841
|
-
|
|
842
|
-
const command = new DoctorCommand()
|
|
843
|
-
command.logger = mockLogger
|
|
844
|
-
|
|
845
|
-
await command.action([], { json: true }, command)
|
|
846
|
-
|
|
847
|
-
// Should call console.log with JSON
|
|
848
|
-
t.true(consoleLogStub.calledOnce, 'should call console.log once')
|
|
849
|
-
|
|
850
|
-
const output = consoleLogStub.firstCall.args[0]
|
|
851
|
-
const parsed = JSON.parse(output)
|
|
852
|
-
|
|
853
|
-
t.truthy(parsed.summary, 'should have summary object')
|
|
854
|
-
t.is(parsed.summary.passed, 2, 'should count passed checks')
|
|
855
|
-
t.is(parsed.summary.failed, 0, 'should count failed checks')
|
|
856
|
-
t.truthy(parsed.results, 'should have results array')
|
|
857
|
-
t.is(parsed.results.length, 2, 'should include all results')
|
|
858
|
-
t.is(parsed.results[0].id, 'node', 'check should have id')
|
|
859
|
-
t.is(parsed.results[0].status, 'passed', 'check should have status')
|
|
860
|
-
})
|
|
861
|
-
|
|
862
|
-
test.serial('doctor command JSON output should set exitCode 1 when checks fail', async (t) => {
|
|
863
|
-
const { sandbox, mockLogger } = t.context
|
|
864
|
-
|
|
865
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
866
|
-
const savedExitCode = process.exitCode
|
|
867
|
-
|
|
868
|
-
try {
|
|
869
|
-
const mockSections = [
|
|
870
|
-
{
|
|
871
|
-
section: 'Environment',
|
|
872
|
-
results: [
|
|
873
|
-
{ id: 'node', name: 'Node.js version', ok: false, message: 'v18.0.0 found' },
|
|
874
|
-
],
|
|
875
|
-
},
|
|
876
|
-
]
|
|
877
|
-
|
|
878
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
879
|
-
'#lib/doctor/index.js': {
|
|
880
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
881
|
-
},
|
|
882
|
-
})
|
|
883
|
-
|
|
884
|
-
const command = new DoctorCommand()
|
|
885
|
-
command.logger = mockLogger
|
|
886
|
-
|
|
887
|
-
await command.action([], { json: true }, command)
|
|
888
|
-
|
|
889
|
-
t.is(process.exitCode, 1, 'should set process.exitCode to 1')
|
|
890
|
-
|
|
891
|
-
const output = consoleLogStub.firstCall.args[0]
|
|
892
|
-
const parsed = JSON.parse(output)
|
|
893
|
-
t.is(parsed.summary.failed, 1, 'should count failed check')
|
|
894
|
-
} finally {
|
|
895
|
-
process.exitCode = savedExitCode
|
|
896
|
-
}
|
|
897
|
-
})
|
|
898
|
-
|
|
899
|
-
test.serial('doctor command JSON output should include remediation for failed checks', async (t) => {
|
|
900
|
-
const { sandbox, mockLogger } = t.context
|
|
901
|
-
|
|
902
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
903
|
-
|
|
904
|
-
const mockSections = [
|
|
905
|
-
{
|
|
906
|
-
section: 'Environment',
|
|
907
|
-
results: [
|
|
908
|
-
{
|
|
909
|
-
id: 'node',
|
|
910
|
-
name: 'Node.js version',
|
|
911
|
-
ok: false,
|
|
912
|
-
message: 'v18.0.0 found',
|
|
913
|
-
remediation: 'Install Node.js 22+',
|
|
914
|
-
docsUrl: 'https://example.com/docs',
|
|
915
|
-
},
|
|
916
|
-
],
|
|
917
|
-
},
|
|
918
|
-
]
|
|
919
|
-
|
|
920
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
921
|
-
'#lib/doctor/index.js': {
|
|
922
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
923
|
-
},
|
|
924
|
-
})
|
|
925
|
-
|
|
926
|
-
const command = new DoctorCommand()
|
|
927
|
-
command.logger = mockLogger
|
|
928
|
-
|
|
929
|
-
await command.action([], { json: true }, command)
|
|
930
|
-
|
|
931
|
-
const output = consoleLogStub.firstCall.args[0]
|
|
932
|
-
const parsed = JSON.parse(output)
|
|
933
|
-
|
|
934
|
-
t.is(parsed.results[0].remediation, 'Install Node.js 22+', 'should include remediation')
|
|
935
|
-
t.is(parsed.results[0].docsUrl, 'https://example.com/docs', 'should include docsUrl')
|
|
936
|
-
})
|
|
937
|
-
|
|
938
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
939
|
-
// --errors and --warnings filter tests
|
|
940
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
941
|
-
|
|
942
|
-
test.serial('doctor command --errors flag should show only failed checks', async (t) => {
|
|
943
|
-
const { sandbox, mockLogger } = t.context
|
|
944
|
-
const consoleStubs = stubConsole(sandbox)
|
|
945
|
-
|
|
946
|
-
const mockSections = [
|
|
947
|
-
{
|
|
948
|
-
section: 'Environment',
|
|
949
|
-
results: [
|
|
950
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
951
|
-
{ id: 'npm', name: 'npm', ok: false, message: 'not found' },
|
|
952
|
-
{ id: 'git', name: 'Git', ok: false, level: 'warn', message: 'old version' },
|
|
953
|
-
],
|
|
954
|
-
},
|
|
955
|
-
]
|
|
956
|
-
|
|
957
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
958
|
-
'#lib/doctor/index.js': {
|
|
959
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
960
|
-
},
|
|
961
|
-
})
|
|
962
|
-
|
|
963
|
-
const command = new DoctorCommand()
|
|
964
|
-
command.logger = mockLogger
|
|
965
|
-
|
|
966
|
-
await command.action([], { errors: true }, command)
|
|
967
|
-
|
|
968
|
-
// Should only show failed checks (not passed or warnings)
|
|
969
|
-
const errorCalls = getCalls(consoleStubs.error)
|
|
970
|
-
const hasNpm = errorCalls.some((arg) => arg && arg.includes('npm'))
|
|
971
|
-
t.true(hasNpm, 'should show failed npm check')
|
|
972
|
-
|
|
973
|
-
// Should not show passed checks in output
|
|
974
|
-
const logCalls = getCalls(consoleStubs.log)
|
|
975
|
-
const hasNode = logCalls.some((arg) => arg && arg.includes('Node.js') && arg.includes('✓'))
|
|
976
|
-
t.false(hasNode, 'should not show passed Node.js check')
|
|
977
|
-
})
|
|
978
|
-
|
|
979
|
-
test.serial('doctor command --warnings flag should show only warning checks', async (t) => {
|
|
980
|
-
const { sandbox, mockLogger } = t.context
|
|
981
|
-
const consoleStubs = stubConsole(sandbox)
|
|
982
|
-
|
|
983
|
-
const mockSections = [
|
|
984
|
-
{
|
|
985
|
-
section: 'Environment',
|
|
986
|
-
results: [
|
|
987
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
988
|
-
{ id: 'cli', name: 'CLI version', ok: false, level: 'warn', message: 'update available' },
|
|
989
|
-
{ id: 'npm', name: 'npm', ok: false, message: 'not found' },
|
|
990
|
-
],
|
|
991
|
-
},
|
|
992
|
-
]
|
|
993
|
-
|
|
994
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
995
|
-
'#lib/doctor/index.js': {
|
|
996
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
997
|
-
},
|
|
998
|
-
})
|
|
999
|
-
|
|
1000
|
-
const command = new DoctorCommand()
|
|
1001
|
-
command.logger = mockLogger
|
|
1002
|
-
|
|
1003
|
-
await command.action([], { warnings: true }, command)
|
|
1004
|
-
|
|
1005
|
-
// Should show warning checks
|
|
1006
|
-
const warnCalls = getCalls(consoleStubs.warn)
|
|
1007
|
-
const hasCli = warnCalls.some((arg) => arg && arg.includes('CLI version'))
|
|
1008
|
-
t.true(hasCli, 'should show CLI version warning')
|
|
1009
|
-
|
|
1010
|
-
// Should not show passed checks
|
|
1011
|
-
const logCalls = getCalls(consoleStubs.log)
|
|
1012
|
-
const hasNode = logCalls.some((arg) => arg && arg.includes('Node.js') && arg.includes('✓'))
|
|
1013
|
-
t.false(hasNode, 'should not show passed Node.js check')
|
|
1014
|
-
})
|
|
1015
|
-
|
|
1016
|
-
test.serial('doctor command should show "No failed checks found" when --errors finds none', async (t) => {
|
|
1017
|
-
const { sandbox, mockLogger } = t.context
|
|
1018
|
-
const consoleStubs = stubConsole(sandbox)
|
|
1019
|
-
|
|
1020
|
-
const mockSections = [
|
|
1021
|
-
{
|
|
1022
|
-
section: 'Environment',
|
|
1023
|
-
results: [
|
|
1024
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1025
|
-
{ id: 'npm', name: 'npm', ok: true, message: '10.2.4' },
|
|
1026
|
-
],
|
|
1027
|
-
},
|
|
1028
|
-
]
|
|
1029
|
-
|
|
1030
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1031
|
-
'#lib/doctor/index.js': {
|
|
1032
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1033
|
-
},
|
|
1034
|
-
})
|
|
1035
|
-
|
|
1036
|
-
const command = new DoctorCommand()
|
|
1037
|
-
command.logger = mockLogger
|
|
1038
|
-
|
|
1039
|
-
await command.action([], { errors: true }, command)
|
|
1040
|
-
|
|
1041
|
-
t.true(
|
|
1042
|
-
calledWithMatch(consoleStubs.log, /No failed checks found/),
|
|
1043
|
-
'should show no failed checks message'
|
|
1044
|
-
)
|
|
1045
|
-
})
|
|
1046
|
-
|
|
1047
|
-
test.serial('doctor command should show "No warnings found" when --warnings finds none', async (t) => {
|
|
1048
|
-
const { sandbox, mockLogger } = t.context
|
|
1049
|
-
const consoleStubs = stubConsole(sandbox)
|
|
1050
|
-
|
|
1051
|
-
const mockSections = [
|
|
1052
|
-
{
|
|
1053
|
-
section: 'Environment',
|
|
1054
|
-
results: [
|
|
1055
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1056
|
-
],
|
|
1057
|
-
},
|
|
1058
|
-
]
|
|
1059
|
-
|
|
1060
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1061
|
-
'#lib/doctor/index.js': {
|
|
1062
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1063
|
-
},
|
|
1064
|
-
})
|
|
1065
|
-
|
|
1066
|
-
const command = new DoctorCommand()
|
|
1067
|
-
command.logger = mockLogger
|
|
1068
|
-
|
|
1069
|
-
await command.action([], { warnings: true }, command)
|
|
1070
|
-
|
|
1071
|
-
t.true(
|
|
1072
|
-
calledWithMatch(consoleStubs.log, /No warnings found/),
|
|
1073
|
-
'should show no warnings message'
|
|
1074
|
-
)
|
|
1075
|
-
})
|
|
1076
|
-
|
|
1077
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1078
|
-
// --json [file] file output tests
|
|
1079
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1080
|
-
|
|
1081
|
-
test.serial('doctor command --json <file> should write JSON to file', async (t) => {
|
|
1082
|
-
const { sandbox, mockLogger } = t.context
|
|
1083
|
-
|
|
1084
|
-
const mockSections = [
|
|
1085
|
-
{
|
|
1086
|
-
section: 'Environment',
|
|
1087
|
-
results: [
|
|
1088
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1089
|
-
],
|
|
1090
|
-
},
|
|
1091
|
-
]
|
|
1092
|
-
|
|
1093
|
-
// Mock fs.writeFileSync
|
|
1094
|
-
const writeFileSyncStub = sandbox.stub()
|
|
1095
|
-
|
|
1096
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1097
|
-
'node:fs': {
|
|
1098
|
-
writeFileSync: writeFileSyncStub,
|
|
1099
|
-
},
|
|
1100
|
-
'#lib/doctor/index.js': {
|
|
1101
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1102
|
-
},
|
|
1103
|
-
})
|
|
1104
|
-
|
|
1105
|
-
const command = new DoctorCommand()
|
|
1106
|
-
command.logger = mockLogger
|
|
1107
|
-
|
|
1108
|
-
// When --json has a value, options.json is the filename string
|
|
1109
|
-
await command.action([], { json: 'report.json' }, command)
|
|
1110
|
-
|
|
1111
|
-
t.true(writeFileSyncStub.calledOnce, 'should call writeFileSync once')
|
|
1112
|
-
t.true(
|
|
1113
|
-
writeFileSyncStub.firstCall.args[0].endsWith('report.json'),
|
|
1114
|
-
'should write to specified file'
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
// Verify the content is valid JSON
|
|
1118
|
-
const writtenContent = writeFileSyncStub.firstCall.args[1]
|
|
1119
|
-
t.notThrows(() => JSON.parse(writtenContent.trim()), 'should write valid JSON')
|
|
1120
|
-
|
|
1121
|
-
t.true(
|
|
1122
|
-
mockLogger.info.calledWith(sinon.match(/Results written to/)),
|
|
1123
|
-
'should log success message'
|
|
1124
|
-
)
|
|
1125
|
-
})
|
|
1126
|
-
|
|
1127
|
-
test.serial('doctor command --json (no file) should write to stdout', async (t) => {
|
|
1128
|
-
const { sandbox, mockLogger } = t.context
|
|
1129
|
-
|
|
1130
|
-
const mockSections = [
|
|
1131
|
-
{
|
|
1132
|
-
section: 'Environment',
|
|
1133
|
-
results: [
|
|
1134
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1135
|
-
],
|
|
1136
|
-
},
|
|
1137
|
-
]
|
|
1138
|
-
|
|
1139
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
1140
|
-
|
|
1141
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1142
|
-
'#lib/doctor/index.js': {
|
|
1143
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1144
|
-
},
|
|
1145
|
-
})
|
|
1146
|
-
|
|
1147
|
-
const command = new DoctorCommand()
|
|
1148
|
-
command.logger = mockLogger
|
|
1149
|
-
|
|
1150
|
-
// When --json is used without a value, options.json is true
|
|
1151
|
-
await command.action([], { json: true }, command)
|
|
1152
|
-
|
|
1153
|
-
t.true(consoleLogStub.calledOnce, 'should write to stdout')
|
|
1154
|
-
t.notThrows(() => JSON.parse(consoleLogStub.firstCall.args[0]), 'should output valid JSON')
|
|
1155
|
-
})
|
|
1156
|
-
|
|
1157
|
-
test.serial('doctor command --json <file> should not write to stdout', async (t) => {
|
|
1158
|
-
const { sandbox, mockLogger } = t.context
|
|
1159
|
-
|
|
1160
|
-
const mockSections = [
|
|
1161
|
-
{
|
|
1162
|
-
section: 'Environment',
|
|
1163
|
-
results: [
|
|
1164
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1165
|
-
],
|
|
1166
|
-
},
|
|
1167
|
-
]
|
|
1168
|
-
|
|
1169
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
1170
|
-
const writeFileSyncStub = sandbox.stub()
|
|
1171
|
-
|
|
1172
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1173
|
-
'node:fs': {
|
|
1174
|
-
writeFileSync: writeFileSyncStub,
|
|
1175
|
-
},
|
|
1176
|
-
'#lib/doctor/index.js': {
|
|
1177
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1178
|
-
},
|
|
1179
|
-
})
|
|
1180
|
-
|
|
1181
|
-
const command = new DoctorCommand()
|
|
1182
|
-
command.logger = mockLogger
|
|
1183
|
-
|
|
1184
|
-
await command.action([], { json: 'report.json' }, command)
|
|
1185
|
-
|
|
1186
|
-
t.false(consoleLogStub.called, 'should not write to stdout when file is specified')
|
|
1187
|
-
t.true(writeFileSyncStub.calledOnce, 'should write to file instead')
|
|
1188
|
-
})
|
|
1189
|
-
|
|
1190
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1191
|
-
// --quiet flag tests
|
|
1192
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1193
|
-
|
|
1194
|
-
test.serial('doctor command --quiet alone should produce no output on success', async (t) => {
|
|
1195
|
-
const { sandbox, mockLogger } = t.context
|
|
1196
|
-
|
|
1197
|
-
const mockSections = [
|
|
1198
|
-
{
|
|
1199
|
-
section: 'Environment',
|
|
1200
|
-
results: [
|
|
1201
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1202
|
-
],
|
|
1203
|
-
},
|
|
1204
|
-
]
|
|
1205
|
-
|
|
1206
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
1207
|
-
|
|
1208
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1209
|
-
'#lib/doctor/index.js': {
|
|
1210
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1211
|
-
},
|
|
1212
|
-
})
|
|
1213
|
-
|
|
1214
|
-
const command = new DoctorCommand()
|
|
1215
|
-
command.logger = mockLogger
|
|
1216
|
-
|
|
1217
|
-
// --quiet alone (no --json) should produce no output
|
|
1218
|
-
await command.action([], { quiet: true }, command)
|
|
1219
|
-
|
|
1220
|
-
t.false(consoleLogStub.called, 'should not write to stdout')
|
|
1221
|
-
t.false(mockLogger.info.called, 'should not log any info messages')
|
|
1222
|
-
})
|
|
1223
|
-
|
|
1224
|
-
test.serial('doctor command --quiet alone should set exitCode 1 on failures', async (t) => {
|
|
1225
|
-
const { sandbox, mockLogger } = t.context
|
|
1226
|
-
|
|
1227
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
1228
|
-
const savedExitCode = process.exitCode
|
|
1229
|
-
|
|
1230
|
-
try {
|
|
1231
|
-
const mockSections = [
|
|
1232
|
-
{
|
|
1233
|
-
section: 'Environment',
|
|
1234
|
-
results: [
|
|
1235
|
-
{ id: 'npm', name: 'npm', ok: false, message: 'not found' },
|
|
1236
|
-
],
|
|
1237
|
-
},
|
|
1238
|
-
]
|
|
1239
|
-
|
|
1240
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1241
|
-
'#lib/doctor/index.js': {
|
|
1242
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1243
|
-
},
|
|
1244
|
-
})
|
|
1245
|
-
|
|
1246
|
-
const command = new DoctorCommand()
|
|
1247
|
-
command.logger = mockLogger
|
|
1248
|
-
|
|
1249
|
-
// --quiet alone should still set exitCode 1 on failure
|
|
1250
|
-
await command.action([], { quiet: true }, command)
|
|
1251
|
-
|
|
1252
|
-
t.is(process.exitCode, 1, 'should set process.exitCode to 1')
|
|
1253
|
-
t.false(consoleLogStub.called, 'should not output anything')
|
|
1254
|
-
t.false(mockLogger.info.called, 'should not log any info messages')
|
|
1255
|
-
} finally {
|
|
1256
|
-
process.exitCode = savedExitCode
|
|
1257
|
-
}
|
|
1258
|
-
})
|
|
1259
|
-
|
|
1260
|
-
test.serial('doctor command --quiet --json should suppress stdout JSON output', async (t) => {
|
|
1261
|
-
const { sandbox, mockLogger } = t.context
|
|
1262
|
-
|
|
1263
|
-
const mockSections = [
|
|
1264
|
-
{
|
|
1265
|
-
section: 'Environment',
|
|
1266
|
-
results: [
|
|
1267
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1268
|
-
],
|
|
1269
|
-
},
|
|
1270
|
-
]
|
|
1271
|
-
|
|
1272
|
-
const consoleLogStub = sandbox.stub(console, 'log')
|
|
1273
|
-
|
|
1274
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1275
|
-
'#lib/doctor/index.js': {
|
|
1276
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1277
|
-
},
|
|
1278
|
-
})
|
|
1279
|
-
|
|
1280
|
-
const command = new DoctorCommand()
|
|
1281
|
-
command.logger = mockLogger
|
|
1282
|
-
|
|
1283
|
-
// --quiet with --json should suppress JSON stdout output
|
|
1284
|
-
await command.action([], { quiet: true, json: true }, command)
|
|
1285
|
-
|
|
1286
|
-
t.false(consoleLogStub.called, 'should not write JSON to stdout in quiet mode')
|
|
1287
|
-
})
|
|
1288
|
-
|
|
1289
|
-
test.serial('doctor command --quiet --json <file> should write to file silently', async (t) => {
|
|
1290
|
-
const { sandbox, mockLogger } = t.context
|
|
1291
|
-
|
|
1292
|
-
const mockSections = [
|
|
1293
|
-
{
|
|
1294
|
-
section: 'Environment',
|
|
1295
|
-
results: [
|
|
1296
|
-
{ id: 'node', name: 'Node.js version', ok: true, message: 'v22.0.0' },
|
|
1297
|
-
],
|
|
1298
|
-
},
|
|
1299
|
-
]
|
|
1300
|
-
|
|
1301
|
-
const writeFileSyncStub = sandbox.stub()
|
|
1302
|
-
|
|
1303
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1304
|
-
'node:fs': {
|
|
1305
|
-
writeFileSync: writeFileSyncStub,
|
|
1306
|
-
},
|
|
1307
|
-
'#lib/doctor/index.js': {
|
|
1308
|
-
runAllChecksWithSections: sandbox.stub().resolves(mockSections),
|
|
1309
|
-
},
|
|
1310
|
-
})
|
|
1311
|
-
|
|
1312
|
-
const command = new DoctorCommand()
|
|
1313
|
-
command.logger = mockLogger
|
|
1314
|
-
|
|
1315
|
-
await command.action([], { quiet: true, json: 'report.json' }, command)
|
|
1316
|
-
|
|
1317
|
-
t.true(writeFileSyncStub.calledOnce, 'should write to file')
|
|
1318
|
-
t.false(
|
|
1319
|
-
mockLogger.info.calledWith(sinon.match(/Results written to/)),
|
|
1320
|
-
'should not log success message in quiet mode'
|
|
1321
|
-
)
|
|
1322
|
-
})
|
|
1323
|
-
|
|
1324
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1325
|
-
// --reset flag tests
|
|
1326
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
1327
|
-
|
|
1328
|
-
test.serial('doctor command --reset should clear build status', async (t) => {
|
|
1329
|
-
const { sandbox, mockLogger } = t.context
|
|
1330
|
-
|
|
1331
|
-
const clearStatusStub = sandbox.stub()
|
|
1332
|
-
const runAllChecksStub = sandbox.stub().resolves([])
|
|
1333
|
-
|
|
1334
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1335
|
-
'#lib/conf/build-status.js': {
|
|
1336
|
-
clearStatus: clearStatusStub,
|
|
1337
|
-
},
|
|
1338
|
-
'#lib/doctor/index.js': {
|
|
1339
|
-
runAllChecksWithSections: runAllChecksStub,
|
|
1340
|
-
checkSections: [],
|
|
1341
|
-
SECTION_NAMES: ['environment', 'project', 'outputs'],
|
|
1342
|
-
CHECK_IDS: ['os', 'cli', 'node', 'npm', 'git', 'project', 'deps', '11ty', 'data', 'build', 'pdf', 'epub'],
|
|
1343
|
-
},
|
|
1344
|
-
})
|
|
1345
|
-
|
|
1346
|
-
const command = new DoctorCommand()
|
|
1347
|
-
command.logger = mockLogger
|
|
1348
|
-
|
|
1349
|
-
await command.action([], { reset: true }, command)
|
|
1350
|
-
|
|
1351
|
-
t.true(clearStatusStub.calledOnce, 'should call clearStatus')
|
|
1352
|
-
t.is(clearStatusStub.firstCall.args[0], process.cwd(), 'should clear status for current directory')
|
|
1353
|
-
t.true(
|
|
1354
|
-
mockLogger.info.calledWith(sinon.match(/Build status cleared/)),
|
|
1355
|
-
'should log confirmation message'
|
|
1356
|
-
)
|
|
1357
|
-
})
|
|
1358
|
-
|
|
1359
|
-
test.serial('doctor command --reset should not run checks', async (t) => {
|
|
1360
|
-
const { sandbox, mockLogger } = t.context
|
|
1361
|
-
|
|
1362
|
-
const clearStatusStub = sandbox.stub()
|
|
1363
|
-
const runAllChecksStub = sandbox.stub().resolves([])
|
|
1364
|
-
|
|
1365
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1366
|
-
'#lib/conf/build-status.js': {
|
|
1367
|
-
clearStatus: clearStatusStub,
|
|
1368
|
-
},
|
|
1369
|
-
'#lib/doctor/index.js': {
|
|
1370
|
-
runAllChecksWithSections: runAllChecksStub,
|
|
1371
|
-
checkSections: [],
|
|
1372
|
-
SECTION_NAMES: ['environment', 'project', 'outputs'],
|
|
1373
|
-
CHECK_IDS: ['os', 'cli', 'node', 'npm', 'git', 'project', 'deps', '11ty', 'data', 'build', 'pdf', 'epub'],
|
|
1374
|
-
},
|
|
1375
|
-
})
|
|
1376
|
-
|
|
1377
|
-
const command = new DoctorCommand()
|
|
1378
|
-
command.logger = mockLogger
|
|
1379
|
-
|
|
1380
|
-
await command.action([], { reset: true }, command)
|
|
1381
|
-
|
|
1382
|
-
t.false(runAllChecksStub.called, 'should not run diagnostic checks')
|
|
1383
|
-
})
|
|
1384
|
-
|
|
1385
|
-
test.serial('doctor command --reset --quiet should suppress output', async (t) => {
|
|
1386
|
-
const { sandbox, mockLogger } = t.context
|
|
1387
|
-
|
|
1388
|
-
const clearStatusStub = sandbox.stub()
|
|
1389
|
-
|
|
1390
|
-
const DoctorCommand = await esmock('./doctor.js', {
|
|
1391
|
-
'#lib/conf/build-status.js': {
|
|
1392
|
-
clearStatus: clearStatusStub,
|
|
1393
|
-
},
|
|
1394
|
-
'#lib/doctor/index.js': {
|
|
1395
|
-
runAllChecksWithSections: sandbox.stub().resolves([]),
|
|
1396
|
-
checkSections: [],
|
|
1397
|
-
SECTION_NAMES: ['environment', 'project', 'outputs'],
|
|
1398
|
-
CHECK_IDS: ['os', 'cli', 'node', 'npm', 'git', 'project', 'deps', '11ty', 'data', 'build', 'pdf', 'epub'],
|
|
1399
|
-
},
|
|
1400
|
-
})
|
|
1401
|
-
|
|
1402
|
-
const command = new DoctorCommand()
|
|
1403
|
-
command.logger = mockLogger
|
|
1404
|
-
|
|
1405
|
-
await command.action([], { reset: true, quiet: true }, command)
|
|
1406
|
-
|
|
1407
|
-
t.true(clearStatusStub.calledOnce, 'should still clear status')
|
|
1408
|
-
t.false(mockLogger.info.called, 'should not log any messages in quiet mode')
|
|
1409
|
-
})
|