diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 09a9773..6f74efe 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -12,6 +12,7 @@ import type { DiagnosticCheck, DiagnosticReport, DiagnosticResult, + DiagnosticSummary, } from '../diagnostics/types.js'; import { EXIT_CODES } from '../errors/errors.js'; import { TerminalOutput } from '../output/terminal.js'; @@ -26,6 +27,22 @@ function formatResult(result: DiagnosticResult): string { return `[${result.status.toUpperCase()}] ${result.label}: ${result.message}`; } +function formatOverallHealth(summary: DiagnosticSummary): string { + if (summary.total === 0) { + return 'Overall health: no diagnostics registered.'; + } + + if (summary.fail > 0) { + return 'Overall health: action required.'; + } + + if (summary.warn > 0) { + return 'Overall health: usable with warnings.'; + } + + return 'Overall health: ready.'; +} + function createDefaultDiagnostics(): readonly DiagnosticCheck[] { return [ ...createRuntimeDiagnostics(), @@ -56,6 +73,7 @@ export function renderDiagnosticReport( output.info( `Summary: ${report.summary.pass} passed, ${report.summary.warn} warnings, ${report.summary.fail} failed.`, ); + output.info(formatOverallHealth(report.summary)); } export function createDoctorCommand( diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index 2ac938e..381dad6 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -45,16 +45,25 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( 'Summary: 0 passed, 0 warnings, 0 failed.', ); + expect(captured.stdoutText()).toContain( + 'Overall health: no diagnostics registered.', + ); expect(captured.stderrText()).toBe(''); }); - it('aggregates pass, warning, and failure results', () => { + it('aggregates pass, warning, and failure results with actionable health output', () => { const captured = createCapturedTerminalOutput(); let exitCode = -1; const command = createDoctorCommand({ checks: [ check('node', 'Node.js', 'pass', 'Supported version detected.'), - check('docker', 'Docker', 'warn', 'Docker is not available.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker is not available.', + 'Install Docker only if the selected workflow requires it.', + ), check( 'stellar', 'Stellar CLI', @@ -78,6 +87,9 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( '[WARN] Docker: Docker is not available.', ); + expect(captured.stdoutText()).toContain( + 'Remediation: Install Docker only if the selected workflow requires it.', + ); expect(captured.stdoutText()).toContain( '[FAIL] Stellar CLI: Required CLI is missing.', ); @@ -87,6 +99,55 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( 'Summary: 1 passed, 1 warnings, 1 failed.', ); + expect(captured.stdoutText()).toContain('Overall health: action required.'); + }); + + it('keeps warning-only diagnostics non-fatal', () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [ + check('node', 'Node.js', 'pass', 'Supported version detected.'), + check( + 'docker', + 'Docker', + 'warn', + 'Docker daemon is unavailable.', + 'Start Docker only when a container-based workflow needs it.', + ), + ], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.success); + expect(captured.stdoutText()).toContain( + 'Summary: 1 passed, 1 warnings, 0 failed.', + ); + expect(captured.stdoutText()).toContain( + 'Overall health: usable with warnings.', + ); + }); + + it('reports a fully healthy diagnostic run clearly', () => { + const captured = createCapturedTerminalOutput(); + let exitCode = -1; + const command = createDoctorCommand({ + checks: [check('node', 'Node.js', 'pass', 'Supported version detected.')], + output: captured.output, + setExitCode: (code) => { + exitCode = code; + }, + }); + + command.parse(['node', 'doctor']); + + expect(exitCode).toBe(EXIT_CODES.success); + expect(captured.stdoutText()).toContain('Overall health: ready.'); }); it('converts an unexpected check exception into a safe failed diagnostic', () => { @@ -114,6 +175,9 @@ describe('doctor command', () => { expect(captured.stdoutText()).toContain( '[FAIL] Unsafe check: Diagnostic check could not complete.', ); + expect(captured.stdoutText()).toContain( + 'Remediation: Retry the command or inspect the relevant tool configuration.', + ); expect(captured.stdoutText()).not.toContain('SECRET_TOKEN'); }); });