-
Notifications
You must be signed in to change notification settings - Fork 0
fix: harden plugin infrastructure — safety and validation #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
961c0ae
470f13f
d175a38
7832557
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -73,21 +73,40 @@ for (const plugin of BUILT_IN_PLUGINS) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Same-type duplicates without overrides throw loudly so operators spot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the conflict at startup instead of debugging a silent replacement. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const { plugin, overrides } of EXTERNAL_PLUGINS) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pluginRegistry.has(plugin.type)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!overrides) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `External plugin "${plugin.type}" conflicts with an existing plugin. ` + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `Set "overrides": true in neoboard-plugins.json to replace the built-in.`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!plugin || typeof plugin !== "object" || !plugin.type) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "External plugin skipped: invalid plugin object (missing type)", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pluginRegistry.unregister(plugin.type); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pluginRegistry.has(plugin.type)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!overrides) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'External plugin "' + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plugin.type + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '" conflicts with an existing plugin. ' + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Set "overrides": true in neoboard-plugins.json to replace it. Skipping.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pluginRegistry.unregister(plugin.type); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pluginRegistry.register(plugin); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "External plugin registration failed for type " + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JSON.stringify(plugin?.type) + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ":", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Continue loading remaining plugins — one broken plugin shouldn't crash the app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pluginRegistry.register(plugin); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Startup validation ────────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify that every chart type declared in CHART_TYPES has a registered | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // plugin. A mismatch is a dev-time bug, not a runtime error. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1. Every CHART_TYPES entry must have a registered plugin. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const registeredTypes = new Set(pluginRegistry.getTypes()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const t of CHART_TYPES) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!registeredTypes.has(t)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -97,6 +116,25 @@ for (const t of CHART_TYPES) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2. Validate compatibleWith references actual connector types. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const KNOWN_CONNECTORS = new Set(["neo4j", "postgresql"]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const type of pluginRegistry.getTypes()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const plugin = pluginRegistry.get(type); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (plugin?.compatibleWith) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const ct of plugin.compatibleWith) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!KNOWN_CONNECTORS.has(ct)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Plugin "' + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '" declares compatibleWith "' + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ct + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '" but no such connector is registered', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+119
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Locate canonical connector type sources and any registry exports the app could use.
fd -t f 'connector-types'
rg -nP --type=ts -C2 '\bCONNECTOR_TYPES\b'
rg -nP --type=ts -C2 'createConnectorRegistry|connectorRegistry'Repository: alfredo1996/neoboard Length of output: 14318 🏁 Script executed: cat -n app/src/plugins/index.ts | sed -n '100,150p'Repository: alfredo1996/neoboard Length of output: 1522 🏁 Script executed: head -50 app/src/plugins/index.tsRepository: alfredo1996/neoboard Length of output: 1805 🏁 Script executed: head -60 connection/src/index.ts | cat -nRepository: alfredo1996/neoboard Length of output: 1764 🏁 Script executed: rg -n "export.*connectorRegistry" connection/src/index.tsRepository: alfredo1996/neoboard Length of output: 46 🏁 Script executed: rg -A5 "connectorRegistry," connection/src/index.tsRepository: alfredo1996/neoboard Length of output: 196 🏁 Script executed: grep -r "from.*connection" app/src/lib --include="*.ts" --include="*.tsx" | head -10Repository: alfredo1996/neoboard Length of output: 1054 Replace hardcoded Line 120 duplicates the connector list from Proposed fix// 2. Validate compatibleWith references actual connector types.
+import { CONNECTOR_TYPES } from "@neoboard/connection/connector-types";
-const KNOWN_CONNECTORS = new Set(["neo4j", "postgresql"]);
+const KNOWN_CONNECTORS = new Set(CONNECTOR_TYPES);
for (const type of pluginRegistry.getTypes()) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Re-export for convenience | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export { pluginRegistry } from "./registry"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export { CHART_TYPES, type ChartType } from "./chart-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors aren't surfaced in the widget UI — falls short of the linked objective.
Issue
#614calls for "surfacing errors in the affected widget rather than crashing the dashboard." Right now, a transform failure silently falls back to raw data and only logs to the console — users see what looks like a working chart with subtly wrong data (or, when chart transform fails, the chart-shaped renderer receives raw rows of an unexpected shape and may itself misrender). At minimum, render an inline warning banner (similar to the truncation notice at lines 631–639) whentransformErroris set.Also applies to: 610-613
🤖 Prompt for AI Agents