From 7bd7d7d2a13cecc535c7ea2ca27092fbbfd6f4a0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 1 Aug 2026 04:51:43 -0700 Subject: [PATCH] fix(plugins): surface actionable doctor warnings (#117357) Co-authored-by: Peter Steinberger --- src/cli/plugins-cli.list.test.ts | 49 ++++++++++++++++++++++++++++++++ src/cli/plugins-cli.runtime.ts | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/cli/plugins-cli.list.test.ts b/src/cli/plugins-cli.list.test.ts index b8df7ae7037c..b145fcafa7eb 100644 --- a/src/cli/plugins-cli.list.test.ts +++ b/src/cli/plugins-cli.list.test.ts @@ -100,6 +100,55 @@ describe("plugins cli list", () => { expect(runtimeLogs).toContain(cleanDoctorMessage); }); + it.each([ + { + description: "a required plugin is missing", + diagnostic: { + level: "warn" as const, + pluginId: "calendar", + message: 'plugin "calendar" requires plugin "contacts"; install "contacts" to use it', + }, + expected: 'calendar: plugin "calendar" requires plugin "contacts"', + }, + { + description: "discovery cannot read an extensions directory", + diagnostic: { + level: "warn" as const, + message: "failed to read extensions dir: /tmp/plugins (permission denied)", + }, + expected: "failed to read extensions dir: /tmp/plugins (permission denied)", + }, + ])( + "reports actionable discovery warnings when $description", + async ({ diagnostic, expected }) => { + buildPluginDiagnosticsReport.mockReturnValue({ plugins: [], diagnostics: [diagnostic] }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain("Diagnostics:"); + expect(output).toContain(expected); + expect(output).not.toContain(cleanDoctorMessage); + }, + ); + + it("keeps actionable discovery warnings alongside existing errors", async () => { + buildPluginDiagnosticsReport.mockReturnValue({ + plugins: [], + diagnostics: [ + { level: "error", pluginId: "broken", message: "plugin manifest invalid" }, + { level: "warn", pluginId: "calendar", message: "required plugin contacts is missing" }, + ], + }); + + await runPluginsCommand(["plugins", "doctor"]); + + const output = runtimeLogs.join("\n"); + expect(output).toContain("broken: plugin manifest invalid"); + expect(output).toContain("calendar: required plugin contacts is missing"); + expect(output).not.toContain(cleanDoctorMessage); + }); + it("reports stale plugin config in doctor output without claiming full plugin health", async () => { const sourceConfig = { plugins: { diff --git a/src/cli/plugins-cli.runtime.ts b/src/cli/plugins-cli.runtime.ts index ed172bf70782..e52f5dfa4679 100644 --- a/src/cli/plugins-cli.runtime.ts +++ b/src/cli/plugins-cli.runtime.ts @@ -375,7 +375,7 @@ export async function runPluginsDoctorCommand(): Promise { | undefined; const report = buildPluginDiagnosticsReport({ config: cfg, effectiveOnly: true }); const errors = report.plugins.filter((p) => p.status === "error"); - const diags = report.diagnostics.filter((d) => d.level === "error"); + const diags = report.diagnostics.filter((entry) => !isConfigSelectedShadowDiagnostic(entry)); const shadowed = report.diagnostics.filter((entry) => isErroredConfigSelectedShadowDiagnostic({ entry, plugins: report.plugins }), );