Skip to content

Commit 3672201

Browse files
authored
Doctor: expose configured plugin install findings (#96171)
* feat(doctor): expose configured plugin install findings * fix(doctor): satisfy configured plugin mapper lint
1 parent 1b8b850 commit 3672201

4 files changed

Lines changed: 647 additions & 0 deletions

File tree

src/commands/doctor/shared/missing-configured-plugin-install.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,167 @@ describe("repairMissingConfiguredPluginInstalls", () => {
320320
}
321321
});
322322

323+
it("maps a missing configured plugin install to a structured finding and dry-run effect", async () => {
324+
mocks.listChannelPluginCatalogEntries.mockReturnValue([
325+
{
326+
id: "matrix",
327+
pluginId: "matrix",
328+
meta: { label: "Matrix" },
329+
install: {
330+
npmSpec: "@openclaw/[email protected]",
331+
expectedIntegrity: "sha512-test",
332+
},
333+
trustedSourceLinkedOfficialInstall: true,
334+
},
335+
]);
336+
337+
const {
338+
configuredPluginInstallIssueToHealthFinding,
339+
configuredPluginInstallIssueToRepairEffect,
340+
detectConfiguredPluginInstallHealthIssues,
341+
} = await import("./missing-configured-plugin-install.js");
342+
const [issue] = await detectConfiguredPluginInstallHealthIssues({
343+
cfg: {
344+
channels: {
345+
matrix: { enabled: true, homeserver: "https://matrix.example.org" },
346+
},
347+
},
348+
env: {},
349+
});
350+
351+
expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
352+
expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
353+
expect(mocks.writePersistedInstalledPluginIndexInstallRecords).not.toHaveBeenCalled();
354+
expect(issue).toEqual({
355+
kind: "missing-install-record",
356+
pluginId: "matrix",
357+
installSpec: "@openclaw/[email protected]",
358+
});
359+
expect(configuredPluginInstallIssueToHealthFinding(issue)).toMatchObject({
360+
checkId: "core/doctor/configured-plugin-installs",
361+
severity: "warning",
362+
target: "matrix",
363+
fixHint: "Run `openclaw doctor --fix` to install @openclaw/[email protected].",
364+
});
365+
expect(configuredPluginInstallIssueToRepairEffect(issue)).toEqual({
366+
kind: "package",
367+
action: "would-install-configured-plugin",
368+
target: "matrix",
369+
dryRunSafe: false,
370+
});
371+
});
372+
373+
it("maps package-update deferrals to structured findings without installing packages", async () => {
374+
const missingDiscordPath = path.resolve("/missing/discord");
375+
const records = {
376+
discord: {
377+
source: "npm",
378+
spec: "@openclaw/discord",
379+
installPath: missingDiscordPath,
380+
},
381+
};
382+
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue(records);
383+
mocks.listChannelPluginCatalogEntries.mockReturnValue([
384+
{
385+
id: "discord",
386+
pluginId: "discord",
387+
meta: { label: "Discord" },
388+
install: {
389+
npmSpec: "@openclaw/discord",
390+
},
391+
},
392+
]);
393+
394+
const {
395+
configuredPluginInstallIssueToHealthFinding,
396+
configuredPluginInstallIssueToRepairEffect,
397+
detectConfiguredPluginInstallHealthIssues,
398+
} = await import("./missing-configured-plugin-install.js");
399+
const [issue] = await detectConfiguredPluginInstallHealthIssues({
400+
cfg: {
401+
plugins: {
402+
entries: {
403+
discord: { enabled: true },
404+
},
405+
},
406+
channels: {
407+
discord: { enabled: true },
408+
},
409+
},
410+
env: {
411+
OPENCLAW_UPDATE_IN_PROGRESS: "1",
412+
OPENCLAW_UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR: "1",
413+
},
414+
});
415+
416+
expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
417+
expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
418+
expect(issue).toEqual({
419+
kind: "deferred-package-manager-repair",
420+
pluginId: "discord",
421+
installPath: missingDiscordPath,
422+
});
423+
expect(configuredPluginInstallIssueToHealthFinding(issue)).toMatchObject({
424+
checkId: "core/doctor/configured-plugin-installs",
425+
severity: "warning",
426+
path: missingDiscordPath,
427+
target: "discord",
428+
});
429+
expect(configuredPluginInstallIssueToRepairEffect(issue)).toEqual({
430+
kind: "package",
431+
action: "would-defer-configured-plugin-install-repair",
432+
target: "discord",
433+
dryRunSafe: true,
434+
});
435+
});
436+
437+
it("reports one finding when a configured plugin record points at a missing package", async () => {
438+
const missingDiscordPath = path.resolve("/missing/discord");
439+
const records = {
440+
discord: {
441+
source: "npm",
442+
spec: "@openclaw/discord",
443+
installPath: missingDiscordPath,
444+
},
445+
};
446+
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue(records);
447+
mocks.listChannelPluginCatalogEntries.mockReturnValue([
448+
{
449+
id: "discord",
450+
pluginId: "discord",
451+
meta: { label: "Discord" },
452+
install: {
453+
npmSpec: "@openclaw/discord",
454+
},
455+
},
456+
]);
457+
458+
const { detectConfiguredPluginInstallHealthIssues } =
459+
await import("./missing-configured-plugin-install.js");
460+
const issues = await detectConfiguredPluginInstallHealthIssues({
461+
cfg: {
462+
plugins: {
463+
entries: {
464+
discord: { enabled: true },
465+
},
466+
},
467+
channels: {
468+
discord: { enabled: true },
469+
},
470+
},
471+
env: {},
472+
});
473+
474+
expect(issues).toEqual([
475+
{
476+
kind: "missing-installed-payload",
477+
pluginId: "discord",
478+
installPath: missingDiscordPath,
479+
installSpec: "@openclaw/discord",
480+
},
481+
]);
482+
});
483+
323484
it("installs a missing configured OpenClaw channel plugin from npm by default", async () => {
324485
mocks.listChannelPluginCatalogEntries.mockReturnValue([
325486
{

0 commit comments

Comments
 (0)