fix: propagate settingsSchema

This commit is contained in:
Linus Rath
2026-05-06 01:48:49 +02:00
parent e68fcb4aec
commit 09302684da
5 changed files with 41 additions and 1 deletions
+3
View File
@@ -187,6 +187,9 @@ export async function POST(request: NextRequest) {
...(manifest.configSchema && typeof manifest.configSchema === 'object' ...(manifest.configSchema && typeof manifest.configSchema === 'object'
? { configSchema: manifest.configSchema as ServerPlugin['configSchema'] } ? { configSchema: manifest.configSchema as ServerPlugin['configSchema'] }
: {}), : {}),
...(manifest.settingsSchema && typeof manifest.settingsSchema === 'object'
? { settingsSchema: manifest.settingsSchema as ServerPlugin['settingsSchema'] }
: {}),
...(declaredFrameOrigins.length > 0 ...(declaredFrameOrigins.length > 0
? { frameOrigins: declaredFrameOrigins } ? { frameOrigins: declaredFrameOrigins }
: {}), : {}),
+3 -1
View File
@@ -43,7 +43,9 @@ export async function GET() {
dev: p.dev, dev: p.dev,
// Surface so clients can enforce api.http.fetch origin allowlists. // Surface so clients can enforce api.http.fetch origin allowlists.
httpOrigins: p.httpOrigins, httpOrigins: p.httpOrigins,
settingsSchema: undefined, // Will be read from the bundle's manifest // Per-user settings schema, captured from the manifest at upload/load
// time so the client can render the settings UI without re-parsing.
settingsSchema: p.settingsSchema,
})); }));
// Only serve enabled themes // Only serve enabled themes
+3
View File
@@ -185,6 +185,9 @@ async function loadDevPlugin(pluginDir: string): Promise<DevPluginEntry | null>
...(manifest.configSchema && typeof manifest.configSchema === 'object' ...(manifest.configSchema && typeof manifest.configSchema === 'object'
? { configSchema: manifest.configSchema as ServerPlugin['configSchema'] } ? { configSchema: manifest.configSchema as ServerPlugin['configSchema'] }
: {}), : {}),
...(manifest.settingsSchema && typeof manifest.settingsSchema === 'object'
? { settingsSchema: manifest.settingsSchema as ServerPlugin['settingsSchema'] }
: {}),
...(frameOrigins.length > 0 ? { frameOrigins } : {}), ...(frameOrigins.length > 0 ? { frameOrigins } : {}),
...(httpOrigins.length > 0 ? { httpOrigins } : {}), ...(httpOrigins.length > 0 ? { httpOrigins } : {}),
installedAt, installedAt,
+16
View File
@@ -28,6 +28,21 @@ export interface PluginConfigField {
options?: { label: string; value: string }[]; options?: { label: string; value: string }[];
} }
/**
* Per-user setting field, mirrors the manifest's `settingsSchema` shape
* (see lib/plugin-types.ts SettingFieldSchema). The server passes these
* through unchanged so the client can render the per-user settings UI.
*/
export interface PluginSettingsField {
type: 'boolean' | 'string' | 'number' | 'select';
label: string;
description?: string;
default: unknown;
options?: string[];
min?: number;
max?: number;
}
export interface ServerPlugin { export interface ServerPlugin {
id: string; id: string;
name: string; name: string;
@@ -40,6 +55,7 @@ export interface ServerPlugin {
enabled: boolean; enabled: boolean;
forceEnabled?: boolean; forceEnabled?: boolean;
configSchema?: Record<string, PluginConfigField>; configSchema?: Record<string, PluginConfigField>;
settingsSchema?: Record<string, PluginSettingsField>;
installedAt: string; installedAt: string;
updatedAt: string; updatedAt: string;
/** /**
+16
View File
@@ -313,6 +313,8 @@ interface ServerPluginInfo {
dev?: boolean; dev?: boolean;
/** Allowlist of origins this plugin may target via api.http.fetch(). */ /** Allowlist of origins this plugin may target via api.http.fetch(). */
httpOrigins?: string[]; httpOrigins?: string[];
/** Per-user settings schema, captured from the manifest server-side. */
settingsSchema?: InstalledPlugin['settingsSchema'];
} }
const SERVER_MANAGED_KEY = 'server-managed-plugin-ids'; const SERVER_MANAGED_KEY = 'server-managed-plugin-ids';
@@ -412,6 +414,7 @@ async function syncServerPlugins(
forceEnabled: sp.forceEnabled, forceEnabled: sp.forceEnabled,
adminApproved: true, // Server-managed plugins are always approved adminApproved: true, // Server-managed plugins are always approved
settings: {}, settings: {},
settingsSchema: sp.settingsSchema,
bundleHash: sp.bundleHash, bundleHash: sp.bundleHash,
...(sp.httpOrigins && sp.httpOrigins.length > 0 ...(sp.httpOrigins && sp.httpOrigins.length > 0
? { httpOrigins: sp.httpOrigins } ? { httpOrigins: sp.httpOrigins }
@@ -452,6 +455,7 @@ async function syncServerPlugins(
forceEnabled: sp.forceEnabled, forceEnabled: sp.forceEnabled,
bundleHash: sp.bundleHash, bundleHash: sp.bundleHash,
httpOrigins: sp.httpOrigins, httpOrigins: sp.httpOrigins,
settingsSchema: sp.settingsSchema,
} }
: p : p
), ),
@@ -464,10 +468,22 @@ async function syncServerPlugins(
...p, ...p,
managed: true, managed: true,
forceEnabled: sp.forceEnabled, forceEnabled: sp.forceEnabled,
settingsSchema: sp.settingsSchema,
} }
: p : p
), ),
})); }));
} else if (
JSON.stringify(local.settingsSchema ?? null) !== JSON.stringify(sp.settingsSchema ?? null)
) {
// Schema drift: the bundle is current but the persisted plugin record
// pre-dates the server passing settingsSchema through, so the per-user
// settings UI was rendering empty. Patch the schema in place.
set(state => ({
plugins: state.plugins.map(p =>
p.id === sp.id ? { ...p, settingsSchema: sp.settingsSchema } : p
),
}));
} else if (sp.forceEnabled && !local.enabled) { } else if (sp.forceEnabled && !local.enabled) {
// Force-enable if the server says so but client has it disabled // Force-enable if the server says so but client has it disabled
set(state => ({ set(state => ({