From 8d703c43be298b95652101b85722a5d93bb6e616 Mon Sep 17 00:00:00 2001 From: DeWitt Gibson Date: Sat, 4 Apr 2026 13:02:36 -0700 Subject: [PATCH] Add peerDependencies and warn on missing peers Expose peerDependencies in the plugin manifest (SDK and entity) and add a non-blocking peer dependency check during plugin install. The installer collects missing peer slugs, logs a warning if any are absent from active plugins, and returns peerWarnings alongside the InstalledPlugin result. Return type updated to include peerWarnings. --- .../src/database/entities/plugin.entity.ts | 1 + .../plugins/installed-plugins.service.ts | 27 +++++++++++++++++-- packages/plugins/sdk/src/index.ts | 2 ++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/core/src/database/entities/plugin.entity.ts b/packages/core/src/database/entities/plugin.entity.ts index 9ad1a50..ef9c834 100644 --- a/packages/core/src/database/entities/plugin.entity.ts +++ b/packages/core/src/database/entities/plugin.entity.ts @@ -43,6 +43,7 @@ export class Plugin { hooks?: string[]; permissions?: string[]; dependencies?: Record; + peerDependencies?: Record; settings?: Record; entryPoint?: string; }; diff --git a/packages/core/src/modules/plugins/installed-plugins.service.ts b/packages/core/src/modules/plugins/installed-plugins.service.ts index 49816ac..830fb80 100644 --- a/packages/core/src/modules/plugins/installed-plugins.service.ts +++ b/packages/core/src/modules/plugins/installed-plugins.service.ts @@ -40,7 +40,7 @@ export class InstalledPluginsService { applicationId: string, pluginId: string, ownerId: string, - ): Promise { + ): Promise { // Verify app ownership const app = await this.appRepo.findOne({ where: { id: applicationId, ownerId }, @@ -97,8 +97,31 @@ export class InstalledPluginsService { pluginId, }); + // Peer dependency check (warning-only, never throws or blocks) + const peerDeps = plugin.manifest?.peerDependencies ?? {}; + const peerWarnings: string[] = []; + if (Object.keys(peerDeps).length > 0) { + const activeInstalled = await this.installedRepo.find({ + where: { applicationId, status: InstalledPluginStatus.ACTIVE }, + relations: ["plugin"], + }); + const activeSlugs = new Set( + activeInstalled.map((i) => i.plugin?.slug).filter(Boolean), + ); + for (const peerSlug of Object.keys(peerDeps)) { + if (!activeSlugs.has(peerSlug)) { + peerWarnings.push(peerSlug); + } + } + if (peerWarnings.length > 0) { + this.logger.warn( + `Plugin "${plugin.name}" installed on app "${app.name}" with missing peer plugins: ${peerWarnings.join(", ")}`, + ); + } + } + this.logger.log(`Plugin ${plugin.name} installed on app ${app.name}`); - return result; + return { ...result, peerWarnings }; } async uninstall( diff --git a/packages/plugins/sdk/src/index.ts b/packages/plugins/sdk/src/index.ts index 4dbe89c..6ada9ce 100644 --- a/packages/plugins/sdk/src/index.ts +++ b/packages/plugins/sdk/src/index.ts @@ -28,6 +28,8 @@ export interface PluginManifest { author?: string; permissions?: string[]; dependencies?: Record; + /** Slugs of plugins this plugin works best alongside. Missing peers trigger a warning but never block install. */ + peerDependencies?: Record; } export interface PluginContext {